Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

prolonging the lifetime of temporaries

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)?

like image 751
fredoverflow Avatar asked Jan 12 '11 14:01

fredoverflow


3 Answers

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.

like image 143
Matthieu M. Avatar answered Oct 21 '22 19:10

Matthieu M.


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.

like image 21
Francesco Avatar answered Oct 21 '22 18:10

Francesco


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.

like image 31
Ben Voigt Avatar answered Oct 21 '22 20:10

Ben Voigt