Why doesn't C++ move construct rvalue references by default? [duplicate]
Asked Answered
M

1

13

Say I have the following function

void doWork(Widget && param)  // param is an LVALUE of RRef type
{
    Widget store = std::move(param); 
}

Why do I need to cast param back to an rvalue with std::move()? Shouldn't it be obvious that the type of param is rvalue since it was declared in the function signature as an rvalue reference? Shouldn't the move constructor be automatically invoked here on this principle alone?

Why doesn't this happen by default?

Malaysia answered 23/8, 2017 at 15:33 Comment(0)
M
25

with your design:

void doWork(Widget && param)
{
    Widget store1 = param;     // automatically move param
    Widget store2 = param;     // boom

    Widget store_last = param; // boom    
}

with current design:

void doWork(Widget && param)
{
    Widget store1 = param;                // ok, copy
    Widget store2 = param;                // ok, copy

    Widget store_last = std::move(param); // ok, param is moved at its last use
}

So the moral here is that even if you have an rvalue reference you have a name for it which means you can use it multiple times. As such you can't automatically move it because you could need it for a later use.


Now let's say you want to re-design the language so that the last use is automatically treated as an rvalue.

This can be easily done in the above example:

void doWork(Widget && param)
{
    Widget store1 = param;     // `param` treated as lvalue here, copy
    Widget store2 = param;     // `param` treated as lvalue here, copy

    Widget store_last = param; // `param` treated as rvalue here, move    
}

Let's ignore the inconsistency of how param is treated (which in itself is a problem).

Now think what use of param is the last use:

void doWork(Widget && param)
{  
    Widget store2 = param;        // this can be last use or not

    while (some_condition())
    {
         Widget store1 = param;   // this can be both last use and not
    }
}

The language simply cannot be designed this way.

Maxim answered 23/8, 2017 at 15:36 Comment(11)
Oh, I see. It does make sense! I agree, the copy design is always safer than implicit move!Malaysia
@Malaysia that moment when a simple example makes you go "Oh, I see. It does make sense!" Glad I could provide one.Maxim
Id really struggle to find some actual code that does this however, and even more so one a compiler couldnt detect and treat as an error. Was there any discussion for the proposal about the relative trade offs, or is there a situation that is far more likely to occur?Kim
yeah, thanks ) I thought: its so stupid because people often forget to cast the lvalue Rref back to rvalue with std::move and if its a special rvalue-reference type, isn't it just enough by itself to be move-constructible from it?(regardless of its l-valueness). But its obvious now that this reversed design is awkward)Malaysia
@FireLancer I don't know about any discussion. I think the compiler should be able to detect a last use without a problem. However I see a problem with this: scenario: use5 in your function is the last, so the compiler safely issues a move instead of a copy. You then at a later time add another use of it, use6 which it's now the last use. The compiler will silently modify use5 to be a copy, without you even remotely touching anything on that part of code.Maxim
@bolov, I was thinking more make the compiler just throw an error on any potential "use after move" operations. And then instead have like a std::explicit_copy as like the opposite of std::move for the 0.1% of cases you "didnt want to move it just yet".Kim
Since basically, I see in actual code people forget to put the std::move and get a copy they didnt want far more often, than multiple uses.Kim
@FireLancer I see now. Make most used scenario easier. Yeah, I think that could have worked.Maxim
@Fire: "And then instead have like a std::explicit_copy as like the opposite of std::move for the 0.1% of cases you "didnt want to move it just yet"." That would break backwards compatibility to C++03.Demeter
@Demeter Except C++ had no && at all. Or is that the "better example" case using say a template<class T> void foo(T arg) or such that C++03 did have?Kim
@FireLancer Widget&& arg means that arg binds to rvalues, not that it is an rvalueBitterweed

© 2022 - 2024 — McMap. All rights reserved.