16

The idea of move semantics is that you can grab everything from another temporary object (referenced by an rvalue reference) and store that "everything" in your object. That helps to avoid deep copying where single construction of things is enough -- so you construct things in a rvalue object and then just move it to your long living object.

Why is it that C++ doesn't allow binding lvalue objects to rvalue references? Both allow me to change the referenced object, so there is no difference to me in terms of accessing internals of referenced object.

The only reason I can guess is function overloading ambiguity issues.

Niall
  • 30,036
  • 10
  • 99
  • 142
pavelkolodin
  • 2,859
  • 3
  • 31
  • 74

2 Answers2

30

But why C++ doesn't allow binding lvalue objects to rvalue references?

Assuming you mean "Why doesn't C++ allow binding rvalue references to lvalue objects": it does. It just isn't automatic, so you have to use std::move to make it explicit.

Why? Because otherwise an innocuous function call can surprisingly destroy something you didn't expect it to:

Class object(much,state,many,members,wow);
looks_safe_to_me(object);
// oh no, it destructively copied my object!

vs.

Class object(much,state,many,members,wow);
obviously_destructive(std::move(object));
// same result, but now the destruction is explicit and expected

A note on destructive copying: why I say destructively and destruction above, I don't mean the object destructor ends its lifetime: just that its internal state has been moved to a new instance. It's still a valid object, but no longer holds the same expensive state it used to.


A note on terminology: let's see if we can clear up the imprecise use of lvalue, rvalue etc. above.

Quoting from cppreference for posterity:

  • an lvalue is

    an expression that has identity and cannot be moved from.

    So, there's no such thing as an lvalue object, but there is an object which is locally named (or referred to) by an lvalue expression

  • an rvalue is

    an expression that is either a prvalue or an xvalue. It can be moved from. It may or may not have identity.

    • a prvalue (pure rvalue) is roughly an expression referring to an un-named temporary object: we can't convert our lvalue expression to one of these IIUC.

    • an xvalue (expiring value) is

      an expression that has identity and can be moved from.

      which explicitly includes the result of std::move

So what actually happens:

  • an object exists
  • the object is identified locally by an lvalue expression, which cannot be moved from (to protect us from unexpected side-effects)
  • std::move yields an xvalue expression (which can be moved from) referring to the same object as the lvalue expression
  • this means objects such as variables (which are named by lvalue expressions) cannot be implicitly moved from, and must instead explicitly moved from via an explicit xvalue expression such as std::move.
  • anonymous temporaries are probably already referred to by prvalue expressions, and can be moved implicitly
Useless
  • 64,155
  • 6
  • 88
  • 132
  • 2
    When you use `std::move`, you're not binding an rvalue reference to an lvalue. You're casting the lvalue to rvalue, then binding the rvalue reference to that. – Jerry Coffin Feb 26 '16 at 14:06
  • Isn't the intermediate an xvalue expression? Either way, the end result is an rvalue reference aliasing an object we previously identified as an lvalue ... – Useless Feb 26 '16 at 14:13
  • 2
    @Useless But objects don't have value categories, expressions do. The expression `object` is an lvalue, the expression `std::move(object)` in an rvalue. – TartanLlama Feb 26 '16 at 14:15
  • Good point: I need to work on the vocabulary to discuss this clearly. – Useless Feb 26 '16 at 14:45
  • 1
    I wish I could upvote this twice :-) - Most straightforward and shortest explanation I've ever seen pointing out the difference between lvalue, prvalues, xvalues (glvalues would make it complete). I mean your comments, not the quotes of course. – andreee May 17 '19 at 14:56
4

Essentially, a mechanism is needed to distinguish between values that can be moved from, and those that cannot be moved from (i.e. a copy would be needed).

Allowing both rvalues and lvalues to be bound to an lvalue reference makes that impossible.

Hence, values bound to an rvalue reference can be moved from (not necessarily always going to be moved from, but it is allowed), and lvalues can be bound to lvalue references and can't be moved from.

std::move is there to allow for the casting between the value categories (to an rvalue) to allow the move to happen.

Note; const lvalue references (const T&) can be bound to both rvalues (temporaries) and lvalues since the referred to object can't change (it is marked as const so there can't be any moving from anyway).

There is some history (back to the early days of C++) to why temporary objects could not be bound to non-const lvalue references to begin with... the detail is blurry but it there was some reasoning that modifying a temporary didn't make sense, since it would destruct at the end of the current statement anyway. Additionally you could be lulled into the sense that you were modifying an lvalue, when you where in fact not - the semantics of the code could/would be wrong and be buggy. There are further reasons tied to addresses, literals etc. This was before moving and the semantics thereof solidified, and is some of the motivation for moving and its semantics.

Community
  • 1
  • 1
Niall
  • 30,036
  • 10
  • 99
  • 142
  • My overall impression is that while trying to fix something C++ committee just created a bigger mess. Prior or `T&&` story it was possible to use references to modify passed objects (it is not important what sort of modification happens). This means that in every situation where a non const reference is passed, it should be checked what happens with the passed entity. They actually try to build notion that `T&` should not be modified (i.e. its content should not be taken). The notion is poor. `const T&` should be used for that. – Kirill Kobelev Jul 19 '16 at 00:12
  • The net effect of their complex rules will be opposite. A lot of buggy code will be written because many programmers instead of trying to understand minute details of the language design will write `something` `somehow`. Yes, sometimes their code will work. In other cases - as usual. – Kirill Kobelev Jul 19 '16 at 00:16