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.