'N-ary cartesian product of variadic templates [duplicate]

I am trying to compute the n-ary cartesian product of variadic templates and so far I haven't found an elegant method for the n-ary case (unary and binary are easy, see below).


Here is the current status of the investigations. For convenience, I introduce a pack<Types...> to store variadic templates. And then some utility functions and helpers:

// Preamble
#include <iostream>
#include <type_traits> 

// Helper function to display the result
template <class...> void print() {std::cout << __PRETTY_FUNCTION__ << "\n";}

// Pack to hold variadic templates
template <class...> struct pack {};

// Sum of packs
template <class...> 
struct pack_sum;
// Sum of packs: single pack specialization
template <class... Types> 
struct pack_sum<pack<Types...>> {
    using type = pack<Types...>;
};
// Sum of packs: n-ary recursive specialization
template <class... Types1, class... Types2, class... Packs>
struct pack_sum<pack<Types1...>, pack<Types2...>, Packs...> {
    using type = typename pack_sum<pack<Types1..., Types2...>, Packs...>::type;
};

Based on that, I can then introduction my cartesian-product (unary and binary versions):

// Cartesian product of packs
template <class...>
struct pack_product;
// Cartesian product of packs: single empty pack specialization
template <>
struct pack_product<pack<>> {
    using type = pack<>;
};
// Cartesian product of packs: empty pack specialization
template <class... Types2, class... Packs>
struct pack_product<pack<>, pack<Types2...>, Packs...> {
    using type = typename pack_product<pack<>, Packs...>::type;
};
// Cartesian product of packs: unary specialization
template <class... Types>
struct pack_product<pack<Types...>> {
    using type = pack<pack<Types>...>;
};
// Cartesian product of packs: binary specialization
template <class Type1, class... Types1, class... Types2>
struct pack_product<pack<Type1, Types1...>, pack<Types2...>> {
    using type = typename pack_sum<
        pack<pack<Type1, Types2>...>,
        typename pack_product<pack<Types1...>, pack<Types2...>>::type
    >::type;
};

Which I can test with:

// Test
int main(int argc, char* argv[]) {
    // Packs
    using empty_pack = pack<>;
    using char_pack = pack<signed char, char, unsigned char>;
    using int_pack = pack<int, unsigned int>;
    using float_pack = pack<float, double, long double>;
    // Tests
    print<typename pack_product<empty_pack>::type>();
    print<typename pack_product<char_pack>::type>();
    print<typename pack_product<empty_pack, char_pack>::type>();
    print<typename pack_product<char_pack, empty_pack>::type>();
    print<typename pack_product<char_pack, float_pack>::type>();
}

The whole functional code is here: https://godbolt.org/z/Pv15W95EK

The output of:

typename pack_product<
    pack<signed char, char, unsigned char>, 
    pack<float, double, long double>
>::type

is:

pack<
    pack<signed char, float>, 
    pack<signed char, double>, 
    pack<signed char, long double>, 
    pack<char, float>, 
    pack<char, double>, 
    pack<char, long double>, 
    pack<unsigned char, float>, 
    pack<unsigned char, double>, 
    pack<unsigned char, long double> 
>

as expected.


Now, the PROBLEM:

I am looking for a n-ary version, but that avoids nested packs so that:

pack_product<pack<A1, A2>, pack<B1, B2>, pack<C1, C2>>

gives the following results:

pack<
    pack<A1, B1, C1>, pack<A1, B1, C2>, pack<A1, B2, C1>, pack<A1, B2, C2>,
    pack<A2, B1, C1>, pack<A2, B1, C2>, pack<A2, B2, C1>, pack<A2, B2, C2>
>

and NOT some kind of:

pack<
    pack<A1, pack<B1, C1>>, pack<A1, pack<B1, C2>>, pack<A1, pack<B2, C1>>, pack<A1, pack<B2, C2>>,
    pack<A2, pack<B1, C1>>, pack<A2, pack<B1, C2>>, pack<A2, pack<B2, C1>>, pack<A2, pack<B2, C2>>,
>

QUESTION: While searching, I found extremely ugly versions that seemed to work, but I would like something as clean and elegant as possible that minimizes the number of helper structs and specializations. If the n-ary specialization covers the binary version that's a plus, but I'm not sure that is possible. Any help would be extremely welcomed :)

NOTE: A possible specialization for the thing I'm looking for would be, but that is just a suggestion:

template <class Type1, class... Types1, class... Types2, class... Pack>
struct pack_product<pack<Type1, Types1...>, pack<Types2...>, Pack...> {
     using type = /* THE THING I'M LOOKING FOR */
};


Solution 1:[1]

NOTE: A possible specialization for the thing I'm looking for would be, but that is just a suggestion:

For the n-ary version, you only need to recurse the binary version since pack_product<a, b, c> is equivalent to pack_product<pack_product<a, b>, c>

// Cartesian product of packs: n-ary specialization
template <class Type1, class... Types1, class... Types2, class... Pack>
struct pack_product<pack<Type1, Types1...>, pack<Types2...>, Pack...> {
  using type = typename pack_product<typename pack_sum<
    pack<pack<Type1, Types2>...>,
    typename pack_product<pack<Types1...>, pack<Types2...>>::type
  >::type, Pack...>::type;
};

However, this will generate

{pack<pack<pack<signed char, float>, int>, pack<pack<signed char, float>, unsigned int>, ...}

In order to remove nested packs, partial specialization can be performed on binary/n-arg specialization

// Cartesian product of packs: binary specialization for nested packs
template <class... Types, class... Types1, class... Types2>
struct pack_product<pack<pack<Types...>, Types1...>, pack<Types2...>> {
  using type = typename pack_sum<
    pack<pack<Types..., Types2>...>,
    typename pack_product<pack<Types1...>, pack<Types2...>>::type
  >::type;
};

Demo

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1