Boost logo

Boost :

From: Eric Niebler (eric_at_[hidden])
Date: 2008-03-18 19:21:38


Eric Niebler wrote:
> Steven Watanabe wrote:
>
>> users_guide\expression_transformation\is_callable.html:
>> ...a problem for a type like std::vector<_arg(_arg1)>(), which is a
>> valid transform...
>> Can't you substitute /first/ and /then/ check is_callable?
>
> I could do that, yes. And I seem to recall having a reason for not doing
> that.
>
> ... time passes ...
>
> Either the reason escapes me, or else my reasoning was wrong. I'll
> investigate this.

In trying to implement this, I reminded myself of why it doesn't work.
Take the case of wanting to use functional::make_expr<> as a transform.
I'll use as my example a variation of the Distribute transform I wrote
for Markus Werle.

using functional::make_expr;
struct Distribute
   : when<
         or_<plus<_,_>, minus<_,_> >
       , make_expr<tag_of<_> >(
             Recurse(_left)
           , Recurse(_right))
         )
>
{};

The idea is to match a plus or a minus and create a new node *of the
same type* with transformed children. The above transform doesn't work
as-is today. The reason is because Proto first checks
is_callable<make_expr<tag_of<_> > >::value before doing anything. It's
true, and so we never substitute tag_of<_> before invoking the
make_expr<> transform.

You might say, "well, don't check is_callable<> first. Just do the
substitution and check after." There are two things wrong with that. The
first is that it is slow. Every template type must be completely
disassembled and reassembled before you know what to do with it. But the
killer is that in some cases it, it is simply wrong. Often I pass one
transform as a template parameter to another. For instance, foo<_arg>
might be a transform that first applies the _arg transform, and then
does foo on it. If Proto actually replaced _arg with an expression
before invoking the foo<> transform, it would be an error!

Another way to see the problem is to consider that, within a transform,
the following should logically behave identically:

   typedef foo<_arg> Foo;
   struct Foo : foo<_arg> {};

These are two equivalent ways of defining some transform Foo today. If I
took the "substitute first, ask questions later" approach, these would
mean different things, because the _arg in the first line would be
subject to substitution, but the _arg in the second would not.

You might then say, "well, the problem is that is_callable<make_expr<X>
>::value was true. It should be false." But no, that's obviously wrong
because make_expr<> really *is* callable. Saying it's not would break
make_expr<tag::plus> ...

You can chase the reasoning to all sorts of strange places.
is_callable<X> should rip apart templates and examine their constituents
to determine the result -- but that breaks things too, like the
foo<_arg> example which should be invoked as-is without substitution. It
comes down to this ... Proto can't know whether you want substitution
first or not, so it errs on the side of doing the simple and efficient
thing.

If you want to do substitution first and *then* invoke the resulting
callable transform, you can use bind:

using functional::make_expr;
struct Distribute
   : when<
         or_<plus<_,_>, minus<_,_> >
       , bind<
             make_expr<tag_of<_> >(
                 Recurse(_left)
               , Recurse(_right))
             )
>
>
{};

(I think bind may be incorrectly named. I may call it "lazy".)

All this belongs in a rationale, if only so that I don't forget it again.

-- 
Eric Niebler
Boost Consulting
www.boost-consulting.com

Boost list run by bdawes at acm.org, gregod at cs.rpi.edu, cpdaniel at pacbell.net, john at johnmaddock.co.uk