Je crée une collection générique de Node
. Chaque Node
a un type Début
et Fin
. Et le type End
de l'un doit correspondre au type Start
du suivant.
Si je devais lister chacun des types de la collection, le constructeur ressemblerait à ceci (pour quatre types):
template <typename Start, typename End> class Node { }; template <typename A, typename B, typename C, typename D> class Collection { public: Collection(Node<A, B> n1, Node<B, C> n2, Node<C, D> n3) { } };
Mais quand j'essaie d'écrire constuctor comme modèle variadic pour prendre en charge n'importe quel nombre de types, je suis perplexe.
p >
3 Réponses :
Avec une certaine indirection, vous pouvez faire:
template <typename Start, typename End> class Node { // ... }; // Implementation using the Nodes // You might add typedef in Node to retrieve Start/End if needed (or create traits) template <typename ... Nodes> struct CollectionImpl { CollectionImpl(Nodes ... ns) : nodes(ns...){} std::tuple<Nodes...> nodes; // You probably want something like that }; // Helper class to build the type template <typename Seq, typename Tup> struct CollectionMaker; template <std::size_t ... Is, typename Tuple> struct CollectionMaker<std::index_sequence<Is...>, Tuple> { using type = CollectionImpl<Node<std::tuple_element_t<Is, Tuple>, std::tuple_element_t<Is + 1, Tuple>>...>; }; // Wanted interface. template <typename ... Ts> using Collection = typename CollectionMaker<std::make_index_sequence<sizeof...(Ts) - 1>, std::tuple<Ts...>>::type;
Je propose une solution un peu différente.
Étant donné une structure tag
triviale pour envelopper un type générique (pour éviter les problèmes avec des types non constructibles par défaut dans std :: tuples code > s)
#include <tuple> #include <type_traits> template <typename Start, typename End> class Node { }; struct A {}; struct B {}; struct C {}; template <typename> struct tag { }; template <typename...> struct getTpls; template <std::size_t ... Is, typename ... Ts> struct getTpls<std::index_sequence<Is...>, Ts...> { using tpl0 = std::tuple<tag<Ts>...>; using ftpl = std::tuple<std::tuple_element_t<Is, tpl0>...>; using stpl = std::tuple<std::tuple_element_t<1u+Is, tpl0>...>; }; template <typename ... Ts> struct Collection { static_assert( sizeof...(Ts) > 1u, "more types, please"); using getT = getTpls<std::make_index_sequence<sizeof...(Ts)-1u>, Ts...>; using ftpl = typename getT::ftpl; using stpl = typename getT::stpl; template <typename ... FTs, typename ... STs, std::enable_if_t< std::is_same_v<ftpl, std::tuple<tag<FTs>...>> && std::is_same_v<stpl, std::tuple<tag<STs>...>>, int> = 0> Collection (Node<FTs, STs> ...) { } }; int main () { Collection<A, B, C> c0{Node<A, B>{}, Node<B, C>{}}; // compile // Collection<A, B, B> c1{Node<A, B>{}, Node<B, C>{}}; // error! }
et une structure d'assistance qui définit 2 types basés sur std::tuple
template <typename ... Ts> struct Collection { static_assert( sizeof...(Ts) > 1u, "more types, please"); using getT = getTpls<std::make_index_sequence<sizeof...(Ts)-1u>, Ts...>; using ftpl = typename getT::ftpl; using stpl = typename getT::stpl; template <typename ... FTs, typename ... STs, std::enable_if_t< std::is_same_v<ftpl, std::tuple<tag<FTs>...>> && std::is_same_v<stpl, std::tuple<tag<STs>...>>, int> = 0> Collection (Node<FTs, STs> ...) { } };
Merci, j'aime la façon dont votre réponse laisse le constructeur ressembler le plus aux données prévues qui le remplissent. Je ne pense pas que les modèles devraient jamais masquer l'intention de la fonction.
La partie qui me manquait dans mon code était de savoir comment déduire les types FT et ST. Je ne savais pas que vous pouviez utiliser enable_if pour faire cela.
Basé sur la réponse de max66:
Cela nettoie la structure tag
inutile et simplifie la index_sequence
en récursivité directe (similaire à la définition de tuple). p>
template <typename Owner, typename Value> class Node { }; struct A {}; struct B {}; struct C {}; struct D {}; template <typename First, typename...Rest> std::tuple<First, Rest...> tuple_push_front(std::tuple<Rest...>); template <typename T1, typename T2, typename...T> struct NodeCollector { private: using nodeRest = NodeCollector<T2, T...>; public: using tplOwners = decltype(tuple_push_front<T1>(std::declval<typename nodeRest::tplOwners>())); using tplValues = decltype(tuple_push_front<T2>(std::declval<typename nodeRest::tplValues>())); }; template <typename T1, typename T2> struct NodeCollector<T1, T2> { public: using tplOwners = std::tuple<T1>; using tplValues = std::tuple<T2>; }; template <typename...Ts> class Collection { static_assert( sizeof...(Ts) > 1u, "Collection requires at least two types."); private: using nodeCollector = NodeCollector<Ts...>; public: template <typename...OTs, typename...VTs, typename=std::enable_if_t< (std::is_same_v<typename nodeCollector::tplOwners, std::tuple<OTs...>> && std::is_same_v<typename nodeCollector::tplValues, std::tuple<VTs...>>)> > Collection(Node<OTs, VTs>...) { } }; int main() { Collection<A, B, C, D> c{Node<A, B>{}, Node<B, C>{}, Node<C, D>{}}; std::cout << demangle(typeid(c).name()) << std::endl; }
Ce serait formidable de montrer votre tentative d'écriture d'un constructeur avec un modèle variadique. Nous pourrons peut-être non seulement vous aider, mais aussi analyser votre processus de réflexion et indiquer où exactement la logique que vous avez proposée était imparfaite.
Pouvez-vous utiliser C ++ 17?