Boost logo

Boost :

From: Fernando Cacciola (fernando_cacciola_at_[hidden])
Date: 2003-08-27 17:58:47


Joel de Guzman <djowel_at_[hidden]> wrote in message
news:003101c36cb0$64a5feb0$64646464_at_godzilla...
> Fernando Cacciola <fernando_cacciola_at_[hidden]> wrote:
> > Joel de Guzman <djowel_at_[hidden]> wrote in message
> > news:006e01c36c6f$781de2a0$0100a8c0_at_godzilla...
> >> << pardon me if this gets re-posted >>
> >>
> >> Hi,
> >>
> >> Is there a reason why we can't allow:
> >>
> >> optional<int&> opt;
> >>
> > Short answer:
> >
> > References are not objects; i.e., you can't really have a reference _stored_
> > somewhere (even though most implementation do it behind the scenes)
>
> So what?
>
optional<U> means that you either has or don't has an instance of U.
The meanig of 'having' implies some sort of state, and with it,
storage.

optional<U> can be seen as a container of capacity 1 which might be empty.
But how could you 'contain' something that is not an object?

You might say: how about tuple<T&>?

Well,

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.

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.

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.

> > 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.
One can write:

typedef tuple<int&,int&> result ;
result foo()
{
  int n = 3, m = 4 ;
  return result(n,m);
}

And nicely enter undefined behaviour.

> > (see below for the long answer)
> >
> >> Also, is there a reason why we can't allow:
> >>
> >> variant<int&, double&> var;
> >>
> >> IIUC, internally, it's just a matter of storing a boost.reference_wrapper
> >> if T is a reference.
> >>
> > reference_wrapper is simply storing the address of the referee.
> > optional<T&> _could_ be made to store a T*, that is, a pointer
> > to the referee, but in that case, the reference itself wouldn't
> > be optional... the referee would.
> > Now, if optional<T&> would be really a T* in disguise,
> > why would you need the optional wrapper when a NULL T* would convey
> > just the same (specially given that optional<> has
> > a pointer-like interface).
>
> 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?

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

>
> > A reference adds a level of indirection to an _existing_ objects, so,
> > while you can have or not a reference, you can't have a reference
> > to an object that might or might not exist.
> > This is unlike pointers were the null pointer value is specially
> > given the semantic of 'pointing to nothing'.
> >
> > If optional<T&> were allowed, it would have to have exactly the same
> > value semantics as optional<T*>, except that an additional
> > addressof/dereference would be automatically added.
> >
> >> In as much as tuple<int&> is allowed, I see no reason why optional
> >> and variant can't allow references.
> >>
> > With a tuple<> the situation is different because tuple<T&> is just
> > a class with a reference data member, and the language specifically allow
>
> That's obvious. However, that's an implementation issue. I am more
> concerned about the concepts. IMO, tuples, optional and variant should
> share the same underlying concepts as regards to the types they can
> operate on. Tuples is a precedent. I'm glad it supports references. I
> believe optional and variant should follow. Conceptually:
>
> tuples: a container of heterogeneous types
> variant: a union of heterogeneous types
> optional: a variant<T, nil>
>
I agree we should let implementation details aside.
But I'm unsure about the soundness of aggregating references.
This is an issue for tuples too just as it is for ordinary
classes wrapping references.

> > data members to be references but by means of special rules.
> > Since references are not objects, when you have a reference data member
> > you're not necessarily adding the data member to the class storage
> > (even though most implementations do just that).
>
> Again, implementation detail. Let's not dwell on that. Let's focus on the
> concepts. For that matter, pardon me if I snip the rest of your explanations
> regarding more implementation issues.
>
> [snip]
>
> > IOWs, optional<T&> be have to represent an explicit special case.
> > Thus, is there any real-world reason you need optional<T&>?
> > At least you can always use reference_wrapper explicitely, as in
> > optional< reference_wrapper<T> >.
>
> 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)

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

Fernando Cacciola


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