Rhaokiel
Rhaokiel

Reputation: 843

Expand Variadic Template Pack by Pairs

I'm creating a generic collection of Nodes. Each Node has a Start and End type. And the End type of one must match the Start type of the next.

If I were to list each of the types in the collection the constructor would look like this (for four 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) { }
};

But when I try to write constuctor as a variadic template to support any number of types, I am stumped.

Upvotes: 2

Views: 569

Answers (3)

Rhaokiel
Rhaokiel

Reputation: 843

Based on max66's answer:

This cleans up the unnecessary tag structure and simplifies the index_sequence to direct recursion (similar to the definition of tuple).

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

Upvotes: 1

max66
max66

Reputation: 66210

I propose a little different solution.

Given a trivial tag struct to wrap a generic type (to avoid problems with types not default constructible in std::tupless)

template <typename>
struct tag
 { };

and an helper struct that define 2 types based on std::tuple

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

you can write Collection as follows

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

The following is a full compiling example

#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!
 }

Upvotes: 2

Jarod42
Jarod42

Reputation: 217468

With some indirection, you might do:

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;

Demo

Upvotes: 1

Related Questions