5

Is it possible, with C++11/14/1z, to have a templated construct with two parameter packs, and within it, instantiate some other templated construct with the two packs inteleaved? i.e. if the first pack is T1_1, T1_2, T1_3 and the second pack is T2_1, T2_2 and T2_3, the interleaved pack will be T1_1, T2_1, T1_2, T2_2, T1_3, T2_3? My guess would be "no", since an ellipsis should not be able to apply to two parameter pack element designators; but perhaps some kind of tuple-construction or recursive-instantiation trick could work?

Edit: Let me be explicit in assuming the packs have equal lengths (sizeof... values). A solution for packs of different lengths would be nice provided it doesn't make things much more convoluted.

einpoklum
  • 118,144
  • 57
  • 340
  • 684

6 Answers6

4

Using tuple_cat is overkill. Not to mention unnecessarily constraining.

Trivial pack class:

template<class...> struct pack {};

A variadic concat, fairly trivial to write:

template<class T = pack<>, class...> 
struct concat { using type = T; };

template<class... T1, class... T2, class... Ts>
struct concat<pack<T1...>, pack<T2...>, Ts...> 
    : concat<pack<T1..., T2...>, Ts...> {};

template<class... Ts> 
using concat_t = typename concat<Ts...>::type;

Then interleave itself is equally trivial - expand into pack of two types, then concatenate them:

template<class... Us>
struct interleave {
     template<class... Vs>
     using with = concat_t<pack<Us, Vs>...>;
};

Demo.

T.C.
  • 133,968
  • 17
  • 288
  • 421
  • this only handles packs of equal length (any solution relying on paired pack expansion would have this limitation due to 'mismatched argument pack lengths while expanding'). I'd expect a solution that appends the types of the longest pack at the end and it seems it would take compile time integer sequences to achieve that. (+1 for the use of `t1` in the Demo) – Nikos Athanasiou Jun 23 '16 at 10:09
  • I was suspicious of this code... it doesn't look it it should compile, especially the `(Us, Vs)...` line. But it does, whaddaya know... – einpoklum Jun 23 '16 at 13:06
  • @NikosAthanasiou You just need a `split>` if you want to support that. That in turn can be implemented either with integer sequences or simple recursion – T.C. Jun 23 '16 at 17:41
3

All the individual pieces for this have actually already been added to the standard. I can't test this myself right now but the idea should work.

template <class Tuple1, class Tuple2, std::size_t ... indices>
auto interleave(Tuple1 t1, Tuple2 t2, std::integer_sequence<std::size_t, indices...>)
{
    return std::tuple_cat(std::make_tuple(std::get<indices>(t1),
                                          std::get<indices>(t2))...);
}


template <class Tuple1, class Tuple2>
auto interleave(Tuple1 t1, Tuple2 t2)
{
    return interleave(t1, t2, std::make_index_sequence<std::tuple_size<Tuple1>::value>());
}
SirGuy
  • 10,660
  • 2
  • 36
  • 66
2

With a function and a decltypes, you can easily do that.
Here is a working example:

#include<tuple>

template<std::size_t... I>
constexpr auto f(std::index_sequence<I...>, auto tup1, auto tup2) {
    return std::tuple_cat(std::make_tuple(std::get<I>(tup1), std::get<I>(tup2))...);
}

template<typename... V>
struct S {
    template<typename... U, std::enable_if_t<(sizeof...(V) == sizeof...(U))>* = nullptr>
    static auto generate() {
        return f(std::make_index_sequence<sizeof...(U)>(), std::tuple<V...>{}, std::tuple<U...>{});
    }
};

