Boost logo

Boost :

Subject: Re: [boost] [mpl] Nested Scopes w/Placeholders
From: Kitten, Nicholas (nkitten_at_[hidden])
Date: 2012-01-26 10:09:36

On Mon, Jan 23, 2012 at 2:30 AM, Aleksey Gurtovoy
<agurtovoy_at_[hidden]> wrote:
> Hi Nicholas,

Hi Aleksey,

> Actually, you don't need 'protect' here, plain 'lambda< push_back< _1, _2 > >::type' would suffice, the resulting Metafunction Class is already
> shielded from being interpreted as a placeholder expression. Or you can just go with simple 'quote2<push_back>'.

Both of those make sense, and that's certainly the kind of short
addition I was hoping for; the quote2 version is especially nice,
since I rarely swizzle the arguments anyway - thanks!

>> template<
>>      typename BOOST_MPL_AUX_NA_PARAM(T)
>>    , int not_le_ = 0 // not lambda expression?
>>    >
>> struct protect : T
>> {
>> ...
>> typedef protect type;
>> };
> That parameter's name is somewhat misleading; what it actually means is something like "this is an non-type template parameter that prevents 'protect' from being treated as a placeholder expression".

I realized that was probably the case a while after posting, but it's
good to have confirmation.

> If we are try this, it would need to be more along the lines of
>    template<
>          typename BOOST_MPL_AUX_NA_PARAM(T)
>        , bool not_le_ = is_lambda_expression<T>::value
>        >
>    struct protect : T
>    {
>        typedef protect type;
>    };
>    template< typename T >
>    struct protect<T,true> : lambda<T>::type
>    {
>        typedef protect type;
>    };
> .. but I'm pretty sure it's still going to break at least some code. Hmm, let me try this quick... yep, breaks the library itself; check out the following code in "equal.hpp":

Well, even after you cleaned up my errors, I guess that was too hopeful...

> Doable, but the change is definitely not backward compatible.
> The thing is, 'protect' was really conceived w/ bind and Metafunction classes in mind, and it actually works as you'd expect in that context (see Example in
> It might be possible to retrofit it to lambda expressions,
> but it'll require quite a bit of work that I'm not sure is worth it.

I think what gets me (and others before me) is the fact that MPL
mirrors the runtime boost::lambda in so many ways,
making it surprising when a function with the same name doesn't serve
the same purpose (especially when it superficially appears to).

However, for a library as old and fundamental as MPL, I agree that
backwards compatibility is probably more important
in this particular case. I think forcing instantiation of inner
lambdas and/or the use of quoten are fine solutions for my use case
 - they're just subtle for neophytes like me in the land of
metaprogramming, and might warrant a piece of documentation,
just as both Phoenix
and Lambda <>
have sections on nesting.

> Personally, I'd rather work on a more general scoping mechanism
> along the lines of

Alright, that seems reasonable, so I'll share my thoughts. Looking at
the options, if you're concerned about backwards compatibility,
then it seems like any kind of implicit local scoping (for library
algorithms or lambda expressions) is out. That leaves explicit
scopes used with either local variable declarations (Phoenix's let[]
solution) or something similar to the outer() syntax you suggested:

> // outer(arg1)
> template< int n >
> outer_argument<1,n> outer(arg<n>);
> // outer(... outer(arg1))
> template< int scope, int n >
> outer_argument<scope+1,n> outer(outer_argument<scope,n>);
> // outer<n>(arg1)
> template< int scope, int n >
> outer_argument<scope,n> outer(arg<n>);

Okay, let's see what syntax might look like for both options. First,
using Phoenix's simple use case:

   write a lambda expression that accepts:

   1. a 2-dimensional container (e.g. vector<vector<int> >)
   2. a container element (e.g. int)

   and pushes-back the element to each of the vector<int>.

Here's my take on it:

  typedef vector< vector<char>, vector<char> > vec_of_vecs;
  typedef vector< vector<char,int>, vector<char,int> > expected_result;

  // scope and outer version
  typedef transform< _1,
      push_back< _1, outer< _2 > >
> lambda_e1;

  // let<> version, with let declared as:
  // template< LetExpression, typename a = _1, typename b = _2, ... >
struct let; <--- LetExpression includes _a, _b, etc.
  typedef transform< _1,
      push_back< _1, _a >,
      _2 // <-- declares _a to be outer _2
> lambda_e2;

  BOOST_MPL_ASSERT(( equal< apply2< lambda_e1, vec_of_vecs, int
>::type, expected_result, equal<_1, _2> > ));
  BOOST_MPL_ASSERT(( equal< apply2< lambda_e2, vec_of_vecs, int
>::type, expected_result, equal<_1, _2> > ));

Here, let<>, like bind<>, reduces readability somewhat as you have to
look after the expression for declarations, so I think the outer<>
form would be the better of the two for MPL.

Now, a more complex example: write a lambda expression that accepts 3
sequences and outputs
a vector containing the Cartesian product of all 3, assuming non-empty
vectors (similar to

  typedef vector<bool, char> v1;
  typedef vector<short, int> v2;
  typedef vector<float> v3;

  typdef vector< vector< bool, short, float >,
    vector< bool, int, float >,
    vector< char, short, float >,
    vector< char, int, float >,
> expected_result;

  typedef fold< _1, // <--- scope 1
    scope< // <--- scope 2
      fold< outer< _2 >,
        scope< // <--- scope 3
          fold< outer<2, _3 >,
            scope< // <--- scope 4
              push_back< _1, vector< outer<2, _2>, outer<_2>, _2 > >
> lambda_e1;

  BOOST_MPL_ASSERT(( equal< apply3< lambda_e1, v1, v2, v3 >::type,
expected_result, equal<_1, _2> > ));

Not too bad, I suppose. Since scopes are explicit here, you wouldn't
need a specialization for every algorithm, as
you pointed out for implicit scopes, right?

> It's interesting how compile-time and run-time lambdas differ here: in
> MPL, there is no way to automatically determine the implicit scope:
> // sums a sequence of sequence of numbers
> typedef fold< _1, int_<0>
> , fold< _2, _1, plus<_1,_2> >
> // ^^^^^^^^^^ third scope, same question
> // ^^^^^^^^^^^^^^^^^^^^^^^^^^^ second scope, but how do we know?
> > f;
> , at least without some additional metafunction's markup which would
> indicate that 'fold's third argument is a predicate, e.g.:
> template< typename T1, typename T2, typename T3 >
> struct scope< fold<T1,T2,T3> >
> {
> typedef fold<T1,T2,scope<T3> > type;
> };

How does this sound?

> HTH,
> --
> Aleksey Gurtovoy
> MetaCommunications Engineering

You were indeed helpful :)


Nick Kitten

Software Engineer
Center for Video Understanding Excellence
ObjectVideo, Inc.

Boost list run by bdawes at, gregod at, cpdaniel at, john at