Topic: Allowing non-const references to temporaries (was: C++0x)


Author: brangdon@cix.co.uk (Dave Harris)
Date: Mon, 7 May 2001 23:21:54 GMT
Raw View
heard@vvnet.com (C. M. Heard) wrote (abridged):
> In effect you are saying that reference members -- or for that matter
> pointer members -- are never safe if they are bound to external objects
> specified by a user of the class, because it is possible for the user
> to specify an external object whose lifetime is too short.

Yes; the area is already fraught with danger.


> On the other had, suppose that T admits an implicit conversion from S
> [...]

This is saying that C++ makes short-lived temporaries at the drop of a
hat. I agree, but this is (or should be) orthogonal to the const versus
non-const issue.


> I would not call this safe, since it is guaranteed to leak memory.

Well, X would need to free it, of course.
    delete &reMember;


> But it is _trivial_ to make it compile.  Just use an explicit named
> temporary (aka local variable):

True. However, a rule which required temporary expressions to be
explicitly named would be quite onerous. That is why we permit things
like:
    int z = a+b+c+d+e;


> [ Aside: since the same thing could be done for const references if the
>   language did not allow them to be bound to temporaries, I wonder why
>   that feature was ever considered desirable or necessary.  No one has
>   ever explained that, despite my having asked more than once. ]

I imagine they wanted to permit the same kinds of short-hand for
user-defined types. Eg:

    class Matrix { ... };
    Matrix operator+( const Matrix &rhs, const Matrix &lhs );
    Matrix a,b,c,d,e;
    Matrix z = a+b+c+d+e;

The result of (a+b) is a temporary, so it would be awkward if we couldn't
pass it to operator+().

We could get around this by copying, but copying of (potentially large)
matrixes is inefficient. We could use reference-counting with
copy-on-write, but even that is relatively expensive (and also hard to
get right). See the debates on std::string.


> The proposed change does adversely affect safety.

I agree it has some adverse effects on safety. However, I think making
the system simpler, more uniform, orthogonal and generally easier to
understand, would have some beneficial effects on safety.

  Dave Harris, Nottingham, UK | "Weave a circle round him thrice,
      brangdon@cix.co.uk      |   And close your eyes with holy dread,
                              |  For he on honey dew hath fed
 http://www.bhresearch.co.uk/ |   And drunk the milk of Paradise."

---
[ comp.std.c++ is moderated.  To submit articles, try just posting with ]
[ your news-reader.  If that fails, use mailto:std-c++@ncar.ucar.edu    ]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://www.research.att.com/~austern/csc/faq.html                ]





Author: brangdon@cix.co.uk (Dave Harris)
Date: Mon, 7 May 2001 01:34:28 GMT
Raw View
heard@vvnet.com (C. M. Heard) wrote (abridged):
> >* enable non-const references to temporaries
>
> I hope not, because that render unsafe interface definitions that
> are now safe.  Consider, for example, a class that binds a reference
> argument to a reference member:
>
> class X {
>   private:
>     T refMember;
>   public:
>     X (T& refAgument):  refMember(refArgument) {}
>   // and so on (T is an arbitrary type name)
> }

Presumably that should be:
    T &refMember;

In my view this interface is not safe. Eg:

    X demo_old() {
        int z = 99;
        return X( z );
    }

returns an X with its refMember bound to a z which has gone out of scope.
It is an unsafe interface and the current rule does not make it safe.


> Changing the current rules would allow a temporary to be *silently*
> bound to instances of X::refMember.  Under current rules attempting
> to provide a temporary as the constructor argument will draw a
> compiler diagnostic.
>
> What is the benefit from this change that would justify breaking
> existing code?

No existing code would be broken, in the sense that the semantics of
existing correct programs would not be changed.

However, it does mean some (more) incorrect uses would not be detected.
The situation would be as for:

    class Y {
    private:
        const T &refMember;
    public:
        Y (const T& refAgument):  refMember(refArgument) {}
    };

which allows an unsafe binding of a temporary today. In my view the extra
safety of X over Y is relying on a hack. Whether refMember is declared
const or non-const should depend on whether const or non-const semantics
are appropriate. We shouldn't change the choice just to exploit this
unrelated rule about lifetimes of temporaries.

It's also worth considering cases like:

    X demo_2() {
        return X( *new int(99) );
    }

which is forbidden under the current rule, but which is safe.

Basically, the current rule is a nightmare. It forbids some safe uses and
allows some unsafe ones. Minor changes to the way code is written affect
what is or is not allowed without affecting its safety.

  Dave Harris, Nottingham, UK | "Weave a circle round him thrice,
      brangdon@cix.co.uk      |   And close your eyes with holy dread,
                              |  For he on honey dew hath fed
 http://www.bhresearch.co.uk/ |   And drunk the milk of Paradise."

---
[ comp.std.c++ is moderated.  To submit articles, try just posting with ]
[ your news-reader.  If that fails, use mailto:std-c++@ncar.ucar.edu    ]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://www.research.att.com/~austern/csc/faq.html                ]





Author: "C. M. Heard" <heard@vvnet.com>
Date: Mon, 7 May 2001 05:26:09 GMT
Raw View
> heard@vvnet.com (C. M. Heard) wrote (abridged):
> > >* enable non-const references to temporaries
> >
> > I hope not, because that render unsafe interface definitions that
> > are now safe.  Consider, for example, a class that binds a reference
> > argument to a reference member:
> >
> > class X {
> >   private:
> >     T refMember;
> >   public:
> >     X (T& refAgument):  refMember(refArgument) {}
> >   // and so on (T is an arbitrary type name)
> > }
>
> Presumably that should be:
>     T &refMember;

