8

If I write the following code:

#include <iostream>

using namespace std;

int main()
{
  cout << &(int &&)123 << endl;
  return 0;
}

Then g++ complains:

foo.cc: In function ‘int main()’:
foo.cc:7:20: error: taking address of xvalue (rvalue reference)

Ok, thanks to What are rvalues, lvalues, xvalues, glvalues, and prvalues? I get that an xvalue means that it's about to "expire", which makes sense. But now if I do this:

#include <iostream>

using namespace std;

int main()
{
  const int &x = (int &&)123;
  cout << &x << endl;
  return 0;
}

This "works" just fine and will print an address. So, I have a few questions:

  1. If the value is about to expire, why can I make a reference to it? The reference won't keep the original object alive (right?).
  2. Does such a reference result in undefined behavior? E.g. because we're referencing an object that may have been destroyed?

In general is there a way to know the lifetime of an rvalue reference?

Community
  • 1
  • 1
FatalError
  • 52,695
  • 14
  • 99
  • 116
  • 3
    Binding to a const reference *will* keep the value alive. – Bo Persson Feb 27 '12 at 16:07
  • Yes. you can only have a const ref and a ordinary ref. I think this is allowed for backward compatibility with c++03 where you could bind the const ref to a object returned by value. – balki Feb 27 '12 at 18:35
  • @BoP I think you have it wrong. – Johannes Schaub - litb Feb 27 '12 at 22:26
  • @Johannes - In that case, I'm not sure. Otherwise I would be. :-) – Bo Persson Feb 28 '12 at 07:44
  • Literals, except for string literals, are prvalues, not xvalues. String literals are lvalues. – ThomasMcLeod Feb 02 '15 at 23:06
  • Perhaps 'xvalue' isn't the right term to use in the question. For example, the result of `std::move` will *not* have its lifetime extended, but apparently ["std::move obtains an rvalue reference to its argument and converts it to an ***xvalue***. "](http://en.cppreference.com/w/cpp/utility/move). Are you asking about all xvalues, or just prvalues directly casted to `&&`? – Aaron McDaid Feb 02 '15 at 23:51
  • 1
    @AaronMcDaid, aren't literals already prvalues? So casting to `&&` should be a no-op, shouldn't it? – ThomasMcLeod Feb 03 '15 at 00:26

2 Answers2

10

Perhaps I'm alone but I think that this is not safe:

const int &x = (int &&)123;

The lifetime-lengthening rules apply only when the reference is initialized by an expression that directly refers to a temporary object. In other words, the expression has to carry the "temporary" attribute. If you omit the (int&&), then for binding to the reference, the compiler will implicitly create a prvalue expression that refers to a temporary object which is initialized by the value 123 and lifetime lengthening then applies.

But if you mix rvalue references in between, the compiler has no way to know at compile time whether or not to lengthen the lifetime of the referred-to object.

int a = 0;
const int &x = ((rand() == 42) ? (int&&)123 : (int&&)a);

I therefor think that your code has undefined behavior, because you evaluate a dangling reference (even if only for taking its address).

ThomasMcLeod
  • 7,603
  • 4
  • 42
  • 80
Johannes Schaub - litb
  • 496,577
  • 130
  • 894
  • 1,212
  • Interesting -- I tried the code snipped you suggested in g++ and it had no complaints. Can the compiler just decide to extend the lifetime of both? Or is there a requirement that the one is destroyed after evaluation? – FatalError Feb 28 '12 at 14:57
  • 1
    @fatal the compiler is not required to complain on undefined behavior. if you try your code with a class that has a destructuor you will observe that gcc destroys the temporary early . – Johannes Schaub - litb Feb 28 '12 at 19:56
  • 2
    Wouldn't the compiler have the same difficulty with the apparently legal `const int &x = ((rand() == 42) ? 123 : a);`? – Dan Nissenbaum Dec 19 '12 at 07:32
  • @DanNissenbaum That case is fundamentally different. With the ternary operator, if 2nd and 3rd operand have the same type and value category then the result has that same type and value category. In the original example , both are xvalues so the ternary operator evaluates to an xvalue. But in your case the operands are different category (`123` is a prvalue, `a` is an lvalue). In the case of built-in types, the result is a prvalue, so a temporary (of type `const int`) is always created from this prvalue and bound to `x`. – M.M Feb 02 '15 at 23:18
  • Is there any difference between `(int &&) 123` and `std::move(123)`? In the latter case, the `123` is clearly going to be discarded quite soon. – Aaron McDaid Feb 02 '15 at 23:38
  • 1
    Well, I did try this on a `struct R` with a destructor and got interesting results. `R const & r = (R&&) R();` compiles, runs and the temporary's destructor is not called until `r` goes out of scope. However, in the case of `R const & r = std::move(R());` the destructor is called at the end of the statement. This in g++ 4.9. – ThomasMcLeod Feb 03 '15 at 00:10
2

Clause 12.2, paragraphs 4-5, says that the lifetime is extended in the second example

There are two contexts in which temporaries are destroyed at a different point than the end of the full-expression. ...

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 except:
(none of the exceptions apply here)

Community
  • 1
  • 1
Bo Persson
  • 90,663
  • 31
  • 146
  • 203
  • 1
    I don't think "when a reference is bound to a temporary" is supposed to cover binding a reference to an xvalue which is bound to a temporary – M.M Feb 02 '15 at 23:25