如何创建类型列表的笛卡尔积?

时间:2012-02-03 00:19:39

标签: c++ c++11 variadic-templates

我想使用可变参数模板创建类型列表的叉积。

这是我到目前为止所拥有的:

#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 cross_product
{
  typedef type_list<typename row<T,T...>::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;
}

该程序输出:

$ g++ -std=c++0x cross_product.cpp ; ./a.out 
type_list<type_list<type_pair<int, int>, type_pair<int, float>, type_pair<int, short> >, type_list<type_pair<float, int>, type_pair<float, float>, type_pair<float, short> >, type_list<type_pair<short, int>, type_pair<short, float>, type_pair<short, short> > >

但我希望输出:

type_list<type_pair<int,int>, type_pair<int,float>, type_pair<int,short>, type_pair<float,int>,...>

即没有嵌套的type_list s。

如果没有row帮助程序,是否有直接的方法可以执行此操作,或者解决方案是否应该以某种方式“展开”嵌套的type_list

8 个答案:

答案 0 :(得分:9)

我认为一个不错的干净版本:

<强> cross_product.cpp:

#include "type_printer.hpp"

#include <iostream>

template<typename ...Ts> struct type_list {};
template<typename T1, typename T2> struct pair {};

// Concatenation
template <typename ... T> struct concat;
template <typename ... Ts, typename ... Us>
struct concat<type_list<Ts...>, type_list<Us...>>
{
    typedef type_list<Ts..., Us...> type;
};

// Cross Product
template <typename T, typename U> struct cross_product;

// Partially specialise the empty case for the first type_list.
template <typename ...Us>
struct cross_product<type_list<>, type_list<Us...>> {
    typedef type_list<> type;
};

// The general case for two type_lists. Process:
// 1. Expand out the head of the first type_list with the full second type_list.
// 2. Recurse the tail of the first type_list.
// 3. Concatenate the two type_lists.
template <typename T, typename ...Ts, typename ...Us>
struct cross_product<type_list<T, Ts...>, type_list<Us...>> {
    typedef typename concat<
        type_list<pair<T, Us>...>,
        typename cross_product<type_list<Ts...>, type_list<Us...>>::type
    >::type type;
};

struct A {};
struct B {};
struct C {};
struct D {};
struct E {};
struct F {};

template <typename T, typename U>
void test()
{
    std::cout << print_type<T>() << " \u2a2f " << print_type<U>() << " = "
        << print_type<typename cross_product<T, U>::type>() << std::endl;
}

int main()
{
    std::cout << "Cartesian product of type lists\n";
    test<type_list<>, type_list<>>();
    test<type_list<>, type_list<A>>();
    test<type_list<>, type_list<A, B>>();
    test<type_list<A, B>, type_list<>>();
    test<type_list<A>, type_list<B>>();
    test<type_list<A>, type_list<B, C, D>>();
    test<type_list<A, B>, type_list<B, C, D>>();
    test<type_list<A, B, C>, type_list<D>>();
    test<type_list<A, B, C>, type_list<D, E, F>>();
    return 0;
}

<强> type_printer.hpp:

#ifndef TYPE_PRINTER_HPP
#define TYPE_PRINTER_HPP

#include "detail/type_printer_detail.hpp"

template <typename T>
std::string print_type()
{
    return detail::type_printer<T>()();
}

#endif

<强>细节/ type_printer_detail.hpp:

#ifndef DETAIL__TYPE_PRINTER_DETAIL_HPP
#define DETAIL__TYPE_PRINTER_DETAIL_HPP

#include <typeinfo>
#include <cxxabi.h>
#include <string>

template <typename ...Ts> struct type_list;
template <typename T1, typename T2> struct pair;

namespace detail {

// print scalar types
template <typename T>
struct type_printer {
    std::string operator()() const {
        int s;
        return abi::__cxa_demangle(typeid(T).name(), 0, 0, &s);
    }   
};

// print pair<T, U> types
template <typename T, typename U>
struct type_printer<pair<T, U>> {
    std::string operator()() const {
        return "(" + type_printer<T>()() + "," + type_printer<U>()() + ")";
    }   
};

// print type_list<T>
template <>
struct type_printer<type_list<>> {
    std::string operator()() const {
        return "\u2205";
    }   
};

template <typename T>
struct type_printer<type_list<T>> {
    std::string operator()() const {
        return "{" + type_printer<T>()() + "}";
    }   
    std::string operator()(const std::string& sep) const {
        return sep + type_printer<T>()();
    }   
};

template <typename T, typename ...Ts>
struct type_printer<type_list<T, Ts...>> {
    std::string operator()() const {
        return "{" + type_printer<T>()() + type_printer<type_list<Ts...>>()(std::string(", ")) + "}";
    }   
    std::string operator()(const std::string& sep) const {
        return sep + type_printer<T>()() + type_printer<type_list<Ts...>>()(sep);
    }   
};
}

#endif

执行命令

g++ -std=c++0x cross_product.cpp && ./a.out

输出:

