5

From what I understand the reason why it is dangerous to return rvalues references to from functions is due to the following code:

T&& f(T&& x) { do_something_to_T(x); return static_cast<T&&>(x); }
T f(const T& x) { T x2 = x; do_something_to_T(x2); return x2; }
T&& y = f(T());

This leaves y as an undefined dangling reference.

However, I don't understand why the above code even compiles? Is there ever a legitimate reason to assign a rvalue reference to another rvalue reference? Aren't rvalues suppose to be, roughly speaking, "temporaries", i.e. going to be made invalid at the end of the expression? Being able to assign them seems silly to me.

Clinton
  • 20,364
  • 13
  • 59
  • 142
  • Eric Lippert talks about implementing a C# variant that allows ref returns in [this post](http://blogs.msdn.com/b/ericlippert/archive/2011/06/23/ref-returns-and-ref-locals.aspx), and also covers a lot of the use cases that come out of that. – Adam Mihalcin Feb 29 '12 at 05:22
  • Which compiler compiles it? o.O... Because it does not compile - you cannot initialize `T&&` with `T`. – lapk Feb 29 '12 at 05:31
  • 2
    @AzzA: Er, you can initialise an rvalue reference with an rvalue, that's like the whole point of them. – Cat Plus Plus Feb 29 '12 at 05:36
  • 1
    Also, that's usual reference binding. You have to be able to bind references, so the question seems silly. – Cat Plus Plus Feb 29 '12 at 05:37
  • @CatPlusPlus But this particular code does not compile: there is no forwarding... You need to `return std::forward(x);`. – lapk Feb 29 '12 at 05:41
  • Given `enum { foo };` then `42` and `foo` are two rvalues which involve no temporaries *at all*. – Luc Danton Feb 29 '12 at 05:44
  • I've added the `static_cast(x)` so it now compiles. – Clinton Feb 29 '12 at 05:57

1 Answers1

10

Aren't rvalues suppose to be, roughly speaking, "temporaries", i.e. going to be made invalid at the end of the expression?

No, they're not.

Given your function f, this is just as legal:

T t{};
T&& y = f(std::move(t));

This is perfectly valid C++11 code. And it's also well-defined what happens.

The only reason your code is undefined is because you pass a temporary. But r-value references don't have to be temporaries.

To elaborate a bit more, an r-value reference conceptually is a reference to a value from which certain operations are considered OK to do, which would not otherwise be OK to do.

R-value references are very carefully specified in C++11. An l-value reference can be bound to any non-temporary without the need for a cast or anything:

T t{};
T &y = t;

An r-value reference can only be implicitly bound to a temporary or other "xvalue" (an object that is most certainly going to go away in the near future):

T &&x = T{};
T &&no = t; //Fail.

In order to bind an r-value reference to a non-xvalue, you need to do an explicit cast. The way C++11 spells this cast is telling: std::move:

T &&yes = std::move(t);

The "certain operations" I was speaking of was "moving". It is OK to move from an object under exactly two conditions:

  1. It is going to go away anyway. IE: a temporary.
  2. The user has explicitly said to move from it.

And these are the only two cases where an r-value reference can bind to something.

There are exactly two reasons r-value references exist: to support move semantics and to support perfect forwarding (which required a new reference type that they could hook funky casting mechanics onto, as well as potentially move semantics). Therefore, if you're not doing one of these two operations, the reasons to use a && are dubious.

Nicol Bolas
  • 378,677
  • 53
  • 635
  • 829
  • If `f` is a legitimate function that is not supposed to work on temporaries, shouldn't its definition be: `T& f(T&)` so it throws a compile error upon receiving a temporary instead of resulting in undefined behaviour? Why not use a normal reference if it's not a temporary? You can move from normal references. – Clinton Feb 29 '12 at 05:56
  • @Clinton: Yes, you can move from an l-value reference. But then you *can't* pass a temporary. You assume that your choices are "functions that take *only* temporaries" and "functions that *can't* take temporaries." Why not allow a function to take whatever and make it work with what it's given? It's easier on both sides. – Nicol Bolas Feb 29 '12 at 06:03
  • 1
    @Clinton: Also, see [this answer for more on how to handle move/copy in parameters.](http://stackoverflow.com/questions/8114276/how-do-i-pass-a-unique-ptr-argument-to-a-constructor-or-a-function/8114913#8114913) – Nicol Bolas Feb 29 '12 at 06:05
  • @Nicol Do you say, by your last paragraph, that - except for the case of move constructor/assignment - whenever I am about to overload a function with a `T const&`- and a `T&&`-parameter version, I should instead just provide a single `T`-parameter version in which I move from the parameter? – Reizo Oct 08 '20 at 20:48
  • @Reizo: In the 8 years since this post, I've reconsidered my stand on rvalue reference parameter types. So I removed the last paragraph. – Nicol Bolas Oct 08 '20 at 20:55
  • @Nicol Yes, I was aware of the age of the post ;) But after thinking about your statement it actually was convincing to me - I just wanted to have it confirmed. So what makes you *not agree* anymore today? – Reizo Oct 08 '20 at 21:03
  • @Nicol Or would you still agree for the the specific case *I* pointed out, but found only your more general statement to be not so viable anymore? – Reizo Oct 08 '20 at 21:05