关于boost mpl占位符的推理

Reasoning about boost mpl placeholders

本文关键字:推理 占位符 mpl boost 关于      更新时间:2023-10-16

Boost MPL库文档的教程:元函数和高阶元编程部分指出,transform可以像一样调用

typename mpl::transform<D1,D2, mpl::minus<_1,_2> >::type

其中占位符_1_2表示当调用变换的BinaryOperation时,它的第一个和第二个自变量将分别在_1_2指示的位置上传递到minus。

我已经一遍又一遍地读了将近一个月了,但我仍然不明白

占位符_1_2究竟有什么值?CCD_ 9和CCD_?如果是,为什么不写mpl::minus<D1,D2>?还考虑到占位符被定义为typedef arg<1> _1;typedef arg<2> _2;,因此在我看来的原始表达式相当于

typename mpl::transform<D1,D2, mpl::minus<<arg<1>,<arg<2> > >::type

我肯定我想占位符的方式不对。我很感激这里的指导。

实际上,您对占位符的考虑是错误的。

mpl::minus是MPL元语言中的一个模板,它象征性地表示(或对应)某种高级行为,即减法。你认为它是一个非元结构,比如函数

int minus(int a, int b) { return a - b; }

但事实并非如此。(标准C++11库确实有类似的东西,称为std::minus<>,但这是而不是mpl::minus的功能!)

mpl::minus表示更高抽象级别上的减法运算。关于mpl::minus是如何实现的,请不要担心接下来的几段内容。想想它代表什么,这是两件事的减法。

啊,但是哪两件事?mpl::minus允许您将这些内容指定为模板参数。例如,

mpl::minus<mpl::int_<7>, mpl::int_<3>>

扩展为成员typedef type与CCD_。

好吧,但在Boost的维度分析示例中,他们不仅仅有两件事;它们具有序列CCD_ 22和CCD_。(这是非常重要的一点!)减去序列与减去integers不是一回事;考虑

auto a = std::vector<int>{ 1, 0, 0 };
auto b = std::vector<int>{ 0, 1, 0 };
auto c = (a - b);  // Won't compile!

同样,在元空间中,

using a = mpl::vector<mpl::int_<1>, mpl::int_<0>, mpl::int_<0>>;
using b = mpl::vector<mpl::int_<1>, mpl::int_<0>, mpl::int_<0>>;
using c = mpl::minus<a,b>;  // Won't compile!

我们的意思说,在第一种情况下,是

auto c = std::vector<int>{};
std::transform(a.begin(), a.end(), b.begin(), std::back_inserter(c), std::minus<>{});

在第二种(元)情况下,我们的意思是

using c = mpl::transform<a, b, mpl::minus>::type;  // caveat: we're not done yet

注意,C++11std::transform采用了成对的迭代器a.begin(), a.end(),而不仅仅是a;它需要CCD_ 27而不是CCD_;并且为了提高效率,它通过输出迭代器对c进行变异,而不是返回一个全新的对象。MPL的编译时元版本直接获取容器ab,并返回一个新的容器c,即它具有值语义,IMHO更容易考虑这一点。

所以,以上都是正确的,除了一个小细节!mpl::transform实际上是一个非常通用的算法,这意味着它希望您拼写出转换的细节。你说"mpl::minus",意思是"减法",好吧,但从什么中减去什么?从第二序列的元素中减去第一序列的元素?从第一个元素中减去第二个元素?从第二个序列的元素中减去42,并完全抛出第一个序列?

好吧,我们的意思是"从第一个序列中减去第二个序列的元素。"我们把它写成

using c = mpl::transform<a, b, mpl::minus<_1, _2>>::type;

我们同样可以写

using c = mpl::transform<b, a, mpl::minus<_2, _1>>::type;

--这意味着完全相同的事情。

这种通用的transform算法使我们能够编写复杂的变换,如

// hide some irrelevant boilerplate behind an alias
template<typename... Ts>
using multiplies_t = mpl::multiplies<Ts...>::type;
// compute c = a^2 + 2ab + 1
using c = mpl::transform<a, b,
    mpl::plus<multiplies_t< _1, _1 >,               // a^2 ...
              multiplies_t< mpl::int_<2>, _1, _2 >, // ... + 2ab ...
              mpl::int_<1>>                         // ... + 1
>::type;

在这里,我们可以使用符号_1来三次引用序列a的相同元素,而_2指代序列b的相应元素。

这就是符号_1_2mpl::transform上下文中的。但你可能仍然想知道它们是如何实现的。这里没有魔法。它们也可以作为来实现

template<int> struct _ {};
using _1 = _<1>;
using _2 = _<2>;

只要它们在C++的类型系统中得到唯一的、可区分的实体,这就是MPL真正关心的。

但事实上,它们实际上被实现为mpl::arg专业化的typedef,这导致了一个巧妙的技巧。由于_1mpl::arg<1>的同义词,我们可以说

_1::apply<A,B,C>::type  is the same type as  A
_2::apply<A,B,C>::type  is the same type as  B
                                ...

我猜想CCD_ 47能够在内部利用这一事实。