Search code examples
c++c++17variadic-templatessfinaetemplate-argument-deduction

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.


Solution

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