Correct.  My apologies for the error.

> In my view this interface is not safe. Eg:
>
>     X demo_old() {
>         int z = 99;
>         return X( z );
>     }
>
> returns an X with its refMember bound to a z which has gone out of scope.
> It is an unsafe interface and the current rule does not make it safe.

In effect you are saying that reference members -- or for that matter
pointer members -- are never safe if they are bound to external objects
specified by a user of the class, because it is possible for the user
to specify an external object whose lifetime is too short.

It's true that it's possible to make this error, but my experience has
been that it's usually not very hard in practice to avoid.

On the other had, suppose that T admits an implicit conversion from S
(e.g. T is unsigned int and S is signed int).  If reference arguments
could be bound to temporaries, then the following code would happily
compile and crash, instead of causing a diagnostic:

S externObject;
X x(externObject);

That error, unfortunately, turns out to be rather easy to make, and I
find the existing rule very helpful at avoiding it.

> No existing code would be broken, in the sense that the semantics of
> existing correct programs would not be changed.

True, existing code would run;  but libraries that can now be used with
relatively safety would cease to be safe.  In my book, that means they
would be broken by the change.

> However, it does mean some (more) incorrect uses would not be detected.
> The situation would be as for:
>
>     class Y {
>     private:
>         const T &refMember;
>     public:
>         Y (const T& refAgument):  refMember(refArgument) {}
>     };
>
> which allows an unsafe binding of a temporary today. In my view the extra
> safety of X over Y is relying on a hack. Whether refMember is declared
> const or non-const should depend on whether const or non-const semantics
> are appropriate. We shouldn't change the choice just to exploit this
> unrelated rule about lifetimes of temporaries.

Right, I've pointed out in previous postings here and to clc++m that the
above practice is unsafe, having been burned by it before.  And I agree
that the inconsistent treatment of const vs. non-const references with
respect to binding to temporaries is a bad thing.  But in my view, it was
a mistake in the design of the language to allow _any_ references to be
bound to temporaries.  The "hack" is making const reference semantics
equivalent to an optimized pass-by-value.  It would have been preferable
to just use pass by value when that's what's wanted semantically.  As
things now stand, the above class needs to to have its constructor
argument changed either to a non-const reference (which works only if
the objects to be bound to the class are not const, even though the
class will not modify them through its reference member), or to a
pointer to const --

    class Y {
    private:
        const T &refMember;
    public:
        Y (const T *ptrArgument):  refMember(*ptrArgument) {}
    };

In this case the pointer argument may be null, which leaves a safety hole
that I do not like, but at least cannot be bound to a temporary.  I'll
take the former over the latter because it almost never fails silently
(most implementations will dump core if a null pointer is dereferenced),
but I'm not pleased I have to make this choice.

> It's also worth considering cases like:
>
>     X demo_2() {
>         return X( *new int(99) );
>     }
>
> which is forbidden under the current rule, but which is safe.

I would not call this safe, since it is guaranteed to leak memory.
But it is _trivial_ to make it compile.  Just use an explicit named
temporary (aka local variable):

    X demo_2() {
        int *z = new int(99);
        return X( *z );
    }

[ Aside: since the same thing could be done for const references if the
  language did not allow them to be bound to temporaries, I wonder why
  that feature was ever considered desirable or necessary.  No one has
  ever explained that, despite my having asked more than once. ]

> Basically, the current rule is a nightmare. It forbids some safe uses and
> allows some unsafe ones. Minor changes to the way code is written affect
> what is or is not allowed without affecting its safety.

The proposed change does adversely affect safety.  If it is enacted, no
one will be able to safely use a reference parameter to initialize a
reference member if the type involved allows for implicit conversions.
Class interfaces can be fixed by changing the parameter from a reference
to a pointer, but at the cost of another safety hole (null pointers
being passed in a context where it is impossible to check for them
before use).  I hope that the majority of the committee disagrees with
you, Anthony Williams, and Mirek Fidler on this point, and keeps
the rule the way it is.

Mike

---
[ comp.std.c++ is moderated.  To submit articles, try just posting with ]
[ your news-reader.  If that fails, use mailto:std-c++@ncar.ucar.edu    ]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://www.research.att.com/~austern/csc/faq.html                ]





Author: "C. M. Heard" <heard@vvnet.com>
Date: Sun, 6 May 2001 11:49:20 GMT
Raw View
Anthony Williams would like to:
>* enable non-const references to temporaries

I hope not, because that render unsafe interface definitions that
are now safe.  Consider, for example, a class that binds a reference
argument to a reference member:

class X {
  private:
    T refMember;
  public:
    X (T& refAgument):  refMember(refArgument) {}
  // and so on (T is an arbitrary type name)
}

Changing the current rules would allow a temporary to be *silently*
bound to instances of X::refMember.  Under current rules attempting
to provide a temporary as the constructor argument will draw a
compiler diagnostic.

What is the benefit from this change that would justify breaking
existing code?

Mike

---
[ comp.std.c++ is moderated.  To submit articles, try just posting with ]
[ your news-reader.  If that fails, use mailto:std-c++@ncar.ucar.edu    ]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://www.research.att.com/~austern/csc/faq.html                ]