int main() {
    static_assert(
        std::is_same<
            decltype(S<int, double>::template generate<char, void*>()),
            std::tuple<int, char, double, void*>
        >::value,
        "!"
    );
}
skypjack
  • 49,335
  • 19
  • 95
  • 187
  • I'd mention `std::integer_sequence` is C++14. – einpoklum Jun 23 '16 at 05:36
  • @einpoklum Yes, it is, but you can find a C++11-implementation of it on the web, thus the solution still applies. Let me search it and integrate the answer. Can [this](http://stackoverflow.com/questions/17424477/implementation-c14-make-integer-sequence) help? – skypjack Jun 23 '16 at 05:43
  • @T.C. Right, it's known that way indeed. :-) – skypjack Jul 28 '16 at 22:15
0

Sure it's possible. You can get two different packs via a wrapper:

template < typename L0, typename L1 >
struct interleave;

template < typename ... Pack >
struct pack {};

template < typename ... P0, typename ... P1 >
struct interleave<pack<P0...>, pack<P1...>>
{
   using type = ???;
};
Edward Strange
  • 40,307
  • 7
  • 73
  • 125
0
#include <tuple>
#include <iostream>
#include <utility>
#include <typeinfo>

template<class T1, class T2>
struct interleave
{
  static constexpr std::size_t size = std::tuple_size<T1>::value;
  static_assert(size == std::tuple_size<T2>::value, ""); 

  template<class T> struct impl;

  template<std::size_t...Is>
  struct impl<std::index_sequence<Is...>>
  {
    using type = std::tuple
      <
      std::tuple<std::tuple_element_t<Is, T1>,
      std::tuple_element_t<Is, T2>>...
      >;
  };

  template<class T> struct dedup;
  template<class...Ts>
    struct dedup<std::tuple<Ts...>>
    {
      using type = decltype(std::tuple_cat(std::declval<Ts>()...)); 
    };

  using dups = typename impl<decltype(std::make_index_sequence<size>())>::type;

  using type = typename dedup<dups>::type;
};


int main()
{
  using t = interleave<std::tuple<int, char, float>, std::tuple<unsigned, double, const char*>>::type;
  std::cout << typeid(t).name() << std::endl;
}
Richard Hodges
  • 68,278
  • 7
  • 90
  • 142
0

Aim:

Get a type alias to:

Foo<int, double, char, const char*>

using 2 parameter packs:

using Pack1 = Pack<int, char>;
using Pack2 = Pack<double, const char*>;

and then interleaving them:

typename ToFoo<Pack1, Pack2>::type

and performing a static_assert that they are equivalent:

using T1 = Foo<int, double, char, const char*>;
using T2 = typename ToFoo<Pack<int, char>, Pack<double, const char*>>::type;

static_assert(std::is_same<T1, T2>::value, "passed");

Solution:

We can interleave 2 tuples into 1 as follows:

template <class Tuple1, class Tuple2, std::size_t ... indices>
auto interleave(Tuple1 t1, Tuple2 t2, std::integer_sequence<std::size_t, indices...>)
{
    return std::tuple_cat(std::make_tuple(std::get<indices>(t1),
                                          std::get<indices>(t2))...);
}

template <class Tuple1, class Tuple2>
auto interleave(Tuple1 t1, Tuple2 t2)
{
    return interleave(t1, t2, std::make_index_sequence<std::tuple_size<Tuple1>::value>());
}

We can get the type of the resulting interleaved tuple as follows:

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

template<typename T0, typename T1>
struct Interleaved;

template<typename... P0, typename... P1>
struct Interleaved<Pack<P0...>, Pack<P1...>>
{
    using Pack0 = typename Pack<P0...>::type;
    using Pack1 = typename Pack<P1...>::type;

    using type = decltype(interleave(std::declval<Pack0>(), std::declval<Pack1>()));
};

We can then take that std::tuple<Ts...> type and "convert" it to Foo<Ts...> as follows:

template<typename T>
struct TupleToFoo;

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

Finally we wrap it all up an a helper class ToFoo which takes 2 Packs and defines a type alias:

template<typename T0, typename T1>
struct ToFoo;

template<typename... P0, typename... P1>
struct ToFoo<Pack<P0...>, Pack<P1...>>
{
    using type = typename TupleToFoo<typename Interleaved<Pack<int, char>, Pack<double, const char*>>::type>::type;
};

Full working example: (coliru)

#include <tuple>

template <class Tuple1, class Tuple2, std::size_t ... indices>
auto interleave(Tuple1 t1, Tuple2 t2, std::integer_sequence<std::size_t, indices...>)
{
    return std::tuple_cat(std::make_tuple(std::get<indices>(t1),
                                          std::get<indices>(t2))...);
}

template <class Tuple1, class Tuple2>
auto interleave(Tuple1 t1, Tuple2 t2)
{
    return interleave(t1, t2, std::make_index_sequence<std::tuple_size<Tuple1>::value>());
}

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

template<typename T0, typename T1>
struct Interleaved;

template<typename... P0, typename... P1>
struct Interleaved<Pack<P0...>, Pack<P1...>>
{
    using Pack0 = typename Pack<P0...>::type;
    using Pack1 = typename Pack<P1...>::type;

    using type = decltype(interleave(std::declval<Pack0>(), std::declval<Pack1>()));
};

template<typename... Ts>
struct Foo 
{};

template<typename T>
struct TupleToFoo;

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

template<typename T0, typename T1>
struct ToFoo;

template<typename... P0, typename... P1>
struct ToFoo<Pack<P0...>, Pack<P1...>>
{
    using type = typename TupleToFoo<typename Interleaved<Pack<int, char>, Pack<double, const char*>>::type>::type;
};


int main()
{
    using T1 = Foo<int, double, char, const char*>;
    using T2 = typename ToFoo<Pack<int, char>, Pack<double, const char*>>::type;

    static_assert(std::is_same<T1, T2>::value, "passed");
    return 0;
}
Steve Lorimer
  • 27,059
  • 17
  • 118
  • 213