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.
An lvalue reference can bind to an lvalue, but not to an rvalue.
“l-value” refers to a memory location that identifies an object. “r-value” refers to the data value that is stored at some address in memory. References in C++ are nothing but the alternative to the already existing variable. They are declared using the '&' before the name of the variable.
An rvalue reference is formed by placing an && after some type. An rvalue reference behaves just like an lvalue reference except that it can bind to a temporary (an rvalue), whereas you can not bind a (non const) lvalue reference to an rvalue.
An lvalue (locator value) represents an object that occupies some identifiable location in memory (i.e. has an address). rvalues are defined by exclusion. Every expression is either an lvalue or an rvalue, so, an rvalue is an expression that does not represent an object occupying some identifiable location in memory.
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:
std::move
yields an xvalue expression (which can be moved from) referring to the same object as the lvalue expressionstd::move
.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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With