Controlla i tratti per tutti gli argomenti del modello variadic

Background: Ho creato la seguente class C , il cui costruttore dovrebbe prendere N variabili di tipo B& :

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

Problema: il mio problema è come limitare gli Arg variadici a essere di tipo B ?

La mia soluzione parziale: volevo definire un predicato come:

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

E ridefinire di conseguenza il mio costruttore:

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

Ho visto una ansible soluzione su questo post: https://stackoverflow.com/a/11414631 , che definisce un predicato check_all generico:

 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> {}; 

Quindi, potrei scrivere qualcosa come:

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

Domanda 1: Non so come definire il Trait , perché ho bisogno in qualche modo di bind std::is_same con B come primo argomento. C’è qualche mezzo per usare il check_all generico nel mio caso, o la grammatica attuale del C ++ è incompatibile?

Domanda 2: Il mio costruttore dovrebbe accettare anche le classi derivate di B (attraverso un riferimento a B ), è un problema per la deduzione degli argomenti del modello? Temo che se utilizzo un predicato come std::is_base_of , std::is_base_of una diversa istanziazione del costruttore per ogni set di parametri, che potrebbe aumentare la dimensione del codice compilato …

Modifica: Ad esempio, ho B1 e B2 che ereditano da B , chiamo C(b1, b1) e C(b1, b2) nel mio codice, creeremo due istanze (di C::C e C::C )? Voglio solo le istanze di C::C .

Definisci all_true come

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

E riscrivi il tuo costruttore su

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

In alternativa, sotto C ++ 17,

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

Temo che se utilizzo un predicato come std :: is_base_of, otterrò una diversa istanziazione del costruttore per ogni set di parametri, che potrebbe aumentare la dimensione del codice compilato …

enable_if_t< …> restituirà sempre il tipo void (con solo un argomento template dato), quindi questo non può essere l’errore di is_base_of . Tuttavia, quando Args ha tipi diversi, cioè i tipi degli argomenti sono distinti, verranno successivamente istanziate diverse specializzazioni. Mi aspetterei comunque che un compilatore ottimizzasse qui.


Se si desidera che il costruttore prenda esattamente N argomenti, è ansible utilizzare un metodo un po ‘più semplice. Definire

 template  using ignore_val = T; 

E ora parzialmente specializzato C come

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

La definizione del costruttore all’interno della specializzazione parziale diventa ora banale

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

Inoltre, non devi più preoccuparti di un sacco di specializzazioni.

 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;} }; 

Il implicit_cast è anche in Boost, il bool_pack rubato da Columbo .

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

Opzione uno, se è implicitamente convertibile è abbastanza buono

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

Opzione due, solo se sono di derivazione pubblica da B e sono convertibili senza ambiguità:

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

Il tipo ctor-template-argument senza nome è void in qualsiasi sostituzione riuscita.