4

This is a very tough one (for me at least). I'll start off by discussing an easier task that I've already solved. ExpandPacks<Packs...>::type is a pack of all packs obtained from taking one type from each pack in Packs.... For example

ExpandPacks<P<int, char>, P<bool, double, long>>::type

is

P< P<int, bool>, P<int, double>, P<int, long>, P<char, bool>, P<char, double>, P<char, long> >

I've already written the code to do this for any number of packs:

#include <iostream>
#include <type_traits>

template <typename T, typename Pack> struct Prepend;
template <typename...> struct Merge;

template <typename T, template <typename...> class P, typename... Ts>
struct Prepend<T, P<Ts...>> {
    using type = P<T, Ts...>;
};

template <typename Pack>
struct Merge<Pack> {
    using type = Pack;
};

template <template <typename...> class P, typename... Ts, typename... Us>
struct Merge<P<Ts...>, P<Us...>> {
    using type = P<Ts..., Us...>;
};

template <typename First, typename... Rest>
struct Merge<First, Rest...> : Merge<First, typename Merge<Rest...>::type> {};

template <typename... Packs> struct ExpandPacks;
template <typename T, typename Pack> struct ExpandPacksHelper;
template <typename T, typename PackOfPacks> struct ExpandPacksHelper2;
template <typename Pack, typename PackOfPacks> struct ExpandPacksHelper3;

template <template <typename...> class P, typename T, typename... Ts>
struct ExpandPacksHelper<T, P<Ts...>> {
    using type = P<P<T, Ts>...>;
};

template <template <typename...> class P, typename T, typename... Packs>
struct ExpandPacksHelper2<T, P<Packs...>> {
    using type = P<typename Prepend<T, Packs>::type...>;
};

template <template <typename...> class P, typename... Ts, typename... Packs>
struct ExpandPacksHelper3<P<Ts...>, P<Packs...>> : Merge<typename ExpandPacksHelper2<Ts, P<Packs...>>::type...> {};

template <template <typename...> class P, typename... Ts, typename Pack>
struct ExpandPacks<P<Ts...>, Pack> : Merge<typename ExpandPacksHelper<Ts, Pack>::type...> {};

template <typename First, typename... Rest>
struct ExpandPacks<First, Rest...> : ExpandPacksHelper3<First, typename ExpandPacks<Rest...>::type> {};

// Testing
template <typename...> struct P;

int main() {
    std::cout << std::boolalpha << std::is_same<
        typename ExpandPacks<P<int, char>, P<bool, double, long>>::type,
        P< P<int, bool>, P<int, double>, P<int, long>, P<char, bool>, P<char, double>, P<char, long> >
    >::value << '\n';  // true

    std::cout << std::is_same<
        typename ExpandPacksHelper3<P<short, float>, P< P<int, bool>, P<int, double>, P<int, long>, P<char, bool>, P<char, double>, P<char, long> >>::type,
        P< P<short, int, bool>, P<short, int, double>, P<short, int, long>, P<short, char, bool>, P<short, char, double>, P<short, char, long>, P<float, int, bool>, P<float, int, double>, P<float, int, long>, P<float, char, bool>, P<float, char, double>, P<float, char, long> >
    >::value << '\n';  // true

    std::cout << std::is_same<
        typename ExpandPacks<P<short, float>, P<int, char>, P<bool, double, long>>::type,
        P< P<short, int, bool>, P<short, int, double>, P<short, int, long>, P<short, char, bool>, P<short, char, double>, P<short, char, long>, P<float, int, bool>, P<float, int, double>, P<float, int, long>, P<float, char, bool>, P<float, char, double>, P<float, char, long> >
    >::value << '\n';  // true

    std::cout << std::is_same<
        typename ExpandPacks<P<int, bool>, P<short, float>, P<int, char>, P<bool, double, long>>::type,
        P< P<int, short, int, bool>, P<int, short, int, double>, P<int, short, int, long>, P<int, short, char, bool>, P<int, short, char, double>, P<int, short, char, long>, P<int, float, int, bool>, P<int, float, int, double>, P<int, float, int, long>, P<int, float, char, bool>, P<int, float, char, double>, P<int, float, char, long>,
            P<bool, short, int, bool>, P<bool, short, int, double>, P<bool, short, int, long>, P<bool, short, char, bool>, P<bool, short, char, double>, P<bool, short, char, long>, P<bool, float, int, bool>, P<bool, float, int, double>, P<bool, float, int, long>, P<bool, float, char, bool>, P<bool, float, char, double>, P<bool, float, char, long> >
    >::value << '\n';  // true
}

But now I want to generalize this. Instead of always choosing just one type from each pack, one must choose N types from each pack, where N is a template parameter. If N exceeds the size of a certain pack, then simply take all the types from that pack. The order of the types from each pack shall be preserved. But I'm totally stuck here. Also, I'm not specifying any particular order of the outputted packs, which makes testing harder too. Here is an example:

