确实,您以错误的方式考虑占位符。
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 与 mpl::int_<4> 相同的类型。
好的,但是在 Boost 的维度分析示例中,它们不仅有两件事;他们有 序列 D1 和 D2 的维度。 (这是非常重要的一点!)减去 序列 与减去 整数 不同;考虑
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++11 std::transform 采用一对迭代器 a.begin(), a.end() 而不仅仅是 a;它需要b.begin() 而不是b.end() (a deficiency that is only now being corrected by the Committee);为了提高效率,它变异 c 通过输出迭代器,而不是返回一个全新的对象。 MPL 的编译时元版本直接采用容器 a 和 b 并返回一个新容器 c,即它具有值语义,恕我直言,这更容易考虑。
所以,以上都是正确的,除了有一个小细节! 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;
这里我们可以引用序列a的相同元素3次,使用符号_1,而_2指的是序列b的对应元素。
所以,这就是mpl::transform 上下文中符号_1 和_2 的点。但是您可能仍然想知道它们是如何实现的。好吧,这里没有魔法。它们也可以实现为
template<int> struct _ {};
using _1 = _<1>;
using _2 = _<2>;
只要它们在 C++ 的类型系统中获得唯一、可区分的实体,这就是 MPL 真正关心的一切。
但实际上它们实际上被实现为 mpl::arg 的特化的 typedef,这导致了一个漂亮的技巧。由于_1 是mpl::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
...
我猜mpl::transform 能够在内部利用这一事实。