c++templatesvariadic-templatestemplate-meta-programmingtemplate-inheritance

Split paramter pack


I would like to split a template parameter pack. Something like this. How could I go about doing this?

template< typename... Pack >
struct TypeB : public TypeA< get<0, sizeof...(Pack)/2>(Pack...) >
             , public TypeA< get<sizeof...(Pack)/2, sizeof...(Pack)>(Pack...) > 
{
};

Here is my take on why this question is not a duplicate: I am looking for a generalised way to do this that will work when passing the split pack to other template classes - as shown above. As well as passing it to functions.


Solution

  • I suppose there are many ways to do this.

    If you can use at least C++14, I propose to use the power of decltype() and std::tuple_cat() as follow:

    (1) declare (there is no reason of define because are used through decltype() a couple of overloaded (and SFINAE enabled/disabled) as follows

    template <std::size_t Imin, std::size_t Imax, std::size_t I, typename T>
    std::enable_if_t<(Imin <= I) && (I < Imax), std::tuple<T>> getTpl ();
    
    template <std::size_t Imin, std::size_t Imax, std::size_t I, typename>
    std::enable_if_t<(I < Imin) || (Imax <= I), std::tuple<>> getTpl ();
    

    The idea is return a std::tuple<T> when the index is in the right range, a std::tuple<> otherwise.

    (2) define an helper class to convert a std::tuple<Ts...> to a TypeA<Ts...>

    template <typename>
    struct pta_helper2;
    
    template <typename ... Ts>
    struct pta_helper2<std::tuple<Ts...>>
     { using type = TypeA<Ts...>; };
    

    (3) define an helper class that concatenate in a tuple only the types in the correct range

    template <std::size_t, std::size_t, typename ... Ts>
    struct pta_helper1;
    
    template <std::size_t I0, std::size_t I1, std::size_t ... Is, typename ... Ts>
    struct pta_helper1<I0, I1, std::index_sequence<Is...>, Ts...>
     : public pta_helper2<decltype(std::tuple_cat(getTpl<I0, I1, Is, Ts>()...))>
     { };
    

    The idea is concatenate a sequence of std::tuple<> and std::tuple<T>, where the T types are the type inside the requested range; the resulting type (the template argument of pta_helper2) is a std::tuple<Us...> where the Us... are exactly the types in the requested range.

    (4) define a using type to use the preceding helper class in a simpler way

    template <std::size_t I0, std::size_t I1, typename ... Ts>
    using proTypeA = typename pta_helper1<
       I0, I1, std::make_index_sequence<sizeof...(Ts)>, Ts...>::type;
    

    (5) now your TypeB simply become

    template <typename ... Ts>
    struct TypeB : public proTypeA<0u, sizeof...(Ts)/2u, Ts...>,
                   public proTypeA<sizeof...(Ts)/2u, sizeof...(Ts), Ts...>
     { };
    

    The following is a full compiling C++14 example example

    #include <tuple>
    #include <type_traits>
    
    template <typename ...>
    struct TypeA
     { };
    
    template <std::size_t Imin, std::size_t Imax, std::size_t I, typename T>
    std::enable_if_t<(Imin <= I) && (I < Imax), std::tuple<T>> getTpl ();
    
    template <std::size_t Imin, std::size_t Imax, std::size_t I, typename>
    std::enable_if_t<(I < Imin) || (Imax <= I), std::tuple<>> getTpl ();
    
    template <typename>
    struct pta_helper2;
    
    template <typename ... Ts>
    struct pta_helper2<std::tuple<Ts...>>
     { using type = TypeA<Ts...>; };
    
    template <std::size_t, std::size_t, typename ... Ts>
    struct pta_helper1;
    
    template <std::size_t I0, std::size_t I1, std::size_t ... Is, typename ... Ts>
    struct pta_helper1<I0, I1, std::index_sequence<Is...>, Ts...>
     : public pta_helper2<decltype(std::tuple_cat(getTpl<I0, I1, Is, Ts>()...))>
     { };
    
    template <std::size_t I0, std::size_t I1, typename ... Ts>
    using proTypeA = typename pta_helper1<
       I0, I1, std::make_index_sequence<sizeof...(Ts)>, Ts...>::type;
    
    
    template <typename ... Ts>
    struct TypeB : public proTypeA<0u, sizeof...(Ts)/2u, Ts...>,
                   public proTypeA<sizeof...(Ts)/2u, sizeof...(Ts), Ts...>
     { };
    
    int main()
     {
       using tb  = TypeB<char, short, int, long, long long>;
       using ta1 = TypeA<char, short>;
       using ta2 = TypeA<int, long, long long>;
    
       static_assert(std::is_base_of<ta1, tb>::value, "!");
       static_assert(std::is_base_of<ta2, tb>::value, "!");
     }