Search code examples
c++c++11move-semanticsrvalue-reference

Why C++ lvalue objects can't be bound to rvalue references (&&)?


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.


Solution

  • 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