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

18

我希望使用可变模板创建类型列表的交叉乘积。

以下是我的代码:

#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

是否有一种直接的方法可以在不使用 row 辅助函数的情况下完成这个任务,或者应该对嵌套的 type_list 进行“展开”以解决问题?


__cxa_demangle 在底层调用了 malloc,因此您需要负责释放内存 - Jesse Good
4
我们都知道那个问题来自哪里。而且也知道这是安德烈给我们布置的家庭作业。 :) - Xeo
以下的解决方案之一可以工作,但我不知道它是否像安德烈的“边距引用”那样简洁优雅。 - keveman
@R.MartinhoFernandes,Andrei是直接给他布置作业还是在博客上发布的链接? - Mr.Anubis
@Mr.Anubis 这是他的GoingNative演讲中的内容。 - R. Martinho Fernandes
这是一个链接,供那些想要查看此问题来源的人使用 :) http://channel9.msdn.com/Events/GoingNative/GoingNative-2012/Variadic-Templates-are-Funadic - Brett Rossier
9个回答

12

以下是我认为比较清晰的版本:

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

detail/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)}

我注意到在Windows上使用Chrome时,叉积Unicode字符显示不正常。很抱歉,我不知道如何修复这个问题。


非常流畅和简洁。赞! - Dave Abrahams

8
一些代码可能过于冗长,导致我感到头昏脑胀。但至少它能达到预期效果(尽管我没有修复内存泄漏)。
#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;
}

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

@keveman:对的,还有另一个错误,现在已经修复了。原来需要一个连接器,这是我第一次忘记的。它是一个产品,就像2乘3等于6一样,而不是{(0,0),(0,1),(0,2),(1,0),(1,1),(1,2)}。如果OP真的想要成对出现,他应该明确说明。 - Kerrek SB
@cheshirekow,笛卡尔积的笛卡尔幂不仅限于平方,即结果不一定是一对。 - Patrick Fromberg
@KerrekSB 我知道这已经很老了,但你确定它按预期工作吗?请参见此实时示例,并尝试取消注释的版本。 - dyp
@DyP:我不能说我真正理解我在做什么;请随意修复它,或者让我知道是否应该删除它。 - Kerrek SB
我认为现在已经修复了,还添加了对空列表作为“product”的第一个参数的支持。带有三个测试用例的实时示例 - dyp
显示剩余3条评论

4

C++17

演示作品

为避免像你所要求的嵌套type_list,需要将type_lists连接起来的逻辑:

// 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的编译时正则表达式中学到了它)。
然后,将您的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;

3
到目前为止,所有的解决方案都有缺点,不必要的依赖关系,不必要的帮助程序,并且都限制于二元笛卡尔积。下面的解决方案没有这些缺点,并支持:
  1. 包括0在内的任何笛卡尔幂。
  2. 如果任何因数为空集,则返回空集。
  3. 代码是自包含的,不依赖于任何包含文件。
  4. 函数的输入可以是任何模板类型。
  5. 输出列表的类型可以通过第一个模板参数指定。
实际上,它比我想象中更难实现(但作为家庭作业很好)。我正在考虑创建一个小型生成器,允许我使用扩展的模板语法,使这些事情变得非常容易。
简化后的代码如下: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;
};

这里是一个可编译的示例,展示了代码如何使用。


1
在我看来,你可以将cat2写成template<class A, class B> struct cat2; template< template<class...> class R, class... A, class... B> struct<R<A...>, R<B...>> { .. }; - dyp

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

1
注意:这不是OP所要求的内容...但对于其他人(比如我)偶然发现这个问题可能会有帮助。以下是使用Loki::TypeList(即C++-11之前)实现的方法,也许具有历史意义或为了兼容性考虑。
此外,也许我在污染loki的命名空间方面有些自以为是。你的情况可能会有所不同。

crossproduct.h

#include "loki/NullType.h"
#include "loki/Typelist.h"

namespace Loki {
namespace   TL {

/// a pair of two types
template <typename A_t, typename B_t>
struct TypePair
{
    typedef A_t A;
    typedef B_t B;
};


/// a template which takes one type and pairs it with all other types
/// in another typelist
template <class T, class TList > struct DistributePair;

/// specialization of Distribute for the nulltype
template < class TList >
struct DistributePair< NullType, TList >
{
     typedef NullType type;
};


/// specialization of Distribute where the second parameter is nulltype
template <class T >
struct DistributePair< T, NullType >
{
     typedef NullType type;
};

/// specialization of Distribute where the first parameter is a
/// typelist
template <class T, class Head, class Tail >
struct DistributePair< T, Typelist<Head,Tail> >
{
     typedef Typelist<
                 TypePair<T,Head>,
                 typename DistributePair<T,Tail>::type
                     > type;
};

/// performs cartesion product of two typelists
template <class TListA, class TListB> struct CrossProduct;

/// specialization of CrossProduct for NullType
template <class TListB>
struct CrossProduct< NullType, TListB >
{
    typedef NullType type;
};

/// specialization of CrossProduct for recursion
template <class Head, class Tail, class TListB>
struct CrossProduct< Typelist<Head,Tail>, TListB >
{
    typedef typename Append<
            typename DistributePair< Head,TListB >::type,
            typename CrossProduct< Tail, TListB >::type
              >::Result type;
};

} // namespace TL
} // namespace Loki

test.cpp

#include <crossproduct.h>
#include <loki/HierarchyGenerators.h>
#include <iostream>

struct A{};
struct B{};
struct C{};

struct D{};
struct E{};
struct F{};

typedef LOKI_TYPELIST_3(A,B,C)  TypeListA_t;
typedef LOKI_TYPELIST_3(D,E,F)  TypeListB_t;

typedef typename Loki::TL::CrossProduct< TypeListA_t, TypeListB_t >::type Cross_t;

template <typename T> const char* toString();

template <> const char* toString<A>(){ return "A"; };
template <> const char* toString<B>(){ return "B"; };
template <> const char* toString<C>(){ return "C"; };
template <> const char* toString<D>(){ return "D"; };
template <> const char* toString<E>(){ return "E"; };
template <> const char* toString<F>(){ return "F"; };

template <typename T> struct Printer
{
    Printer()
    {
        std::cout << toString<T>() << ", ";
    }
};

template <typename T1, typename T2>
struct Printer< Loki::TL::TypePair<T1,T2> >
{
    Printer()
    {
        std::cout << "(" << toString<T1>() << "," << toString<T2>() << "), ";
    }
};


typedef Loki::GenScatterHierarchy< TypeListA_t, Printer > PrinterA_t;
typedef Loki::GenScatterHierarchy< TypeListB_t, Printer > PrinterB_t;
typedef Loki::GenScatterHierarchy< Cross_t, Printer >     PrinterCross_t;

int main(int argc, char** argv)
{
    std::cout << "\nType list A: \n   ";
    PrinterA_t a;
    std::cout << "\nType list B: \n   ";
    PrinterB_t b;
    std::cout << "\nType list Cross: \n   ";
    PrinterCross_t cross;

    return 0;
}

输出

Type list A: 
   A, B, C, 
Type list B: 
   D, E, F, 
Type list Cross: 
   (A,D), (A,E), (A,F), (B,D), (B,E), (B,F), (C,D), (C,E), (C,F), 

1

使用Boost.Mp11,这是一个短的一行代码(一如既往):

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

演示


我们可以将此推广到从输入中选择具有重复的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>
        >>;

演示


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;

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接