Boost logo

Boost :

From: David Abrahams (dave_at_[hidden])
Date: 2003-08-27 19:03:25


"Fernando Cacciola" <fernando_cacciola_at_[hidden]> writes:

> A reference is supposed to be always bound to an existing object,
> right?

Ideally, but as we know that isn't always the case.

> 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.
>
> 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.

There's nothing particularly wrong with aggregating references. There
are ways to create dangling references which don't involve
aggregation:

        void f()
        {
           int *p = new int;
           int& r = *p;
           delete p;
        };

This seems to be a judgement against references as they exist in the
language. Well, they may be a mistake (they certainly mess up the
cleanliness of the type system), but they're here to stay, and people
need to use them. I don't see any reason here that Boost libraries
shouldn't serve references as well as other types.

> 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.

The special rule that governs the lifetime of temporaries bound to
references clearly isn't enough to justify an expectation that
references never dangle. As we've shown, there are several ways to
demonstrate/learn that fact. I don't think Boost libraries should try
to protect people from gross misconceptions about the fundamental
nature of C++, because the nature is still there -- it will bite them
in other ways if they're not aware of it.

> I've never used a tuple of references and I'm not quite sure it should
> be supported.

Ouch. It's going to be a fundamental component of an upcoming
zip_iterator (its reference type).

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

What does that prove? I can "nicely" enter undefined behaviour with
any number of language constructs.

>> 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.

There are other reasons for that; it would violate an invariant that
for any array x, addressof(x[0]) + 1 == addressof(x[1]).

> Why can't you have a reference as a member of a union?

Probably because nobody could figure out how to initialize it.

>> 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<>.

I don't think you need any special guidelines. The behavior of
references is well-understood and not magically different in this
context.

> 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)
>
> 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);
> }

I have a hard time envisioning that as being any more evil than other
dangling references you can't prevent, or than complicating life for
users who want/need an optional reference.

-- 
Dave Abrahams
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