Boost logo

Boost :

From: Joel de Guzman (djowel_at_[hidden])
Date: 2003-08-27 22:28:29

Fernando Cacciola <fernando_cacciola_at_[hidden]> wrote:

> tuple<T&> works because the language explicitely allows references
> to be data members of non-POD class types.
> Yet this partial support for aggregation has subtle (if not incorrect)
> semantics.
> Let me explain,
> A reference is supposed to be always bound to an existing object, right?
> So it makes sense that you must bind a reference to an object upon
> initialization, and that you can't return a reference to a local
> variable, as in:
> int& foo() { int n ; return n ; }
> This rules are intended to preserve reference integrity.

There are a zillion ways to create dangling references. Some are
subtle, some are obvious. Those lines above are obvious and a
modern compiler will catch it. For other more subtle errors, you
will not be warned.

The bottom line is: Trust the programmer. More especially,
trust the Boost programmer! She knows what she's doing and
certainly will not want a straight-jacket imposed on her.

> But there's a subtle loophole though.
> We can agregate a reference as a class member,
> so the following is formally legal, while moraly horrendous:
> struct s
> {
> s( int& v_ ) : v(v_) {}
> int& v ;
> } ;
> s foo()
> {
> int n ;
> return s(n);
> }
> int main()
> {
> s x = foo();
> x.v = 1 ; // Opps! v is unbounded here. How come?
> }
> Here, foo() is effectively returning a reference to a local
> variable, which is left bound to a non existent object.
> That shouldn't have happened.

I am aware of gotchas like this as are other programmers.
Please have trust in us! We won't blame you if we go into
undefined territory, we are solely to blame for any dangling
references that we produce. It won't be your fault. Please don't
be like the high priest who insisted that index range checking is
a must.

> We know that pointers might be dangling, so if 's' above had
> a pointer to v instead of a reference to 'v', the net effect
> would be exactly the same (undefined behaviour), but it wouldn't
> be a surprise since there isn't anything in the language
> trying to make sure pointers donb't loose track of pointee
> existence.

As I said, there are a zillion ways to create dangling references
using straight C++.

>>> optional<T> contains a copy of a value of type T, that is, an _object_ of
>>> type T.
>>> You can't store an object which is a reference, you can only store the
>>> referee or a pointer to it.
>> So how do you state that in tuple's terms? IMO, it's the same. But you're
>> premise seems to be flawed. What is a tuple? Is it a heterogeneous
>> container of *only* "objects"?
> No it isn't, it can also contain references even though tuples were
> originally invented for multiple return values, yet we shouldn't use
> a tuple of references for that.
> I've never used a tuple of references and I'm not quite sure it should
> be supported.

LL and Phoenix wouldn't be possible without them. IIRC, bind also uses
tupled references to arguments. Lot's of cool libraries will be severely
crippled if you impose that!

> One can write:
> typedef tuple<int&,int&> result ;
> result foo()
> {
> int n = 3, m = 4 ;
> return result(n,m);
> }
> And nicely enter undefined behaviour.

Fortunately, Jaakko trusts that we are not that dumb. In fact,
the tie (tuple of references) is a very important mechanism that
is used a lot to great success. Is it dangerous? Yes, but so what?
We know what we are doing. We want the freedom! Otherwise,
we'll be programming in Java!

>> Why? Because of genericity. Why treat references differently?
> Because they are different.
> Why can't you write: int& [2] = {n,m}; ?
> This is after all just a container of two references...
> yet is illegal, and rightly so.
> Why can't you have a reference as a member of a union?

The same reason why unions can't hold non-POD types.
Yet, optional and variant can hold non-POD types, right?
Let's not digress. Stick to the concepts. Let us not be limited
by what can be done in straight C++. This is one of the very
reasons why we are writing libraries: to get past the limitations
of the language.

>> There are
>> cases where you have no control of the type of T and so you have to make
>> it a special case. It's like returning void. Without it, it's a real pain
>> with template programming to treat the special case. So what if void is *not*
>> an object?
> optional<T&> as a special case can be considered, but for this we need
> a strong rationale and quite clear usage guidelines since there is no way to
> guarantee the integrity of the reference, and that is something akward
> to the value semantics of optional<>.
> If it had pointer semantics it would be easier.

Again, no one can guarantee the integrity of a reference. We all know
the behavior of references.

> I agree we should let implementation details aside.
> But I'm unsure about the soundness of aggregating references.

Trust us!

> This is an issue for tuples too just as it is for ordinary
> classes wrapping references.


>> Yes, I have a use-case. But again, I wouldn't want to digress. As I said,
>> I wish that we focus only on the concepts.
> The existence of reference data members are backed up
> by important usage patterns that make it useful in spite its odd semantic
> (i.e., you can end up with an unbound reference)

There's nothing "odd" about it. The behavior of references are well known.
The possibility of a dangling reference is present even in straight C++.

> I need to find such important usage patterns to allow a case that
> will make the following compile and consistently produce undefined
> behaviour:
> optional<int&> foo()
> {
> int n = 2 ;
> return optional<int&>(n);
> }

That's like writing:

    int n = 2;
    return reference_wrapper<int>(n);

Do we ban reference_wrapper because its use might cause undefined


Joel de Guzman

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