ExpandPacks<2, P<int, char, short>, P<bool, double, long>>::type

is, up to order of the packs,

P< P<int, char, bool, double>, P<int, char, bool, long>, P<int, char, double, long>,
P<int, short, bool, double>, P<int, short, bool, long>, P<int, short, double, long>,
P<char, short, bool, double>, P<char, short, bool, long>, P<char, short, double, long> >

Packs of 4 obtained by taking 2 from P<int, char, short> and 2 from P<bool, double, long>. I could alternatively define

ExpandPacks<std::index_sequence<2,1>, P<int, char, short>, P<bool, double, long>>::type

to mean take 2 from P<int, char, short> and 1 from P<bool, double, long>, which should be an easy extension once the first problem is solved (or solve this one right away, which will solve both questions). Since the order of the outputted packs is not specified, I guess the easiest way to test the output is to check that

ExpandPacks<1, P<int, char>, P<bool, double, long>>::type

or alternatively,

ExpandPacks<std::index_sequence<1,1>, P<int, char>, P<bool, double, long>>::type

is

P< P<int, bool>, P<int, double>, P<int, long>, P<char, bool>, P<char, double>, P<char, long> >

since that must reduce to the version I've already solved. I'll be willing to offer a bounty if no one can solve this so quickly. Thanks.

Update: For testing purposes, I've just written a program to check if two packs of types are equal to each other up to permutation of the types: http://ideone.com/zb7NA7 This may be of help in the tests since the output of the packs is not specified to be in any specific order here.

prestokeys
  • 4,817
  • 3
  • 20
  • 43

2 Answers2

1

The below works for two packs (can be easily extended):

#include <type_traits>

template <typename...> struct pack {using type=pack;};
template <typename, typename> struct cat;
template <typename... l, typename... r>
struct cat<pack<l...>, pack<r...>> : pack<l..., r...> {};

template <typename T>
using eval = typename T::type;

//! N choose K

namespace detail{
    template <typename, typename, std::size_t, typename...> struct n_choose_k;

    template <typename head, typename... tail, std::size_t K,  typename... prev>
    struct n_choose_k<std::enable_if_t<(sizeof...(tail)+1 >= K && K > 0)>,
                      pack<head, tail...>, K, prev...> :
      cat<typename n_choose_k<void, pack<tail...>, K-1, prev..., head>::type,
          typename n_choose_k<void, pack<tail...>, K, prev...>::type> {};

    template <typename... tail, typename... prev>
    struct n_choose_k<void, pack<tail...>, 0, prev...> :
        pack<pack<prev...>> {};

    template <typename... tail, std::size_t K, typename... prev>
    struct n_choose_k<std::enable_if_t<(K > sizeof...(tail))>,
                      pack<tail...>, K, prev...> : pack<> {};
}
template <typename p, std::size_t k>
using n_choose_k = eval<detail::n_choose_k<void, p, k>>;

//! Interleave

template <typename P, typename... Packs>
using cat_all = pack<eval<cat<P, Packs>>...>;

template <typename, typename> struct cross_interleave;
template <typename l, typename... ls, typename... r>
struct cross_interleave<pack<l, ls...>, pack<r...>> :
    eval<cat<typename cat_all<l, r...>::type,
                eval<cross_interleave<pack<ls...>, pack<r...>>>>> {};
template <typename... r>
struct cross_interleave<pack<>, pack<r...>> : pack<> {};

template <typename A, std::size_t Na, typename B, std::size_t Nb>
using interleave_multi = eval<cross_interleave<n_choose_k<A, Na>, n_choose_k<B, Nb>>>;

Example usage with interleave_multi<pack<int, char>, 1, pack<bool, double, long>, 2>:

static_assert(
  std::is_same<interleave_multi<pack<int, char>, 1, pack<bool, double, long>, 2>,
               pack<pack<int, bool, double>, pack<int, bool, long int>,
                    pack<int, double, long int>, pack<char, bool, double>,
                    pack<char, bool, long int>, pack<char, double, long int>>>{}, "" );

Demo.

Columbo
  • 60,038
  • 8
  • 155
  • 203
0

Ok, I think I've obtained the general solution I sought:

template <typename...> struct Merge;

template <typename Pack>
struct Merge<Pack> { using type = Pack; };

template <template <typename...> class P, typename... Ts, typename... Us>
struct Merge<P<Ts...>, P<Us...>> { using type = P<Ts..., Us...>; };

template <typename First, typename... Rest>
struct Merge<First, Rest...> : Merge<First, typename Merge<Rest...>::type> {};

// The all-important ExpandPacks class.
template <typename... PackOfPacks> struct ExpandPacks;
template <typename T, typename PackOfPacks> struct ExpandPacksHelper;
template <typename T, typename PackOfPacks> struct ExpandPacksHelper2;

template <template <typename...> class P, typename Pack, typename... Packs>
struct ExpandPacksHelper<Pack, P<Packs...>> {
    using type = P<typename Merge<Pack, Packs>::type...>;
};

