Reasoning about incremental mpl entries
"Tutorial: Metafiles and Higher-Order Metaprogramming" in the Boost MPL documentation says that transform
you can call like this
typename mpl::transform<D1,D2, mpl::minus<_1,_2> >::type
where placeholders _1
and _2
mean that when the transformation is invoked, BinaryOperation
its first and second arguments will be passed to minus at the positions indicated by _1
and, _2
respectively.
I have been reading this over and over again for almost a month and I still don't get it.
What are the meanings of placeholders _1
and _2
? D1
and D2
? If so, why not write mpl::minus<D1,D2>
? Also considering that placeholders are defined as typedef arg<1> _1;
and typedef arg<2> _2;
and hence the original expression in my mind is
typename mpl::transform<D1,D2, mpl::minus<<arg<1>,<arg<2> > >::type
I'm sure I'm thinking the placeholders are wrong. I would appreciate some guidance here.
source to share
In fact, you are thinking about placeholders the wrong way.
mpl::minus
is a template in the MPL metalanguage that symbolically represents (or corresponds to) a certain high-level behavior, namely subtraction. You think of it like it's a non-meta construct like a function
int minus(int a, int b) { return a - b; }
but this is not the case. (The C ++ 11 Standard Library has something like this called std::minus<>
, but not what mpl::minus
it does!)
mpl::minus
represents a subtraction operation at a higher level of abstraction. Don't worry about a couple more paragraphs on how it's mpl::minus
implemented. Just think about what it represents, which is a subtraction of two things.
Oh, but what two things? So, mpl::minus
lets you specify these parameters as template parameters. For example,
mpl::minus<mpl::int_<7>, mpl::int_<3>>
expands to a type, the typedef member is the type
same as mpl::int_<4>
.
Okay, but in the Boost sizing example, they only have two things missing; they have consistency D1
and D2
sizes. (This is a very important point!). Subtracting sequences is not the same as subtracting whole numbers; Consider
auto a = std::vector<int>{ 1, 0, 0 };
auto b = std::vector<int>{ 0, 1, 0 };
auto c = (a - b); // Won't compile!
Similarly, in the metaspace
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 want to say, in the first case,
auto c = std::vector<int>{};
std::transform(a.begin(), a.end(), b.begin(), std::back_inserter(c), std::minus<>{});
and what we want to say is in the second (meta) case,
using c = mpl::transform<a, b, mpl::minus>::type; // caveat: we're not done yet
Note that C ++ 11 std::transform
accepts pairs of iterators a.begin(), a.end()
instead of just a
; required b.begin()
, but not b.end()
(a flaw that is only now being corrected by the Committee ); and it modifies c
with the output iterator rather than returning a completely new object, for efficiency reasons. The meta version of MPL compilation takes containers a
and b
directly and returns a new container c
i.e. Has meaning semantics that IMHO is just easier to think about.
So the above is all right, EXCEPT for one tiny detail! mpl::transform
is actually a very general algorithm, which means it expects you to explain the details of the transformation. You said " mpl::minus
" which means "subtract", okay, but subtract from what? Subtract the elements of the first sequence from the elements of the second? Subtract the second item from the first? Subtract 42
from the elements of the second sequence and discard the first part entirely?
Well, we mean "subtract the elements of the second sequence, step by step, from the first." Which we write as
using c = mpl::transform<a, b, mpl::minus<_1, _2>>::type;
We could write well
using c = mpl::transform<b, a, mpl::minus<_2, _1>>::type;
- it will mean the same thing.
This general algorithm transform
allows you to write complex transformations 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 sequence element a
three times using a symbol _1
, and _2
refers to the corresponding sequence element b
.
So that's the point of symbols _1
and _2
context mpl::transform
. But you are probably still wondering how they are implemented. Well, there is no magic here. They can also be implemented as
template<int> struct _ {};
using _1 = _<1>;
using _2 = _<2>;
As long as they get unique, distinguishable entities in a C ++ type system, that's all MPL really cares about.
But in reality they are actually implemented as typedefs for specializations mpl::arg
, which leads to a neat trick. Since it _1
is synonymous 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 suggest that I mpl::transform
can take advantage of this fact internally.
source to share