Cartesian product of type lists
∅ ⨯ ∅ = ∅
∅ ⨯ {A} = ∅
∅ ⨯ {A, B} = ∅
{A, B} ⨯ ∅ = ∅
{A} ⨯ {B} = {(A,B)}
{A} ⨯ {B, C, D} = {(A,B), (A,C), (A,D)}
{A, B} ⨯ {B, C, D} = {(A,B), (A,C), (A,D), (B,B), (B,C), (B,D)}
{A, B, C} ⨯ {D} = {(A,D), (B,D), (C,D)}
{A, B, C} ⨯ {D, E, F} = {(A,D), (A,E), (A,F), (B,D), (B,E), (B,F), (C,D), (C,E), (C,F)}

(我在使用Chrome的Windows上注意到,交叉产品unicode字符效果不佳。抱歉,我不知道如何解决这个问题。)

答案 1 :(得分:7)

不知怎的,我的大脑被炒了:我想我使用的代码比需要的多,但至少它有预期的效果(虽然我没有修复内存泄漏):

#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;
}

答案 2 :(得分:4)

也许是这样的:

template <typename ...Args> struct typelist { };

template <typename S, typename T> struct typelist_cat;

template <typename ...Ss, typename ...Ts>
struct typelist_cat<typelist<Ss...>, typelist<Ts...>>
{
    typedef typelist<Ss..., Ts...> type;
};


template <typename S, typename T> struct product;

template <typename S, typename ...Ss, typename ...Ts>
struct product<typelist<S, Ss...>, typelist<Ts...>>
{
    // the cartesian product of {S} and {Ts...}
    // is a list of pairs -- here: a typelist of 2-element typelists
    typedef typelist<typelist<S, Ts>...> S_cross_Ts;

    // the cartesian product of {Ss...} and {Ts...} (computed recursively)
    typedef typename product<typelist<Ss...>, typelist<Ts...>>::type
        Ss_cross_Ts;

    // concatenate both products
    typedef typename typelist_cat<S_cross_Ts, Ss_cross_Ts>::type type;
};

// end the recursion
template <typename ...Ts>
struct product<typelist<>, typelist<Ts...>>
{
    typedef typelist<> type;
};

现在您应该可以使用product<typelist<A,B,C>, typelist<D,E,F>>::type

答案 3 :(得分:2)

这是另一种解决方案。

#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;
}

答案 4 :(得分:2)

到目前为止,所有解决方案都存在缺点,不必要的依赖关系,不必要的帮助器,并且所有解决方案都受限于二者的笛卡尔幂。以下解决方案没有这样的缺点和支持:

  1. 任何笛卡尔力量,包括0。
  2. 如果任何因素为空集,则返回空集。
  3. 代码是自包含的,不依赖于任何包含文件。
  4. 该功能的输入可以是任何模板类型。
  5. 可以通过第一个模板指定输出列表的类型 参数。
  6. 实际上实际上更难实现(但作为家庭作业很好)然后我想。我实际上正在考虑创建一个小型生成器,它允许我使用扩展模板语法,这使得这些事情变得非常容易。

    简化代码的工作原理如下:product将输入列表tuple<A...>,tuple<B...>,tuple<C...>转换为tuple<tuple<A>...>, tuple<B...>, tuple<C...>。然后将第二个列表传递给product_helper,它执行递归笛卡尔积计算。

    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;
    };
    

    以下是compilable example如何使用代码。

答案 5 :(得分:0)

真的很喜欢这个“作业”作业:)

下面的两个解决方案都创建了一个完整的type_list typedef类,以及成员函数,它们将检查类中是否存在给定的类型列表作为type_list。

第一个解决方案为每个type_list创建所有可能的类型组合,从1到N个类型(width参数定义N)。只做敷衍测试,但我很确定它正在创造所有可能的组合。第二种解决方案只创建成对的类型。

第一个解决方案

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");
    };
};

用法:

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;

第二个解决方案

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");
    };
};

用法:

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;

答案 6 :(得分:0)

C ++ 17

Working Demo

type_lists连接起来的逻辑,以避免像您要求的那样嵌套type_list

// 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{}...));

请注意,这些函数没有(或不需要)实现;这是避免类模板专业化的一种技巧(我从Hana Dusikova的compile time regular expressions中学到了)

然后,简化rowcross_product分别表示为pairscross_product_impl

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使我们更容易表达逻辑。

最后是cross_product的类型别名,它给了我们理论上调用cross_product_impl的类型:

template<class... T>
using cross_product = decltype(cross_product_impl<T...>());

用法与以前基本相同:

cross_product<int, float, short> result;

答案 7 :(得分:0)

使用Boost.Mp11,这是一条短线(一如既往):

using input = type_list<int, float, short>;
using result = mp_product<
    type_pair,
    input, input>;

Demo


我们可以将其概括为从该输入中重复选择N个事物。我们再也无法使用type_pair来对元素进行分组,因此我们只有type_list个元素的列表。为此,我们基本上需要编写:

mp_product<type_list, input, input, ..., input>
//                    ~~~~~~~ N times ~~~~~~~~

与以下相同:

mp_product_q<mp_quote<type_list>, input, input, ..., input>
//                                ~~~~~~~ N times ~~~~~~~~

一种方法是:

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