Boost logo

Boost :

Subject: Re: [boost] [operators] The Dangling Reference Polarization
From: Steven Watanabe (watanabesj_at_[hidden])
Date: 2013-04-25 18:01:12


AMDG

On 04/25/2013 01:40 PM, Daniel Frey wrote:
> <snip>
>
> c) It rely's on the fact that it extends the lifetime of a returned temporary value. The rules of the C++ standard that allow this are quite clear, and hence it's the users responsibility to make sure that there actually is a temporary value whose lifetime can be extended. It has always been an error if the expression returned a reference and not a value. Consider
>
> const std::string& oops = std::vector<std::string>(1, "hello, world" ).front();
>
> This compiles are creates a dangling reference, as the standard says that front() returns a reference and not a value. It's not 100% comparable as in this case the standard guarantees a bug, but the main point for C++ has always been which guarantees are given and not about a certain expectation one might have.

The designer of a component should try to make sure that
the guarantees provided match user expectations. Anything
that's clever, but has subtly different semantics is
likely to be a source of bugs.

The problem I have with returning a reference in this case,
is that the result is /logically/ a value. Therefore, it's
reasonable for users to expect that the result is a real
value (and in fact, users could safely rely on this in C++03,
because there were no rvalue references). The fact that
the result is a real value some of the time, depending
on the details of the arguments, makes it even worse.

Also, if Boost.Operators can return an rvalue reference,
then retrofitting Boost.Operators into an existing class
is a breaking change, because code that was valid is no
longer so.

> As an example:
>
> static_assert( std::is_empty< std::tuple<> >::value, "Expectation 1 broken" );
> static_assert( std::is_empty< std::array< T, 0 > >::value, "Expectation 2 broken" ); // with any valid T
>
> The first case is certainly a reasonable expectation and it currently works for all implementations of std::tuple that I know of, but it is not guaranteed! If your code relies on it and it breaks in the future, you can not blame the person who modified std::tuple. The second case is also a reasonable expectation, but it breaks everywhere and AFAIK there is no standard-conforming way to implement std::array so that std::array<T,0> is empty. For the curious, here's the StackOverflow question I asked about it: <http://stackoverflow.com/q/15512827/2073257>. Another example about how to properly handle expectations vs. guarantees: <http://stackoverflow.com/q/14882588/2073257>.
>
> d) I fail to see any valid use-case for binding the result of the expression to a reference. Can someone please provide a convincing example of why (P1) or (P2) are needed/useful? Keep in mind that in the context of operator+ (or any other operator in question), we already require that the type T is copy-/moveable and that copy-elision most likely takes place if you use "T r = …" instead of "const T& r = …".
>

It's really not a question of whether binding a reference
is actually better. It's supported by the language, and
you can't detect the error at compile-time.

> e) The alternative needs to prove its claimed/theoretical efficiency in practice. Too often I have heard about "sufficiently smart compilers", but I'm more the prove-it-in-the-real-world type.
>

Um... I think that argument goes the other way.
As the person introducing an optimization, it's
your responsibility to show that it actually helps.

In Christ,
Steven Watanabe


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