多个模板的笛卡尔积

Cartesian product of multiple templates

我有几个类:

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,如果它不能用数字常量完成的话。

我发现了一个类似的问题,但难度要大得多。 Creating all template permutations with MPL。我想,我的情况有更好更简单的解决方案。

这里有一个名为 combine_view 的实现: that I used in my library 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 );

为了好玩,我尝试使用可变参数模板 std::integer_sequencestd::tuple_cat 手动实现 并且真的很惊讶很容易让它工作:基于简单的任意长度的数组

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

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

using SomeAlias = typename AllClassesAllPermutations<t1.size(), t2.size(), t1, t2, Foo, Bar>::type;

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


为此,我写了一个 class,它基于一些模板参数,例如上述 intchar 参数的组合以及相应的 template template class - 创建一个 std::tuple,其中包含 单个 template template classintchar 数组的所有排列。这是通过创建两个包含成对排列的排列向量来完成的。例如。两个输入数组 t1_in = {1, 2}t2_in = {'a', 'b', 'c'}; 使用函数 duplicateArray 扩展为 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 结合为您提供单个 class:

的所有排列
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 合并,以创建包含 [=30 的所有可能排列 的 元组=] 和 char 数组以及不同的可变参数 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'>>

Try it here

终于自己想通了

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 作为参数,但它简单而简短。 Try it here

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

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

Try it here!

UPD2: 只有 clang 可以编译这段代码。似乎 msvc 和 gcc 中存在错误,因为即使使用 -pedantic-errors

clang 也可以构建此代码

UPD3: 现在gcc也可以编译了