What is the design rationale behind allowing this
const Foo& a = function_returning_Foo_by_value();
but not this
Foo& a = function_returning_Foo_by_value();
?
What could possible go wrong in the second line (which would not already go wrong in the first line)?
I'll answer your question... the other way around.
Why did they allowed Foo const& foo = fooByValue();
to begin with ?
It makes life (somewhat) easier, but introduces potential undefined behavior all over the place.
Foo const& fooByReference()
{
return fooByValue(); // error: returning a reference to a temporary
}
This is obviously wrong, and indeed the compiler will dutifully report it. As per Tomalak's comment: it is not mandated by the standard, but good compilers should report it. Clang, gcc and MSVC do. I think that Comeau and icc would too.
Foo const& fooByIndirectReference()
{
Foo const& foo = fooByValue(); // OK, you're allowed to bind a temporary
return foo; // Generally accepted
}
This is wrong, but is more subtle. The problem is that the lifetime of the temporary is bound to the lifetime of foo
, which goes out of scope at the end of the function. A copy of foo
is passed to the caller, and this copy points into the ether.
I raised the bug on Clang, and Argyris was able to diagnose this case (kudos really :p).
Foo const& fooForwarder(Foo const&); // out of line implementation which forwards
// the argument
Foo const& fooByVeryIndirectReference()
{
return fooForwarder(fooByValue());
}
The temporary created by fooByValue
is bound to the lifetime of the argument of fooForwarder
, which dutifully provide a copy (of the reference), copy that is returned to the caller, even though it now points into the ether.
The issue here is that fooForwarder
's implementation is perfectly fine wrt the standard, and yet it creates undefined behavior in its caller.
The daunting fact though, is that diagnosing this requires knowing about the implementation of fooForwarder
, which is out of reach for the compiler.
The only solution I can fathom (apart from WPA) is a runtime solution: whenever a temporary is bounded to a reference, then you need to make sure that the returned reference does not share the same address... and then what ? assert
? raise an exception ? And since it's only a runtime solution, it is clearly not satisfactory.
The idea of binding a temporary to a reference is brittle.
I have understood the rationale as follows: a temporary is expected to be destroyed when it goes out of scope. If you promise not to modify it I will let you prolong its lifetime.
The reason that non-const pointers don't prolong the lifetime of temporaries is that non-const references can't be bound to temporaries in the first place.
There are LOTS of reasons for that, I'll just show one classic example involving implicit widening conversions:
struct Foo {};
bool CreateFoo( Foo*& result ) { result = new Foo(); return true; }
struct SpecialFoo : Foo {};
SpecialFoo* p;
if (CreateFoo(p)) { /* DUDE, WHERE'S MY OBJECT! */ }
The rationale for allowing const references to bind temporaries is that it enables perfectly reasonable code like this:
bool validate_the_cat(const string&);
string thing[3];
validate_the_cat(thing[1] + thing[2]);
Note that no lifetime extension was needed in this case.
"What could possibly go wrong" is that you modify an object then instantly lose the changes, and the rule is therefore defined to help you not make such mistakes. You might think if you called the function again you would get an object with your changes, which of course you wouldn't because you modified a copy.
The typical case where you create a temporary then call a non-const method on it is when you are going to swap it:
std::string val;
some_func_that_returns_a_string().swap( val );
This can sometimes be very useful.