1

In my mind it doesn't make sense to be able to convert an r-value reference to an l-value reference giving one the ability to ultimately access a temporary as an l-value. For example:

struct S
{
  int val=5;
};

void foo(S &&s)
{
  S &s2=s;

  s2.val=7
}

void foo2(S &s2)
{
  s2.val=7
}

int main()
{
  foo(S());  // This is fine
  foo2(S()); // Error: Even though this is logically equivalent to foo()
}

The call to foo() seems to be valid in C++ or at least it compiles with gcc 4.7.1. The call to foo2() is not, even though the two functions seem logically equivalent. What gives?

Michael Goldshteyn
  • 71,784
  • 24
  • 131
  • 181
  • 1
    You're not converting anything. You're binding the value of the expression `s` to the reference variable `s2`. Nothing wrong with that. – Kerrek SB Oct 22 '13 at 15:12
  • [Read all about it](http://stackoverflow.com/a/5591006/2089675) – smac89 Oct 22 '13 at 15:16
  • 1
    You can directly do `s.val=7` inside `foo()`. So there's no real difference between `&` and `&&`, they can both be used in the same way. In fact, it's quite difficult (by design) to tell any difference between them. The most important difference is that when `&&` is part of a function parameter's type, it allows it to bind (as a reference) to a temporary. – Aaron McDaid Oct 22 '13 at 15:17
  • What about the ability to change member variables and call non-const member functions on s in foo(). Isn't that a function of whether the passed in parameter is a (non-const) l-value or an r-value? – Michael Goldshteyn Oct 22 '13 at 15:19
  • @MichaelGoldshteyn, I guess we're now being encouraged, in some circumstances, to modify data that has been passed in by reference. The `&&` in the function header is the function's way of saying "Warning: I might modify any parameter that you pass to me. Use me at your own risk." Then the compiler knows to be careful, and to only pass data to this (non-const) reference if it's sure that it doesn't matter, e.g. it's a temporary that's going to die anyway. – Aaron McDaid Oct 22 '13 at 15:22
  • ... A related question is: "Instead of introducing `&&`, why didn't the committee simply relax the rules and allow (non-const) `&` to bind to temporaries?" What maybe would ensue if that was the case? (I guess copy constructors would be confused, should they move or copy, that would be the dilemma.) – Aaron McDaid Oct 22 '13 at 15:27
  • @AaronMcDaid The entire language would be completely broken and nothing would work anymore. – Sebastian Redl Oct 22 '13 at 15:41
  • @SebastianRedl, I agree! But I think the questioner is interested in the design philosophy of the language, why are the rules about binding to r-values the way they are? – Aaron McDaid Oct 22 '13 at 15:46
  • The title says "cast", but there are no casts in the given code. – GManNickG Oct 22 '13 at 16:07
  • @GMan, changed ```cast``` to ```conversion```, to be more correct. – Michael Goldshteyn Oct 29 '13 at 20:06

1 Answers1

4

s is already an lvalue, like any other named variable. So having an lvalue reference to it doesn't really change anything.

The key conceptual difference between an rvalue and an lvalue is that you can only access an rvalue once, which makes it safe to do weird things to it. But if that one access is binding it to an rvalue reference, you've just created a way to access it multiple times, i.e. you turned it into an lvalue. So you can't implicitly bind it to another rvalue reference, whereas you can bind it to an lvalue reference.

Edit:

The two things are not logically equivalent. In one, you bind an rvalue to an lvalue reference. That's not allowed, because it's misleading. (Not quite as dangerous as allowing an lvalue to bind to an rvalue reference, but still misleading.) foo2, by taking an lvalue reference, announces that it will probably modify the object passed and that this is absolutely a central part of the function's operation. Passing a temporary to such a function doesn't make sense, because you couldn't observe the modification.

In the other, you bind an rvalue to an rvalue reference. foo, by taking an rvalue reference, announces that it might modify the object passed, but that this is a performance optimization and is not meant to be observed by anyone outside. Passing a temporary makes sense, but passing a non-temporary would be very dangerous, which is why you would have to explicitly std::move the argument in such a case.

Then, in foo, you bind the lvalue (which happens to be an expression referring to a variable of rvalue reference type, but that's irrelevant) to an lvalue expression. This makes sense: you've got your rvalue reference variable s which points to something which, by virtue of having been passed to your function, has become your plaything. So if you want to pass it to something that modifies its state (such as foo2) and then observe the changes (which you can, through s or s2 both), that's conceptually perfectly sound.

Basically, being something that can be observed just once or multiple times is a matter of scope. And that's why the "same" thing (it's not really the same thing, but you think of it that way) can be bound to an rvalue reference in one context, and to an lvalue reference in another.

Sebastian Redl
  • 69,373
  • 8
  • 123
  • 157
  • I updated my question to be more specific about why I was confused. – Michael Goldshteyn Oct 22 '13 at 15:22
  • The rule is that non-const `&` are not allowed to bind to temporaries. Maybe you're asking "why was the language designed with this rule? Why not allow non-const `&` to bind to temporaries?" – Aaron McDaid Oct 22 '13 at 15:41