Boost logo

Boost :

From: Peter Dimov (pdimov_at_[hidden])
Date: 2001-10-19 06:23:56


From: <brianjparker_at_[hidden]>
> --- In boost_at_y..., "Peter Dimov" <pdimov_at_m...> wrote:
> > * You can tell from a glance at the bind expression which arguments
> are
> > by-value and which are by-ref.
>
> I think that this is back-to-front. The writer of the function and
> its signature defines the semantics of the function call- if he/she
> uses pass by non-const reference then it because the function is
> modifying and returning a result. The caller will need this result in
> the vast majority of cases.

Yes, but the disadvantage is that you have to look up the declaration of the
function to understand the call semantics.

Of course there are two schools of thought on this topic, and this argument
alone is not sufficient.

> This also seems (to me) to be a simpler,
> more obvious behaviour to explain i.e. just the usual function call
> semantics.

But it's less consistent, since it separates functions from function
objects. The (idealistic) goal is that the two should have the same
properties and semantics, not only when being bound, but in general.

That's why it's being debated whether to enable Koenig lookup for function
objects, for instance.

Then there is the problem with const references: does a const reference mean
an optimized pass-by-value or is it really a reference argument? Impossible
to infer automatically, so we have to decide what is more intuitive.
Unfortunately, relying on what's intuitive is very dangerous, since users
have different intuitions.

That's why I avoided any kind of autodetection - even on the first argument
to a member function. I don't want to second-guess the user.

> > * If you make bind autodetect, you need a mechanism to force pass-
> by-value
> > when the autodetection is wrong.
>
> The autodetection is never wrong (for functions and function pointer
> anyway), though the user may wish to subvert the function author's
> intentions.

It might well be in the const reference case. You don't know whether the
const reference you are getting when the function is being bound is a
temporary that will vanish soon or a real reference to a const object that
will persist till the actuall call.

> In this less common case where a caller wants to discard
> a pass by reference result, then they can just pass in a copy of the
> variable; using static_cast will do- there is an example in the
> sample code of this (one could of course write a simple T copy_val(T)
> function to do the same thing).

One thing that I don't understand from the code is: doesn't passing a
temporary to your bind() lead to a dangling reference? Remember that you can
assign the bind() result to boost::function, which will outlive the
temporaries.

[...]
> Without this basic requirement being met then the object can not have
> a well-defined signature, and if there are overloaded versions of
> operator() then the object is no longer equivalent to a single global
> function but rather to a set of overloaded global functions.

Yes, and this property can be quite convenient at times; bind() will
currently do overload resolution, as intended. The only limitation is the
single return type - no typeof, sadly.

> Finally, I should say that for function objects this whole issue is
> less important- being objects they carry their own state and so have
> less need for pass by reference.

No, I don't think so. Function objects produced by lambda libraries
routinely take arguments by reference: _1 += _2;

> Yes, it weakens type safety slightly, in two cases- (1) temporaries
> can be bound to non-const references and (2) non-const references can
> bind to const variables. In fact, for the bind function (1) is an
> advantage and is used as described above to avoid a pass-by-
> reference. Case (2) is unavoidable without additional language
> support (e.g. a safe_const_cast that only allows the casting away of
> const if the original variable wasn't really const). (Though note
> that modifing a const lvalue through a non-const reference has
> unspecified behaviour and so a good compiler could give an error or
> warning in most cases, in theory, anyway).

It's (2) that I'm concerned about:

void f(int & a, int b) { a += b; }

vector<int> const v(5);

for_each(v.begin(), v.end(), bind(f, _1, 5)); // undefined behavior AFAICS

I could live with this were the temporaries issue a showstopper, but it's
not. It's only a problem in toy examples, like those in the documentation.
:-)

> On a related note, one feature that might be useful to add to the
> library is a _free dummy argument for the common case where the order
> of the unbound arguments isn't changed or duplicated e.g.
>
> func(a, _free, b, _free, _free) (c, d, e)
>
> would be equivalent to
>
> func(a, _1, b, _2, _3)(c, d, e)

This would be

bind(f, _, b, _, _)

in my dream syntax.

> I don't know if that is possible, I couldn't think of an easy way to
> do it.

I think that it's possible on a real compiler. I don't have the nerve to try
to get that working on MSVC.

--
Peter Dimov
Multi Media Ltd.

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