18

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

fredoverflow
  • 256,549
  • 94
  • 388
  • 662
  • Isn't this the same question discussed by Herb Sutter here http://herbsutter.com/2008/01/01/gotw-88-a-candidate-for-the-most-important-const/ ? – DumbCoder Jan 12 '11 at 15:31
  • @DumbCoder: no, Herb Sutter design the uses wrt to the C++ standard while Fred discusses the rationale behind the standard. – Matthieu M. Jan 12 '11 at 15:47

4 Answers4

6

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.

Matthieu M.
  • 287,565
  • 48
  • 449
  • 722
  • "This is obviously wrong, and indeed the compiler will dutifully report it" if you're lucky, if you have a toolchain that does this, if your warnings are set to a certain level, etc etc. The C++ language mandates no diagnostic for this case. – Lightness Races in Orbit Jan 14 '11 at 10:54
  • @Tomalak: I'll correct, it's reported by at least MSVC, gcc and Clang, and I think that Comeau and icc would probably too. – Matthieu M. Jan 14 '11 at 12:47
5

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.

Ben Voigt
  • 277,958
  • 43
  • 419
  • 720
  • The problem that this code exhibits is that rvalues shouldn't bind to normal references. It doesn't demonstrate why rvalues should bind to const references instead. Loving the dude, where's my object :) – Puppy Jan 12 '11 at 15:11
  • 1
    @David: `const Foo*&` is not a const reference. Did you mean `Foo* const&`? – Ben Voigt Jan 12 '11 at 16:38
  • My bad! I should be more careful... I had the intuition and tested, but I performed the wrong test. You are right. I have removed the comment where I made a fool of myself :) +1 – David Rodríguez - dribeas Jan 12 '11 at 22:16
  • 1
    @JeskoHüttenhain It is a reference to a pointer, not a pointer to a reference. A reference to a pointer might be expected to be used to reseat a pointer and replace it with an allocated object. – Chris Drew May 04 '17 at 11:56
  • Oh I see. I need to get better at reading C++ types. Well, then yea, this is actually a great example. – Jesko Hüttenhain May 04 '17 at 16:52
4

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.

Francesco
  • 3,200
  • 1
  • 34
  • 46
0

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

CashCow
  • 30,981
  • 5
  • 61
  • 92
  • Why would I lose the changes? Look at the title of my question, the temporary would live as long as `a`, just as it does in the `const Foo&` case. – fredoverflow Jan 12 '11 at 15:09