35

Consider the below.

#include <string>
using std::string;

string middle_name () {
    return "Jaan";
}

int main ()
{
    string&& danger = middle_name();   // ?!
    return 0;
}

This doesn't compute anything, but it compiles without error and demonstrates something that I find confusing: danger is a dangling reference, isn't it?

Andres Jaan Tack
  • 22,566
  • 11
  • 59
  • 78
  • 2
    puzzles me that you can use && on the left side at all, when would that be useful? – Viktor Sehr Sep 15 '10 at 09:31
  • 2
    @Viktor: An rvalue reference can bind to a temporary and still be modifiable. Eg `int &&variable_or_dummy = modify_var? move(var) : int();` – Potatoswatter Sep 15 '10 at 09:50
  • 1
    One you name an rvalue it becomes an lvalue so in main `danger` is an lvalue. rvalueness or lvalueness is a property of an expression – Peter McG Oct 06 '10 at 04:37

4 Answers4

46

Do rvalue references allow dangling references?

If you meant "Is it possible to create dangling rvalue references" then the answer is yes. Your example, however,

string middle_name () {
    return "Jaan";
}

int main()
{
    string&& nodanger = middle_name();   // OK.
    // The life-time of the temporary is extended
    // to the life-time of the reference.
    return 0;
}

is perfectly fine. The same rule applies here that makes this example (article by Herb Sutter) safe as well. If you initialize a reference with a pure rvalue, the life-time of the tempoary object gets extended to the life-time of the reference. You can still produce dangling references, though. For example, this is not safe anymore:

int main()
{
    string&& danger = std::move(middle_name());  // dangling reference !
    return 0;
}

Because std::move returns a string&& (which is not a pure rvalue) the rule that extends the temporary's life-time doesn't apply. Here, std::move returns a so-called xvalue. An xvalue is just an unnamed rvalue reference. As such it could refer to anything and it is basically impossible to guess what a returned reference refers to without looking at the function's implementation.

sellibitze
  • 27,611
  • 3
  • 75
  • 95
  • 5
    Excellent example of well-intentioned `move`-ing gone awry! – Potatoswatter Sep 15 '10 at 09:46
  • @sellibitze would `string& danger = std::move(middle_name());` be ok? – KitsuneYMG Sep 15 '10 at 11:22
  • 1
    @kts: No. It wouldn't even compile because you can't initialize a non-const lvalue reference with an rvalue expression. If you write `string const& danger = move(middle_name());` it won't work either. It'll compile but `danger` will be a dangling reference. – sellibitze Sep 15 '10 at 12:05
  • 3
    Is there an intuition for why this requires _pure_ rvalues? The xvalue case seems perfectly reasonable... – Andres Jaan Tack Sep 15 '10 at 19:01
  • 6
    @Andres: I tried to explain this in the answer. Only when the initializer is a pure rvalue the compiler knows exactly what the to-be-initialized reference will refer to. In this case, the correct temporary's life-time can and will be extended easily. If the initializer itself is a reference you don't really know *in general* what object exactly it refers to. `std::move` doesn't mean anything to the compiler. The information that the result of std::move refers to the same object is not preserved in the function's type. – sellibitze Sep 15 '10 at 21:34
  • 3
    @sellibitze That makes sense! A referenced object's lifetime can have any kind of lifetime, where a pure rvalue's lifetime is easily predicted and thus trivially extended. – Andres Jaan Tack Sep 16 '10 at 00:12
  • 2
    See http://stackoverflow.com/questions/42441791/lifetime-extension-prvalues-and-xvalues/42441832#42441832 the value category does not determine whether the lifetime of an object will be extended or not. Whether the value on the right is an xvalue or not is not sufficient to determine whether the lifetime of the rvalue will be extended. Could you update the answer to include the actual places where object lifetimes are extended, so people are not misled? – Curious Feb 24 '17 at 15:11
17

rvalue references bind to rvalues. An rvalue is either a prvalue or an xvalue [explanation]. Binding to the former never creates a dangling reference, binding to the latter might. That's why it's generally a bad idea to choose T&& as the return type of a function. std::move is an exception to this rule.

T&  lvalue();
T   prvalue();
T&& xvalue();

T&& does_not_compile = lvalue();
T&& well_behaved = prvalue();
T&& problematic = xvalue();
Community
  • 1
  • 1
fredoverflow
  • 256,549
  • 94
  • 388
  • 662
  • +1 You're right, it's a good rule of thumb not to write functions that return rvalue references. std::move and std::forward are the obvious exceptions. – sellibitze Sep 15 '10 at 12:42
3

danger is a dangling reference, isn't it?

Not any more than if you had used a const &: danger takes ownership of the rvalue.

Konrad Rudolph
  • 530,221
  • 131
  • 937
  • 1,214
0

Of course, an rvalue reference is still a reference so it can be dangling as well. You just have to bring the compiler into a situation where he has to drag the reference along and at the same time you just escape the refered-to value's scope, like this:

Demo

#include <cstdio>
#include <tuple>

std::tuple<int&&> mytuple{ 2 };

auto pollute_stack()
{
    printf("Dumdudelei!\n");
}

int main()
{
    {
        int a = 5;
        mytuple = std::forward_as_tuple<int&&>(std::move(a));
    }
    pollute_stack();
    int b = std::get<int&&>(mytuple);
    printf("Hello b = %d!\n", b);
}

Output:

Dumdudelei!
Hello b = 0!

As you can see, b now has the wrong value. How come? We stuffed an rvalue reference to an automatic variable a into a global tuple. Then we escaped the scope of a and retrieve its value through std::get<int&&> which will evaluate to an rvalue-reference. So the new object b is actually move constructed from a, but the compiler doesn't find a because its scope has ended already. Therefore std::get<int&&> evaluates to 0 (although it is probably UB and could evaluate to anything).

Note that if we don't touch the stack, the rvalue reference will actually still find the original value of object a even after its scope has ended and will retrieve the right value (just try it and uncomment pollute_stack() and see what happens). The pollute_stack() function just moves the stack pointer forward and back while writing values to the stack by doing some io-related stuff through printf().

The compiler doesn't see through this though at all so be aware of this.

glades
  • 3,778
  • 1
  • 12
  • 34