【问题标题】:Cartesian product of multiple templates多个模板的笛卡尔积
【发布时间】:2021-08-13 21:41:55
【问题描述】:

我有几节课:

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。我认为,对于我的情况,有一个更好、更简单的解决方案。

【问题讨论】:

  • 在您的模板类中是否总是存在相同种类的参数? IE。我们可以仅以 template &lt;int,char&gt; 为例吗?
  • @Caleth 是的,每个模板类中的参数都是相同的。
  • 你为什么要这个呢?更具体地说,MPL 不是 C++11 之前的库,被后来的努力(有时被直接的编译时函数)取代吗?
  • @einpoklum 按照你的逻辑, boost::mp11 不应该存在。您仍然无法使用非模板化的 constexpr 函数创建和修改类型。我需要它进行一些测试。
  • @ИгорьПугачев: mp_list 是 mp11 的一部分还是原始 mpl 的一部分?

标签: c++ templates boost c++17 boost-mpl


【解决方案1】:

为了好玩,我尝试使用可变参数模板 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;

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


为此,我编写了一个类 - 基于一些模板参数,例如上述 intchar 参数的组合以及相应的 template template class - 创建一个包含 此单个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&lt;t1[I], t2[I]&gt;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: p>

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

【讨论】:

  • 好的简单的解决方案,谢谢!它不能扩展到 N 个模板参数,但对我来说没问题。还有mp_rename 用于将元组转换为另一个模板。
  • @ИгорьПугачев 欢迎您!很高兴你喜欢它。您应该能够将其扩展到 N 模板参数。上面的版本已经采用任意多个输入类,因为这些类是可变的,您可以使用constexpr 函数将std::array 初始化为任意长度Add e.g. this。如果您知道模板参数的确切外观,您可以将我放在命名空间中的代码放在另一个类中,其中包含数组下限和上限的模板参数,然后简单地调用该模板。
  • 能够改变类的数量,intchar 的问题是你不能有多个可变参数模板(例如,在这种情况下三个) 不能从参数中扣除的参数包。你可以做的是创建一个std::integer_sequence,将它作为输入参数传递给一个函数(类似于我在上面对数组的引用所做的)。在这种情况下,您最终会像上面一样进入两个阶段的过程,因此一个类将处理单个类的实例化,另一个类将它们合并。
  • 原因是(至少据我所知)你不能在同一个函数中使用两个不同大小的参数包。所以我实际上我不确定像Boost::MPL 这样的库是否可以做到这一点。我想有了这样一个库,可以说你只能有一个可变维度。但请随时保持问题开放并使用Boost::MPL 等待更好的答案。与此同时,我想我的解决方案应该可以完成这项工作!祝你好运!
  • 所以,我自己找到了 MPL 解决方案,即使有数字。这比我想象的要容易。但感谢您的帮助!
【解决方案2】:

最后,我自己想通了。

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

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

Try it here!

UPD2:只有 clang 可以编译此代码。好像msvc和gcc有bug,因为clang即使用-pedantic-errors也可以构建这段代码

UPD3:现在gcc也可以编译了

【讨论】:

  • 不错!实际上,我会牢记这一点,以防有一天我不得不做类似的事情。比我的解决方案容易得多。感谢分享!
【解决方案3】:

这里有一个名为 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 );

【讨论】:

  • 我试过mp11::mp_product,但是template模板参数只有一个。我见过一些类似的问题,其中在类似的情况下使用了占位符,但我仍然没有弄清楚如何在这里使用它。
  • @ИгорьПугачев,看这里的例子:soulrace.top:3001/boost/mp11/src/commit/…
  • 这不是我所需要的。我想使用多个模板和类型,但 mp_product 只使用一个模板(第一个参数)。而且,据我了解,您的 combine_view 也不能像问题中那样组合模板和类型。我找到了,但这种情况要困难得多。 -py4u.net/discuss/97058。我想,对我来说有更简单的解决方案。
  • @ИгорьПугачев,更好的链接,stackoverflow.com/questions/5908961/…。是的,由于语言的限制,组合类型和值的问题是有问题的(它们不符合T... 模式)。我建议首先将常量包装在一个类型中以开始。 (例如std::integral_constant&lt;char, 'a'&gt;)或MPL的char_
  • 正如我所说,我可以用类型替换常量,这不是问题。但我不能组合多个模板。我写了mp_product&lt;mp_assign, mp_list&lt;Foo&lt;_1&gt;, Bar&lt;_1&gt;&gt;, mp_transform&lt;mp_list, mp_list_c&lt;int, 1, 2, 3&gt;&gt;&gt; 并得到了我需要的东西,但我不知道如何将它扩展到模板的 N 个参数(Foo、Bar)。我认为这个解决方案只是一个糟糕的拐杖。
猜你喜欢
  • 2011-12-18
  • 2016-07-10
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2022-01-17
  • 2012-05-14
  • 2015-08-08
相关资源
最近更新 更多