template <template <typename...> class P, typename... Packs, typename PackOfPacks>
struct ExpandPacksHelper2<P<Packs...>, PackOfPacks> : Merge<typename ExpandPacksHelper<Packs, PackOfPacks>::type...> {};

template <template <typename...> class P, typename... Packs, typename PackOfPacks>
struct ExpandPacks<P<Packs...>, PackOfPacks> : Merge<typename ExpandPacksHelper<Packs, PackOfPacks>::type...> {};

template <typename First, typename... Rest>
struct ExpandPacks<First, Rest...> : ExpandPacksHelper2<First, typename ExpandPacks<Rest...>::type> {};

// The Expand and ExpandGeneral classes themselves:
template <template <std::size_t, typename> class SubpackType, std::size_t N, typename... Packs>
struct Expand : ExpandPacks<typename SubpackType<N, Packs>::type...> {};  // Expanding using special subpacks of size N within each pack.

template <template <std::size_t, typename> class SubpackType, typename Indices, typename... Packs> struct ExpandGeneral;

template <template <std::size_t, typename> class SubpackType, std::size_t... Is, typename... Packs>
struct ExpandGeneral<SubpackType, std::index_sequence<Is...>, Packs...> : ExpandPacks<typename SubpackType<Is, Packs>::type...> {};  // Just as Expand, but using different N values for each pack.

Passing SubpackType into Expand (or ExpandGeneral) will specify what type of subpacks are to be used in the expansion, for example Columbo's n_choose_k. But here I've tested it with AllAdjacentSubpacks<N, Pack>, which uses all subpacks consisting of N adjacent types in Pack (a special case of Columbo's n_choose_k). For example, we have the test output:

std::cout << std::is_same<
    typename ExpandGeneral<AllAdjacentSubpacks, std::index_sequence<2,1,2>, P<short, float, int>, P<int, char>, P<bool, double, long>>::type,
    P< P<short, float, int, bool, double>, P<short, float, int, double, long>, P<short, float, char, bool, double>, P<short, float, char, double, long>, P<float, int, int, bool, double>, P<float, int, int, double, long>, P<float, int, char, bool, double>, P<float, int, char, double, long> >
>::value << '\n';  // true

The very first example I gave at the beginning of the question is now simply the special case

Expand<AllAdjacentSubpacks, 1, P<int, char>, P<bool, double, long>>::type

which gives the exact same output. In case you're curious, here is the implementation of AllAdjacentSubpacks<N, Pack> used in the above test:

// AllAdjacentSubpacks<N, Pack>::type is the pack of all subpacks consisting of N adjacent types in Pack.
template <std::size_t N, typename Pack> struct AllAdjacentSubpacks;
template <std::size_t N, std::size_t Count, typename Pack, typename Output> struct AllAdjacentSubpacksBuilder;
template <std::size_t N, typename Pack> struct Head;
template <std::size_t N, typename Pack, typename Output> struct HeadBuilder;

template <std::size_t N, template <typename...> class P, typename First, typename... Rest, typename... Ts>
struct HeadBuilder<N, P<First, Rest...>, P<Ts...>> : HeadBuilder<N-1, P<Rest...>, P<Ts..., First>> {};

template <template <typename...> class P, typename First, typename... Rest, typename... Ts>
struct HeadBuilder<0, P<First, Rest...>, P<Ts...>> { using type = P<Ts...>; };

template <template <typename...> class P, typename... Ts>
struct HeadBuilder<0, P<>, P<Ts...>> { using type = P<Ts...>; };

template <std::size_t N, template <typename...> class P, typename... Ts>
struct Head<N, P<Ts...>> : HeadBuilder<N, P<Ts...>, P<>> {};

template <std::size_t N, std::size_t Count, template <typename...> class P, typename First, typename... Rest, typename... Packs>
struct AllAdjacentSubpacksBuilder<N, Count, P<First, Rest...>, P<Packs...>> : AllAdjacentSubpacksBuilder<N, Count-1, P<Rest...>, P<Packs..., typename Head<N, P<First, Rest...>>::type>> {};

template <std::size_t N, template <typename...> class P, typename First, typename... Rest, typename... Packs>
struct AllAdjacentSubpacksBuilder<N, 0, P<First, Rest...>, P<Packs...>> {
    using type = P<Packs...>;
};

template <std::size_t N, template <typename...> class P, typename... Packs>
struct AllAdjacentSubpacksBuilder<N, 0, P<>, P<Packs...>> {
    using type = P<Packs...>;
};

template <std::size_t N, template <typename...> class P, typename... Ts>
struct AllAdjacentSubpacks<N, P<Ts...>> : AllAdjacentSubpacksBuilder<N, sizeof...(Ts) - N + 1, P<Ts...>, P<>> {};
prestokeys
  • 4,817
  • 3
  • 20
  • 43