多个模板的笛卡尔乘积



我有几个类:

template <int,char>
class Foo{};
template <int,char>
class Bar{};

我想用一些参数得到所有的组合,比如:

// {1, 2}, {'a', 'b'}
using CartesianProduct = mp_list<Foo<1,'a'>, Foo<1,'b'>,...,Bar<2,'b'>>;

如果不能用数字常量完成,我可以将模板参数更改为类型并使用std::integral_constant

我发现了一个类似的问题,但要困难得多。使用MPL创建所有模板排列。我想,对于我的情况,有一个更好、更容易的解决方案。

为了好玩,我尝试用可变模板std::integer_sequencestd::tuple_cat对这项工作进行手动实现,实际上很惊讶它能很容易地工作:基于任意长度的简单数组

constexpr std::array<int,2>  t1 = {1, 2};
constexpr std::array<char,3> t2 = {'a', 'b', 'c'};

并且给定可变模板类(例如FooBar(,它自己生成所有可能的排列,并将它们合并为std::tuple(或boost::mpl::list(数据类型,可以为其定义方便的别名:

using SomeAlias = typename AllClassesAllPermutations<t1.size(), t2.size(), t1, t2, Foo, Bar>::type;
  • 由于不熟悉Boost::MPL,我试图远离它:我的第一个版本实际上是基于std::tuple

    请在此处尝试std::tuple版本

  • 如果您想要使用boost::mpl::list而不是std::tuple,则可以使用另一个可变模板转换功能来轻松完成

    请在此处尝试boost::mpl::list版本

下一节将详细介绍如何实现这一目标!


为此,我编写了一个类,该类基于一些模板参数,如intchar参数的所述组合以及相应的template template class,创建了一个std::tuple,包含该单个template template classintchar数组的所有排列。这是通过创建保持成对排列的两个排列向量来完成的。例如,使用函数duplicateArray将两个输入数组t1_in = {1, 2}t2_in = {'a', 'b', 'c'};扩展为t1 = {1, 1, 1, 2, 2, 2}t2 = {'a', 'b', 'c', 'a', 'b', 'c'},然后创建一个std::integer_sequence,以便将两者融合到一个模板T<t1[I], t2[I]>中,该模板与std::integer_sequence相结合,为您提供单个类的所有排列:

template <std::size_t I1, std::size_t I2, std::array<int,I1> const& t1_in, std::array<char,I2> const& t2_in, template <int, char> class T>
class SingleClassAllPermutations {
private:    
template <std::size_t K, std::size_t I, typename TA, std::size_t J>
static constexpr auto duplicateArray(std::array<TA, J> const& arr_in) {
std::array<TA, I*J*K> arr_out {0};
std::size_t l {0};
for (std::size_t i = 0; i < I; ++i) {
for (std::size_t j = 0; j < J; ++j) {
for (std::size_t k = 0; k < K; ++k) {
arr_out[l] = arr_in[j];
++l;
}
}
}
return arr_out;
}
static constexpr std::size_t N = I1*I2;
static constexpr std::array<int,N>  t1 = duplicateArray<I2,1>(t1_in);
static constexpr std::array<char,N> t2 = duplicateArray<1,I1>(t2_in);
static constexpr auto index_seq = std::make_index_sequence<N>{};
template <std::size_t... I>
static constexpr auto getTuple(std::index_sequence<I...>) {
return std::make_tuple(T<t1[I], t2[I]>() ...);
}
public:
static constexpr auto tup = getTuple(index_seq);
};

然后,我创建了一个可变模板,它可以采用几个不同的template template classes(只要它们的模板参数分别为intchar(作为额外的输入参数,然后将各个排列的内部元组tupstd::tuple_cat合并,以创建包含intchar的所有可能排列的元组阵列以及不同的可变template template classes:

template <std::size_t I1, std::size_t I2, std::array<int,I1> const& t1, std::array<char,I2> const& t2, template <int,char> class... Ts>
class AllClassesAllPermutations {
public:
static constexpr auto getTuple() {
return std::tuple_cat(SingleClassAllPermutations<I1,I2,t1,t2,Ts>::tup ...);
}
using type = decltype(getTuple());
};

现在,可以在名称空间内定义全局constexpr数组,并定义方便的alias(如(

namespace some_namespace {
constexpr std::array<int,2>  t1 = {1, 2};
constexpr std::array<char,3> t2 = {'a', 'b', 'c'};
using SomeInstantiation = typename AllClassesAllPermutations<t1.size(), t2.size(), t1, t2, Foo, Bar>::type;
}

通过这种方式,可以重用上面的模板来生成不同的数据类型。

然后模板将其扩展到所有可能的排列,在上面的情况下扩展到

std::tuple<Foo<1,'a'>, Foo<1,'b'>, Foo<1,'c'>, Foo<2,'a'>, Foo<2,'b'>, Foo<2,'c'>,
Bar<1,'a'>, Bar<1,'b'>, Bar<1,'c'>, Bar<2,'a'>, Bar<2,'b'>, Bar<2,'c'>>

最后,如果你想要一个boost::mpl::list,你可以引入一个转换函数,比如

template <class... Ts>
static constexpr auto tupleToMplList(std::tuple<Ts...>) {
return boost::mpl::list<Ts...>{};
}

再次用于decltype而不是std::tuple,这会导致数据类型

boost::mpl::list<Foo<1,'a'>, Foo<1,'b'>, Foo<1,'c'>, Foo<2,'a'>, Foo<2,'b'>, Foo<2,'c'>,
Bar<1,'a'>, Bar<1,'b'>, Bar<1,'c'>, Bar<2,'a'>, Bar<2,'b'>, Bar<2,'c'>>

在这里试试

最后,我自己想明白了。

using namespace boost::mp11;
template <typename C1, typename C2>
struct Foo{};
template <typename C1, typename C2>
struct Bar{};
template <template <typename...> typename... F>
using mp_list_q = mp_list<mp_quote<F>...>;
using TypeList = mp_product<mp_invoke_q, 
mp_list_q<Foo, Bar>, 
mp_list_c<int, 1, 2>, 
mp_list_c<char, 'a', 'b'>>;

结果:

boost::mp11::mp_list<
Foo<std::integral_constant<int, 1>, std::integral_constant<char, (char)97> >,
Foo<std::integral_constant<int, 1>, std::integral_constant<char, (char)98> >,
Foo<std::integral_constant<int, 2>, std::integral_constant<char, (char)97> >,
Foo<std::integral_constant<int, 2>, std::integral_constant<char, (char)98> >,
Bar<std::integral_constant<int, 1>, std::integral_constant<char, (char)97> >,
Bar<std::integral_constant<int, 1>, std::integral_constant<char, (char)98> >,
Bar<std::integral_constant<int, 2>, std::integral_constant<char, (char)97> >,
Bar<std::integral_constant<int, 2>, std::integral_constant<char, (char)98> > >

它使用std::integral_constant作为参数,但它简单而简短。在这里试试

UPD:我发现了如何使用整数本身!

template <int, char>
struct Foo{};
template <int, char>
struct Bar{};
template <template <auto...> typename F>
struct mp_quote_c
{
template <typename... C>
using fn = F<C::value...>;
};
template <template <auto...> typename... F>
using mp_list_qc = mp_list<mp_quote_c<F>...>;
using TypeList = mp_product<mp_invoke_q,
mp_list_qc<Foo, Bar>,
mp_list_c<int, 1, 2>, 
mp_list_c<char, 'a', 'b'>>;

结果:

boost::mp11::mp_list<
Foo<1, (char)97>, Foo<1, (char)98>, Foo<2, (char)97>, Foo<2, (char)98>,
Bar<1, (char)97>, Bar<1, (char)98>, Bar<2, (char)97>, Bar<2, (char)98> >

在这里试试!

UPD2:只有clang才能编译此代码。msvc和gcc中似乎有一个错误,因为clang即使使用-pedantic-errors也可以构建此代码

UPD3:现在gcc也可以编译它

这里有一个名为combine_view的实现:https://stackoverflow.com/a/27175631/225186我在图书馆里用过的https://gitlab.com/correaa/boost-covariant

也许现在MP11库中有更好的东西。

这是我的提炼版本:

#include<boost/mpl/vector.hpp>
#include<boost/mpl/set.hpp>
#include<boost/mpl/fold.hpp>
#include<boost/mpl/zip_view.hpp>
#include <boost/mpl/at.hpp>
#include <boost/mpl/pop_front.hpp>
namespace boost{
namespace mpl{
template <class Seq, class ItrSeq>
class SequenceCombiner{
template < class _Seq = mpl::vector<int_<1> > >
struct StateSeq{
typedef typename pop_front<_Seq>::type sequence;
typedef typename mpl::at<_Seq, int_<0> >::type state;
typedef _Seq type;
};
template < class _Seq, class _State >
struct set_state{
typedef StateSeq< typename push_front<_Seq, _State>::type > type;
};
struct NextOp {
template<typename Out, typename In, typename Enable = typename Out::state>
class apply{
using seq = typename Out::sequence;
using new_state = typename Out::state;
using in_seq = typename mpl::at<In,int_<0> >::type;
using in_itr = typename mpl::at<In,int_<1> >::type;
using new_seq = typename mpl::push_back<seq, in_itr>::type;
public:
typedef typename set_state<new_seq, int_<0> >::type type;
};
template<typename Out, typename In>
class apply<Out,In,mpl::int_<1> >{
typedef typename Out::sequence seq;
typedef typename Out::state state;
typedef typename mpl::at<In,int_<0> >::type in_seq;
typedef typename mpl::at<In,int_<1> >::type in_itr;
typedef typename mpl::begin<in_seq>::type Itr_begin;
typedef typename mpl::next<in_itr>::type  Itr_next;
typedef typename mpl::end<in_seq>::type   Itr_end;
typedef typename mpl::if_<
boost::is_same<Itr_next,Itr_end>,
typename mpl::push_back<seq,Itr_begin>::type,
typename mpl::push_back<seq,Itr_next>::type
>::type new_seq;
typedef typename mpl::if_<boost::is_same<Itr_next,Itr_end>,
mpl::int_<1>,
mpl::int_<0>
>::type new_state;
public:
typedef typename set_state<new_seq, new_state>::type type;
};
};
typedef typename mpl::fold<
typename mpl::zip_view<mpl::vector<Seq, ItrSeq> >::type,
StateSeq<>,
NextOp
>::type StateResult;
public:
typedef typename mpl::if_<
boost::is_same<typename StateResult::state, int_<1> >,
typename mpl::transform<Seq, mpl::end<_1> >::type,
typename StateResult::sequence
>::type next;
};
template<typename Seq, typename Itrs>
struct combine_iterator{
typedef mpl::forward_iterator_tag category;
typedef Seq  seq;
typedef typename transform<Itrs, deref<_1> >::type type;
};
template <class Seq, class Pos>
struct next<typename mpl::combine_iterator<Seq, Pos>>{
typedef typename mpl::SequenceCombiner<Seq,Pos>::next next_Pos;
typedef boost::mpl::combine_iterator<Seq, next_Pos> type;
};
template<class Seq>
class combine_view{
using Pos_begin = typename mpl::transform<Seq, mpl::begin<_1> >::type;
using Pos_end   = typename mpl::transform<Seq, mpl::end<_1>   >::type;
public:
using begin = combine_iterator<Seq, Pos_begin>;
using end   = combine_iterator<Seq, Pos_end>;
using type  = combine_view;
};
} // mpl
} // boost

示例用法:

using boost::mpl::combine_view;
using product = combine_view<
boost::mpl::list<
boost::mpl::list<double, int, std::string>, 
boost::mpl::list<double, int>,
boost::mpl::list<std::string, char>
>
>::type;               
static_assert( boost::mpl::size<product>::value == 12 );