Überprüfen Sie die Eigenschaften für alle variablen Vorlagenargumente

Hintergrund: Ich habe die folgende class C , deren Konstruktor N Variablen des Typs B& :

 class A; class B { A* getA(); }; template class C { public: template inline C(Args&... args) : member{args.getA()...} {} private: std::array member; }; 

Problem: Mein Problem ist, wie man die variadischen Args so Args , dass sie alle vom Typ B .

Meine Teillösung: Ich wollte ein Prädikat wie definieren:

 template  struct is_range_of : std::true_type // if Args is N copies of T std::false_type // otherwise {}; 

Und definiere meinen Konstruktor entsprechend neu:

 template <typename... Args, typename = typename std::enable_if<is_range_of_::value>::type > inline C(Args&... args); 

Ich habe eine mögliche Lösung für diesen Beitrag gesehen: https://stackoverflow.com/a/11414631 , die ein generisches check_all Prädikat definiert:

 template <template class Trait, typename... Args> struct check_all : std::false_type {}; template <template class Trait> struct check_all : std::true_type {}; template <template class Trait, typename T, typename... Args> struct check_all : std::integral_constant<bool, Trait::value && check_all::value> {}; 

Also könnte ich etwas schreiben wie:

 template  struct is_range_of : std::integral_constant<bool, sizeof...(Args) == N && check_all::value > {}; 

Frage 1: Ich weiß nicht, wie ich die std::is_same definieren soll, weil ich irgendwie std::is_same mit B als erstes Argument binden muss. Gibt es irgendwelche Möglichkeiten, das generische check_all in meinem Fall zu verwenden, oder ist die aktuelle Grammatik von C ++ inkompatibel?

Frage 2: Mein Konstruktor sollte auch abgeleitete classn von B akzeptieren (durch einen Verweis auf B ), ist es ein Problem für die Ableitung von Template-Argumenten? Ich befürchte, dass ich, wenn ich ein Prädikat wie std::is_base_of , eine andere Instantiierung des Konstruktors für jeden Satz von Parametern bekomme, was die kompilierte Code-Größe erhöhen könnte …

Edit: Zum Beispiel, ich habe B1 und B2 , die von B erbt, ich rufe C(b1, b1) und C(b1, b2) in meinem Code, wird es zwei Instanzen erstellen (von C::C und C::C )? Ich möchte nur Instanzen von C::C .

    Definiere all_true als

     template  struct bool_pack; template  using all_true = std::is_same, bool_pack>; 

    Und schreiben Sie Ihren Konstruktor um

     // Check convertibility to B&; also, use the fact that getA() is non-const template{}...>> C(Args&... args) : member{args.getA()...} {} 

    Alternativ, unter C ++ 17,

     template && ...)>> C(Args&... args) : member{args.getA()...} {} 

    Ich befürchte, dass ich, wenn ich ein Prädikat wie std :: is_base_of verwende, eine andere Instantiierung des Konstruktors für jeden Satz von Parametern bekomme, was die kompilierte Code-Größe erhöhen könnte …

    enable_if_t< …> liefert immer den Typ void (mit nur einem Template-Argument), also kann dies nicht der Fehler von is_base_of sein. Wenn jedoch Args unterschiedliche Typen aufweist, dh die Typen der Argumente unterschiedlich sind, werden anschließend verschiedene Spezialisierungen instanziiert. Ich würde allerdings erwarten, dass ein Compiler hier optimiert wird.


    Wenn der Konstruktor genau N Argumente annehmen soll, können Sie eine etwas einfachere Methode verwenden. Definieren

     template  using ignore_val = T; 

    Und jetzt spezialisieren Sie sich teilweise auf C als

     // Unused primary template template > class C; // Partial specialization template  class C> { /* … */ }; 

    Die Definition des Konstruktors innerhalb der Teilspezialisierung wird nun trivial

     C(ignore_val... args) : member{args.getA()...} {} 

    Außerdem müssen Sie sich nicht mehr um eine Menge Spezialisierungen kümmern.

     namespace detail { template  struct bool_pack; template  using all_true = std::is_same, bool_pack>; template constexpr X implicit_cast(std::enable_if_t x) {return x;} }; 

    Der implicit_cast ist auch in Boost, dem bool_pack von Columbo gestohlen.

     // Only callable with static argument-types `B&`, uses SFINAE template...>>> C(ARGS&... args) noexcept : member{args.getA()...} {} 

    Option eins, wenn es implizit umwandelbar ist, ist das gut genug

     template(std::declval())), ARGS&>...>> C(ARGS&... args) noexcept(noexcept(implicit_cast(args)...)) : C(implicit_cast(args)...) {} 

    Option zwei, nur wenn sie öffentlich von B abgeleitet sind und eindeutig konvertierbar sind:

     // Otherwise, convert to base and delegate template(std::declval())..., void())> C(ARGS&... args) noexcept : C(implicit_cast(args)...) {} 

    Der unbenannte ctor-template-argument-type ist bei jeder erfolgreichen Substitution void .