2
votes

Développez le pack de modèles Variadic par paires

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 >


2 commentaires

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?


3 Réponses :


1
votes

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;

Démo a >


0 commentaires

2
votes

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


2 commentaires

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.



0
votes

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


0 commentaires