Split paramter pack
Asked Answered
O

2

6

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.

Onrush answered 27/4, 2019 at 19:35 Comment(2)
Possible duplicate of Splitting argpack in half?Agar
I really like max66' answer because the usage is wery clean and easy to read. I expect I will be able to pass TypeA as another template parameter.Onrush
C
1

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, "!");
 }
Calcutta answered 27/4, 2019 at 20:37 Comment(0)
E
1

With std::tuple (C++11) and std::index_sequence (C++14, but there are backports), the standard library contains everything to efficiently and somewhat conveniently split the pack.

template <class, class, class>
struct TypeBImpl;
template <std::size_t... N, std::size_t... M, class T>
struct TypeBImpl<std::index_sequence<N...>, std::index_sequence<M...>, T>
: TypeA<typename std::tuple_element_t<T, N>::type...>
, TypeA<typename std::tuple_element_t<T, M + sizeof...(N)>::type...>
{};

template <class... Ts>
struct TypeB
: TypeBImpl<
    std::make_index_sequence<sizeof...(Ts) / 2>,
    std::make_index_sequence<(sizeof...(Ts) + 1) / 2>,
    std::tuple<std::enable_if<1, Ts>...>
>
{};

I used an intermediate base for convenience, which has the added advantage of keeping the helper-types for re-use in members.


If you don't need it, or its existence in RTTI is inconvenient, there are other solutions:

template <class T, std::size_t N, std::size_t... M>
auto TypeA_part_impl(std::index_sequence<M...>)
-> TypeA<typename std::tuple_element_t<T, N + M>::type...>;

template <bool tail, class... Ts>
using TypeA_part = decltype(TypeA_part_impl<
    std::tuple<std::enable_if<1, Ts>...>,
    tail * sizeof...(Ts) / 2>(
    std::make_index_sequence<(sizeof...(Ts) + tail) / 2>()));

template <class... Ts>
struct TypeB : TypeA_part<0, Ts...>, TypeA_part<1, Ts...>
{
};

I'm using std::enable_if<1, T> as a convenient vehicle to transfer arbitrary type-information, even if the type is so irregular it cannot be stored in a std::tuple, or is incomplete. No need to define my own.

Ectype answered 27/4, 2019 at 21:24 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.