Boost logo

Boost :

From: Daniel Walker (daniel.j.walker_at_[hidden])
Date: 2007-04-28 20:10:41


On 4/28/07, Peter Dimov <pdimov_at_[hidden]> wrote:
> Daniel Walker wrote:
> > On 4/28/07, Peter Dimov <pdimov_at_[hidden]> wrote:
> >> Daniel Walker wrote:
> >>
> >>> So, you mean that Bind is definitely going to overload the
> >>> relational operators.
> >>
> >> Bind already overloads the relational operators:
> >>
> >> http://www.boost.org/libs/bind/bind.html#operators
> >
> > Aha, that was the source of my confusion. I'm not so worried about
> > bind(f, _1) < x. This already works even when the lambda header is
> > included. Moving forward, I think breaking this code can be avoided.
> >
> > I'm worried about bind(f, _1 < x) or bind(f, _1) < _2.
>
> bind(f, _1) < _2 is also handled by Bind.

OK, yes, it works because of the bind expression. Bind's relational
operators are overloaded for bind expressions. Got it.

>
> >> in more limited contexts. It is trivial to extend this overloading
> >> to work on expressions that involve no bind calls such as _1 == 5,
> >> _1 > _2 or !_4 - provided that the placeholders aren't inline
> >> functions, of course. :-)
> >
> > It's also trivial to make lambda generate is_bind_expression
> > specialized functors. I think one or the other of the libraries should
> > take care of this use-case but not both, just to avoid the head aches.
>
> This is how I think it should work:

Everything seems fine to me except for one issue. To make sure we're
on the same page, I'm going to try to itemize a couple of the tasks
that need to be done. Let me know if I miss something.

>
> boost::bind(...) relop ...: Bind (as today)

Task for bind: boost::bind needs to use is_bind_expression in case the
second argument to relop is generated by lambda. Other than that, this
already works if one argument to relop is a boost::bind expression and
the other is either a boost::bind expression or a placeholder.

Task for lambda: relop needs to be supplied for std::bind but disabled
for boost::bind. is_bind_exression needs to be specialized for
lambda_functor so that boost::bind's relop can handle it. lambda needs
to accept is_bind_expressions (from either boost::bind, std::bind or a
3rd-party for that matter) so that the whole thing can be composed
into larger lambda expressions.

> !boost::bind(...): Bind

Task for bind: None. This works as is.

Task for lambda: operator! needs to be supplied for std::bind but
disabled for boost::bind.

> _1 relop ...: Bind, translates to boost::bind( __relop(), _1, ... )
> !_1: Bind, translates to boost::bind( __not(), _1 )

If the second argument to relop is a boost::bind expression nothing
needs to be done. It works like above. If not there's a problem. Also,
for !_1 there's a problem. I'll comment below on the TBD items.

>
> _1 + ...: Lambda (based on is_placeholder<>)

I should have this shortly.

> ll::bind( f, _1 ): as above
> boost::bind(...) + ...: Lambda (based on is_bind_expression<>)

No problem here. This comes next.

>
> ll::_1 relop ...: Lambda or Bind - TBD
> !ll::_1: Lambda or Bind - TBD

To get these to be handled by Bind instead of lambda for either lambda
placeholders or standard placeholders you need to add overloads for
placeholders in Boost.Bind, right?. Certainly for !_1. I don't like
this idea. This will already work with lambda's new overloads, which
need to be there because std::bind doesn't have them.

As I tried to illustrate above, the relops for boost::bind expressions
don't cause a problem. lambda will provide them for std::bind, and if
ADL doesn't take care of it, I can explicitly disable lambda's
overloads for boost::bind expressions so that we don't step on each
other's toes. I cannot do that for TR1 placeholders because we'll both
be using the same types. I can only think of two options if you add
relop overloads for placeholders to Bind.

1) I could disable/enable relops for placeholders based on some proxy
type generated from a hold or release function.
2) I could provided relops for std::placeholders in general but
disable them for boost::bind placeholders specifically like I might
have to do for relops used with boost::bind expressions.

Let me know if there's a proverbial 3rd way. Regardless, it would be
much easier (and less of a burden on users) if you just didn't add
operator overloads for placeholders to begin with. Or if you added all
operators to Boost.Bind and got rid of lambda entirely.

The thing is that it's confusing now for people to remember when to
use boost::bind and when to use lambda::bind. I think having duplicate
operators for placeholders will only compound the problem. And now
there's a third bind, std::bind, that behaves differently from the
other two with regard to relop expressions, and users have to keep
track of that. Actually, it could be a massive and unwelcome surprise
if someone using Boost.TR1 for portability reasons switched to a new
platform that didn't have TR1 and found that some of the relops were
suddenly being delayed. Not that that's likely, but it may be
possible.

What I would like to see is for users to be able to think ...

"I need to delay a function call"
  - go to std::bind
  (no need to consider lambda for this ever again)

"OK, now I need to curry the function with placeholders"
  - go to std::placeholders
  (no need to consider lambda for this ever again)

"So far so good. Now I need to compose the function in an expression
with operators"
  - go to boost::lambda
  (no need to worry about qualifying bind, _1, _2, etc. or what
expressions are supported by one and not the other. no more nonsense.)

"Great, that was easy"
  - go home and take a nap

One day boost::bind could be rewritten to support function composition
from operator expressions like lambda or there could be a 3-way merge
with Phoenix and Proto could help things along. That would be great
and lambda could be made obsolete entirely. If it made it into some
future standard, say C++1x, all the better.

For now (i.e. Boost 1.35), I think it might clarify things if there
were a delineation of functionality between bind and lambda with bind
supporting delayed-calls, currying, and composition for functions and
lambda supporting delayed-calls, curing and composition for
expressions with operators. And both can be combined together freely,
intuitively and portably. Also, this should be done without breaking
old code that relies on lambda's bind or placeholders or boost::bind's
relop overloads.

Note that the only reason to keep lambda::bind, lambda::_1, etc. is
too avoid breaking existing code that qualifies the names, and that's
only for a release or two assuming lambda's bind and placeholders are
deprecated. With TR1, they're definitely no longer needed. Future code
should never have to deal with these issues again because the standard
bind and placeholders can just be used.

>
> boost::bind( f, _1 + _2 ): won't work with boost::bind; should work with
> std::bind.

This won't work for boost::bind until it supports is_bind_expression;
afterwards it should be fine, right?

I guess that's it.

Oh, one more thing, while I've been working on result_of I've written
about umpteen million test assertions for the various operators, other
lambda functions for construction, unlambda, embedded stl algorithms,
etc. with various function arities to make sure that I didn't miss an
expression and that they can all be used with result_of. I think I
have good coverage and plan on reusing these tests for TR1
placeholders and bind. So, it should give us some confidence that the
changes work.

Thanks!
Daniel


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