16
votes

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?

3

3 Answers

14
votes

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.

12
votes

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

5
votes

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