Search code examples
c++c++11movervalue-referencemove-constructor

Rvalue references without std::move


I have following class

class widget {
// The methods only print their name, i.e. c'tor, destructor etc.
public:
    widget();
    widget(const widget&);
    widget(widget&&);
    ~widget();
    auto operator=(const widget&)  -> widget&;
    auto operator=(widget&&) -> widget&;
};

which I use in following code

#include "widget.h"

auto main() -> int {

    widget c(std::move(widget()));
    c = std::move(widget());

    return 0;
};

The resulting behavior is comprehensible to me. In the first call a widget is constructed, then the move constructor is invoked and the destructor is called on the temporary widget.

The second call does the same, expect for calling the move assignment operator instead of the move constructor. Leaving the main method, the destructor is invoked on c.


Now comes the interesting part:

#include "widget.h"

auto main() -> int {

    widget c((widget()));
    c = widget();

    return 0;
};

If I leave out the call to std::move, the first case stops working and results in just one constructor call. Whereas the second case is still working as before.

What am I missing here? Why are those two function calls treating their parameters differently? I tried this on gcc and clang.


Solution

  • widget() is a pure rvalue (prvalue), so in the line

    widget c((widget())); // use widget c{widget()} or c{widget{}} for more clear code
    

    it will be moved. However, the compiler just performs copy/move elision. Compile with -fno-elide-constructors and you'll see the calls to the move constructors in all their glory.

    Whenever you explicitly use std::move to move a prvalue, you're not allowing the compiler to perform the elision; that's why you see the move constructor in action in the first snippet. That's why it's almost always a bad idea to try to "help" the compiler by using a std::move as the return (unless you really want to return a rvalue reference).