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.

+3


source to share


1 answer


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.

+5


source







All Articles