Self explanatory.
Basically, say I have type lists like so:
using type_list_1 = type_list;
using type_list_2 = type_list
Fold expressions to the rescue again
template
typelist...> layered(typelist);
template
auto operator+(typelist, typelist)
-> typelist;
template
auto operator*(typelist, typelist)
-> typelist;
template
auto operator^(typelist, TL tl)
-> decltype(((typelist{} * tl) + ...));
template
using product_t = decltype((layered(TLs{}) ^ ...));
And you're done. This has the additional benefit over recursion of having O(1) instantiation depth.
struct A0;
struct A1;
struct B0;
struct B1;
struct C0;
struct C1;
struct C2;
using t1 = typelist;
using t2 = typelist;
using t3 = typelist;
using p1 = product_t;
using p2 = product_t;
using expect1 = typelist,
typelist,
typelist,
typelist>;
using expect2 = typelist,
typelist,
typelist,
typelist,
typelist,
typelist,
typelist,
typelist,
typelist,
typelist,
typelist,
typelist>;
static_assert(std::is_same_v);
static_assert(std::is_same_v);