The Tutorial: Metafunctions and Higher-Order Metaprogramming section of the Boost MPL library documentation states that transform
can be invoked like so
typename mpl::transform<D1,D2, mpl::minus<_1,_2> >::type
where the placeholders _1
and _2
signify that when the transform's BinaryOperation
is invoked, its first and second arguments will be passed on to minus in the positions indicated by _1
and _2
, respectively.
I've been reading this over and over again for almost a month and I still don't understand it.
What values exactly do the placeholders _1
and _2
have? D1
and D2
? If so, why not write mpl::minus<D1,D2>
? Also considering that the placeholders are defined as typedef arg<1> _1;
and typedef arg<2> _2;
and consequently the original expression in my mind amounts to
typename mpl::transform<D1,D2, mpl::minus<<arg<1>,<arg<2> > >::type
I'm sure I'm thinking about placeholders the wrong way. I'd appreciate some guidance here.
Indeed, you're thinking about placeholders the wrong way.
mpl::minus
is a template in MPL's metalanguage that symbolically represents (or corresponds to) a certain high-level behavior, namely, subtraction. You're thinking of it as if it's a non-meta construct such as a function
int minus(int a, int b) { return a - b; }
but it's not. (The standard C++11 library does have something kind of like that, called std::minus<>
, but that's not what mpl::minus
does!)
mpl::minus
represents the subtraction operation at a higher level of abstraction. Don't worry for another couple of paragraphs about how mpl::minus
is implemented. Just think about what it represents, which is subtraction of two things.
Ah, but which two things? Well, mpl::minus
lets you specify those things as template parameters. For example,
mpl::minus<mpl::int_<7>, mpl::int_<3>>
expands to a type whose member typedef type
is the same as mpl::int_<4>
.
Okay, but in the dimensional analysis example from Boost, they don't have just two things; they have sequences D1
and D2
of dimensions. (This is a very important point!) Subtracting sequences isn't the same thing as subtracting integers; consider
auto a = std::vector<int>{ 1, 0, 0 };
auto b = std::vector<int>{ 0, 1, 0 };
auto c = (a - b); // Won't compile!
Likewise, in the meta space,
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!
What we mean to say, in the first case, is
auto c = std::vector<int>{};
std::transform(a.begin(), a.end(), b.begin(), std::back_inserter(c), std::minus<>{});
and what we mean to say, in the second (meta) case, is
using c = mpl::transform<a, b, mpl::minus>::type; // caveat: we're not done yet
Notice that the C++11 std::transform
takes pairs of iterators a.begin(), a.end()
instead of just a
; it takes b.begin()
but not b.end()
(a deficiency that is only now being corrected by the Committee); and it mutates c
via an output iterator, rather than returning a completely new object, for efficiency. MPL's compile-time meta-version takes containers a
and b
directly and returns a new container c
, i.e., it has value semantics, which IMHO is strictly easier to think about.
So, the above is all correct, EXCEPT for one tiny detail! mpl::transform
is actually a very generic algorithm, which means that it expects you to spell out the details of the transformation. You said "mpl::minus
", which means "subtract", okay, but subtract what from what? Subtract the elements of the first sequence from elements of the second? Subtract the second's elements from the first? Subtract 42
from the elements of the second sequence and toss out the first one entirely?
Well, we mean "subtract the second sequence's elements, element-wise, from the first's." Which we write as
using c = mpl::transform<a, b, mpl::minus<_1, _2>>::type;
We could equally well write
using c = mpl::transform<b, a, mpl::minus<_2, _1>>::type;
— it would mean the exact same thing.
This generic transform
algorithm lets us write complicated transforms such as
// 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;
Here we can refer to the same element of sequence a
three times, using the symbol _1
, while _2
refers to the corresponding element of sequence b
.
So, that's the point of the symbols _1
and _2
in the context of mpl::transform
. But you're probably still wondering how they're implemented. Well, there's no magic here. They might as well be implemented as
template<int> struct _ {};
using _1 = _<1>;
using _2 = _<2>;
As long as they get unique, distinguishable entities in C++'s type system, that's all MPL really cares about.
But in fact they're actually implemented as typedefs for specializations of mpl::arg
, which leads to a nifty trick. Since _1
is a synonym for mpl::arg<1>
, we can say
_1::apply<A,B,C>::type is the same type as A
_2::apply<A,B,C>::type is the same type as B
...
and I would guess that mpl::transform
is able to take advantage of that fact internally.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With