Cartesian product of list of templates and list of types to instantiate them with

Viewed 106

Not sure if title is descriptive, but what I would like is this:

input:

  1. list of templates(in my case containers) taking 1 (required, can be more optional) type arguments
  2. list of types

output:

"cartesian product" where each template in first set is instantiated with every type in second set.

example:

template_list<std::vector, std::set> x type_list<int, double> => 
type_list<std::vector<int>, std::vector<double>, std::set<int>, std::set<double>>

I found this question, but that is about a case when elements of both sets are types.

How to create the Cartesian product of a type list?

I presume that what I want is impossible without macros, but it may be possible that I am missing something.

2 Answers

You could use std::tuples. Especially std::tuple_cat is nice for this.

Example:

#include <iostream>
#include <set>
#include <tuple>
#include <type_traits>
#include <vector>

template <template <class...> class... C>
struct template_list {};

template <template <class...> class C, class... Ts>
auto toc(const std::tuple<Ts...>&) {
    return std::tuple<C<Ts>...>{};
}

template <template <class...> class... C, class... Ts>
auto operator*(template_list<C...>, const std::tuple<Ts...>& tup) {
    return std::tuple_cat(toc<C>(tup)...);
}

int main() {
    template_list<std::vector, std::set> templ;
    std::tuple<int, double, float> typel;

    auto res = templ * typel;

    static_assert(
        std::is_same_v<decltype(res),
                       std::tuple<std::vector<int>, std::vector<double>,
                                  std::vector<float>, std::set<int>,
                                  std::set<double>, std::set<float>>>);
}

Demo


A similar solution made into a more classical type trait in which res will result in having the same type as above could look like this:

template <template <class...> class... C>
struct template_list {};

template <class Temps, class Types>
struct cartesian_product {
    
    template <template <class...> class C, class... Ts>
    static std::tuple<C<Ts>...> toc(const std::tuple<Ts...>&);
    
    template <template <class...> class... C, class... Ts>
    static auto build(template_list<C...>, const std::tuple<Ts...>& tup)
        -> decltype(std::tuple_cat(std::declval<decltype(toc<C>(tup))>()...));

    using type = decltype(build(std::declval<Temps>(), std::declval<Types>()));
};

template<class Temps, class Types>
using cartesian_product_t = typename cartesian_product<Temps,Types>::type;
int main() {
    using templt = template_list<std::vector, std::set>;
    using typelt = std::tuple<int, double, float>;

    cartesian_product_t<templt, typelt> res;
}

As in my answer on he linked question, with Boost.Mp11, this is a short one-liner (as always):

using templates = mp_list<mp_quote<std::vector>, mp_quote<std::set>>;
using types = mp_list<int, double>;

using result = mp_product<
    mp_invoke_q,
    templates, types>;

static_assert(std::same_as<
    result,
    mp_list<std::vector<int>,
            std::vector<double>,
            std::set<int>,
            std::set<double>
            >>);

Demo.

Note that you need templates as mp_list<mp_quote<C1>, mp_quote<C2>> instead of template_list<C1, C2>, since metaprogramming is lot easier if everything is a type. But that's not a huge burden.

Related