16

Once upon a time, I assumed that code like this would fail:

const MyClass& obj = MyClass();
obj.DoSomething();

because the MyClass object would be destroyed at the end of its full-expression, leaving obj as a dangling reference. However, I learned (here) that this isn't true; the standard actually has a special provision that allows const references to keep temporaries alive until said references are destroyed themselves. But, it was emphasized, only const references have this power. Today I ran the code below in VS2012 as an experiment.

struct Foo
{
    Foo() { std::cout << "ctor" << std::endl; }
    ~Foo() { std::cout << "dtor" << std::endl; }
};

void f()
{
    Foo& f = Foo();
    std::cout << "Hello world" << std::endl;
}

The output when calling f() was:

ctor  
Hello world  
dtor  

So I had a look at the C++11 draft standard, and only found this (§ 12.2/4):

There are two contexts in which temporaries are destroyed at a different point than the end of the full-expression. The first context [doesn't apply]. The second context is when a reference is bound to a temporary. The temporary to which the reference is bound or the temporary that is the complete object of a subobject to which the reference is bound persists for the lifetime of the reference.

The word const is conspicuously absent from the above. So; has this behavior been changed for C++11, was I wrong about the const thing to begin with, or does VS2012 have a bug and I just haven't found the relevant part of the standard?

dlf
  • 9,045
  • 4
  • 32
  • 58

3 Answers3

14

The behavior hasn't changed, you just need to turn your warning level up to /W4. VisualStudio implements the lifetime extension rule even for non-const lvalue references as a compiler extension. In this context, binding an rvalue to the non-const reference behaves the same as if you were binding it to a const reference.

With /W4 you'd see this:

warning C4239: nonstandard extension used : 'initializing' : conversion from 'Foo' to 'Foo &'
1>  A non-const reference may only be bound to an lvalue

The text disallowing binding of an rvalue to a non-const lvalue reference can be found in §8.5.3/5

— Otherwise, the reference shall be an lvalue reference to a non-volatile const type (i.e., cv1 shall be const), or the reference shall be an rvalue reference.
[ Example:

  double& rd2 = 2.0; // error: not an lvalue and reference not const
  int i = 2;
  double& rd3 = i; // error: type mismatch and reference not const

—end example ]

The second half of the quoted statement is what allows binding of a temporary to an rvalue reference, as shown in litb's answer.

string &&s = string("hello");

This, combined with the lifetime extension rule in §12.2/5, means the lifetime of the temporary will now match the lifetime of the (rvalue) reference it is bound to.

Praetorian
  • 106,671
  • 19
  • 240
  • 328
  • 2
    The fact that you're allowed to bind an rvalue to a non-const lvalue reference is itself an extension. – Brian Bi May 15 '14 at 18:04
  • 2
    @Brian Isn't that what I said? – Praetorian May 15 '14 at 18:07
  • @Praetorian Indeed I do. So the "only const references extend lifetime" rule is a side-effect of some other rule stating that only const references can be bound to rvalues? – dlf May 15 '14 at 18:10
  • @dlf Finally found the text you were looking for, see updated answer – Praetorian May 15 '14 at 18:22
  • @Praetorian Great; I had been hunting too without success. I'll accept this answer. – dlf May 15 '14 at 18:25
  • 3
    @dlf There's never been a rule that "only const references extend lifetime". Long before the rule banning initialization of a non-const reference with a temporary, the extension of the lifetime applied to all references. – James Kanze May 15 '14 at 18:45
  • Given that this is a C++11 question, a mention of rvalue reference lifetime extension (for a standards-compliant non-`const` reference extension) might be a good idea. – Yakk - Adam Nevraumont May 15 '14 at 19:41
  • @Yakk The quote does contain *or the reference shall be an rvalue reference*. Anyway, I've made it more explicit. – Praetorian May 15 '14 at 19:53
13

The word const was never present in this section. The rule has always been (from as long as I can remember) that a temporary used to initialize a reference has its lifetime extended to match that of the reference, regardless of the type of the reference.

Sometime in the late 1980's (very pre-standard), C++ introduced the rule that a temporary could not be used to initialize a non-const reference. Initializing a non-const reference with a temporary would still extend the lifetime (presumably), but since you couldn't do it... Most compilers implemented a transition period, in which such an initialization would only emit a warning (and the lifetime was extended).

For some reason, when Microsoft finally decided to implement C++ (some time in the early 1990's), they decided not to implement the new rule, and allowed initialization of a non-const reference with a temporary (without even a warning, at a time when most other vendors were gradually turning the warning into an error). And of course, the implemented the usual lifetime rule.

Finally, in C++11, new types of references were introduced, which allowed (or even required) initialization with a temporary. The rule about the lifetime of temporaries hasn't changed, though; a temporary which is used to initialize a reference (regardless of the type of reference) has its lifetime extended.

(With a few exceptions: I would not recommend using a temporary to initialize a class member reference in an initialization list.)

James Kanze
  • 150,581
  • 18
  • 184
  • 329
  • Could you elaborate on that last paragraph regarding member references? It never occurred to me to do this, anyway, but I'd be interested in your exact reasoning. – Christian Hackl May 16 '14 at 17:26
  • 2
    @ChristianHackl The lifetime of a temporary used to initialize a member reference is _not_ extended to the lifetime of the reference; its lifetime ends at the end of the constructor (which results in a dangling reference). – James Kanze May 19 '14 at 08:08
  • Thanks for the clarification. I would have guessed so intuitively, but it's nice to have it confirmed. Still makes me wonder why such a thing does not require a compiler diagnostic in the first place. – Christian Hackl May 25 '14 at 13:55
5

No, because rvalue references don't need to be const, so the Standard quote is correct

string &&s = string("hello");

The lifetime is still enlargened. The constraints that make the code invalid for non-const lvalue reference is at clause 8 (notice that it is not the right place to just add "const" and "rvalue reference" etc in the paragraph you quoted. You need an active rejection of such bindings, not just saying that the lifetime of such bindings are not enlarged because you would leave the binding itself still wellformed).

Johannes Schaub - litb
  • 496,577
  • 130
  • 894
  • 1,212
  • The question really asks *three* yes-no questions chained together. It's unclear to me which one you're answering *no* to. Or are you giving the same answer to all three? – Rob Kennedy May 15 '14 at 18:16