I\'d like to create the cross product of a list of types using variadic templates.
Here\'s what I have so far:
#include
#include <
Really enjoyed this "homework" assignment :)
Both solutions below create a class full of type_list typedefs, along with member functions that will check to see if a given list of types exist in the class as a type_list.
The first solution creates all possible combinations of types from 1 to N types per type_list (the width
parameter defines N). The second solution creates only pairs of types.
First Solution
template<typename... Ts> struct type_list { typedef type_list<Ts...> type; };
template<size_t, typename...> struct xprod_tlist_ {};
template<typename... Ts, typename... Us>
struct xprod_tlist_<1, type_list<Ts...>, Us...> {};
template<size_t width, typename... Ts, typename... Us>
struct xprod_tlist_<width, type_list<Ts...>, Us...>
: type_list<Ts..., Us>...
, xprod_tlist_<width - 1, type_list<Ts..., Us>, Us...>... {};
template<size_t width, typename... Ts> struct xprod_tlist
: type_list<Ts>..., xprod_tlist_<width, type_list<Ts>, Ts...>... {
template<typename... Us> struct exists
: std::is_base_of<type_list<Us...>, xprod_tlist<width, Ts...>> {};
template<typename... Us> struct assert_exists {
static_assert(exists<Us...>::value, "Type not present in list");
};
};
Usage:
typedef xprod_tlist<5, int, char, string, float, double, long> X;
//these pass
X::assert_exists<int, int, int, int, int> assert_test1;
X::assert_exists<double, float, char, int, string> assert_test2;
//these fail
X::assert_exists<char, char, char, char, char, char> assert_test3;
X::assert_exists<int, bool> assert_test4;
//true
auto test1 = X::exists<int, int, int, int, int>::value;
auto test2 = X::exists<double, float, char, int, string>::value;
//false
auto test3 = X::exists<char, char, char, char, char, char>::value;
auto test4 = X::exists<int, bool>::value;
Second Solution
template<class T, class U> struct type_pair { typedef type_pair<T, U> type; };
template<class... Ts> struct type_list {};
template<class...> struct xprod_tlist_ {};
template<class T, class... Ts, class... Us>
struct xprod_tlist_<type_list<T, Ts...>, type_list<Us...>>
: type_pair<T, Us>..., xprod_tlist_<type_list<Ts...>, type_list<Us...>> {};
template<class... Ts>
struct xprod_tlist : xprod_tlist_<type_list<Ts...>, type_list<Ts...>> {
template<class T, class U> struct exists
: std::is_base_of<type_pair<T, U>, xprod_tlist<Ts...>> {};
template<class T, class U> struct assert_exists {
static_assert(exists<T, U>::value, "Type not present in list");
};
};
Usage:
typedef xprod_tlist<int, float, string> X;
//these pass
X::assert_exists<int, int> assert_test1;
X::assert_exists<int, float> assert_test2;
X::assert_exists<int, string> assert_test3;
X::assert_exists<float, int> assert_test4;
X::assert_exists<float, float> assert_test5;
X::assert_exists<float, string> assert_test6;
X::assert_exists<string, int> assert_test7;
X::assert_exists<string, float> assert_test8;
X::assert_exists<string, string> assert_test9;
//this fails
X::assert_exists<int, char> assert_test10;
//true
auto test1 = X::exists<int, int>::value;
auto test2 = X::exists<int, float>::value;
auto test3 = X::exists<int, string>::value;
auto test4 = X::exists<float, int>::value;
auto test5 = X::exists<float, float>::value;
auto test6 = X::exists<float, string>::value;
auto test7 = X::exists<string, int>::value;
auto test8 = X::exists<string, float>::value;
auto test9 = X::exists<string, string>::value;
//false
auto test10 = X::exists<int, char>::value;
Logic to concatenate type_lists
to avoid nested type_list
like you are asking for:
// base case: 2 type_lists
template<class... Ts, class... Us>
auto concat(type_list<Ts...>, type_list<Us...>) -> type_list<Ts..., Us...>;
// recursive case: more than 2 type_lists
template<class... Ts, class... Us, class... Rest>
auto concat(type_list<Ts...>, type_list<Us...>, Rest...) -> decltype(concat(type_list<Ts..., Us...>{}, Rest{}...));
Note that these functions don't have (or need) implementations; this is a trick to avoid class template specialization (I learned it from Hana Dusikova's compile time regular expressions)
Then, simplifying your row
and cross_product
impls as pairs
and cross_product_impl
, respectively:
template<class T, class... Ts>
using pairs = type_list<type_pair<T, Ts>...>;
template<class... T>
auto cross_product_impl()
{
if constexpr(sizeof...(T) == 0)
return type_list<> {};
if constexpr(sizeof...(T) == 1)
return type_list<type_pair<T, T>...>{};
if constexpr(sizeof...(T) > 1)
return concat(pairs<T, T...>{}...);
}
if constexpr
allows us to more easily express the logic, I think.
Finally a type alias for cross_product
that gives us what the type would be if we theoretically invoked cross_product_impl
:
template<class... T>
using cross_product = decltype(cross_product_impl<T...>());
Usage basically the same as before:
cross_product<int, float, short> result;
With Boost.Mp11, this is a short one-liner (as always):
using input = type_list<int, float, short>;
using result = mp_product<
type_pair,
input, input>;
Demo.
We can generalize this to picking N
things, with repetition, from that input. We can't use type_pair
anymore to group our elements, so we'll just have a list of type_list
of elements. To do that, we basically need to write:
mp_product<type_list, input, input, ..., input>
// ~~~~~~~ N times ~~~~~~~~
Which is also the same as:
mp_product_q<mp_quote<type_list>, input, input, ..., input>
// ~~~~~~~ N times ~~~~~~~~
One way to do that is:
template <int N>
using product = mp_apply<
mp_product_q,
mp_append<
mp_list<mp_quote<type_list>>,
mp_repeat_c<mp_list<input>, N>
>>;
Demo.
So far all solutions have drawbacks, unnecessary dependencies, unnecessary helpers and all are restricted to the Cartesian power of two. The following solution has no such drawbacks and supports:
It was actually to harder to implement (but good as homework) then I thought. I am actually thinking about creating a little generator which allows me an extended template syntax which makes these things really easy.
Simplified the code works as follows: product
converts an input list tuple<A...>,tuple<B...>,tuple<C...>
into tuple<tuple<A>...>, tuple<B...>, tuple<C...>
. This second list is then passed to product_helper
which does the recursive Cartesian product computation.
template <typename... T> struct cat2;
template <template<typename...> class R, typename... As, typename... Bs>
struct cat2 <R<As...>, R<Bs...> > {
using type = R <As..., Bs...>;
};
template <typename... Ts> struct product_helper;
template <template<typename...> class R, typename... Ts>
struct product_helper < R<Ts...> > { // stop condition
using type = R< Ts...>;
};
template <template<typename...> class R, typename... Ts>
struct product_helper < R<R<> >, Ts... > { // catches first empty tuple
using type = R<>;
};
template <template<typename...> class R, typename... Ts, typename... Rests>
struct product_helper < R<Ts...>, R<>, Rests... > { // catches any empty tuple except first
using type = R<>;
};
template <template<typename...> class R, typename... X, typename H, typename... Rests>
struct product_helper < R<X...>, R<H>, Rests... > {
using type1 = R <typename cat2<X,R<H> >::type...>;
using type = typename product_helper<type1, Rests...>::type;
};
template <template<typename...> class R, typename... X, template<typename...> class Head, typename T, typename... Ts, typename... Rests>
struct product_helper < R<X...>, Head<T, Ts...>, Rests... > {
using type1 = R <typename cat2<X,R<T> >::type...>;
using type2 = typename product_helper<R<X...> , R<Ts...> >::type;
using type3 = typename cat2<type1,type2>::type;
using type = typename product_helper<type3, Rests...>::type;
};
template <template<typename...> class R, typename... Ts> struct product;
template <template<typename...> class R>
struct product < R > { // no input, R specifies the return type
using type = R<>;
};
template <template<typename...> class R, template<typename...> class Head, typename... Ts, typename... Tail>
struct product <R, Head<Ts...>, Tail... > { // R is the return type, Head<A...> is the first input list
using type = typename product_helper< R<R<Ts>...>, Tail... >::type;
};
Here is a compilable example of how the code can be used.
Here's another solution.
#include <iostream>
#include <typeinfo>
#include <cxxabi.h>
template <typename ...Args> struct typelist { };
template <typename, typename> struct typepair { };
template <typename S, typename T> struct product;
template <typename S, typename T> struct append;
template<typename ...Ss, typename ...Ts>
struct append<typelist<Ss...>, typelist<Ts...>> {
typedef typelist<Ss..., Ts...> type;
};
template<>
struct product<typelist<>, typelist<>> {
typedef typelist<> type;
};
template<typename ...Ts>
struct product<typelist<>, typelist<Ts...>> {
typedef typelist<> type;
};
template<typename ...Ts>
struct product<typelist<Ts...>, typelist<>> {
typedef typelist<> type;
};
template<typename S, typename T, typename ...Ss, typename ...Ts>
struct product<typelist<S, Ss...>, typelist<T, Ts...>> {
typedef typename
append<typelist<typepair<S, T>,
typepair<S, Ts>...,
typepair<Ss, T>...>,
typename product<typelist<Ss...>, typelist<Ts...>>::type>::type type;
};
int main(void)
{
int s;
std::cout << abi::__cxa_demangle(
typeid(product<typelist<int, float>, typelist<short, double>>::type).name(), 0, 0, &s) << "\n";
return 0;
}
Somehow my brain is fried: I think I'm using more code than is needed but, at least, it has the desired results (although I didn't fix the memory leak):
#include <iostream>
#include <typeinfo>
#include <cxxabi.h>
template<typename...> struct type_list {};
template<typename T1, typename T2> struct type_pair {};
template<typename T, typename... Rest>
struct row
{
typedef type_list<type_pair<T,Rest>...> type;
};
template <typename... T> struct concat;
template <typename... S, typename... T>
struct concat<type_list<S...>, type_list<T...>>
{
typedef type_list<S..., T...> type;
};
template <typename... T>
struct expand
{
typedef type_list<T...> type;
};
template <> struct expand<> { typedef type_list<> type; };
template <typename... T, typename... L>
struct expand<type_list<T...>, L...>
{
typedef typename concat<typename expand<T...>::type, typename expand<L...>::type>::type type;
};
template<typename... T>
struct cross_product
{
typedef typename expand<type_list<typename row<T,T...>::type...>>::type type;
};
int main()
{
int s;
typedef cross_product<int, float, short>::type result;
std::cout << abi::__cxa_demangle(typeid(result).name(), 0, 0, &s) << std::endl;
return 0;
}