C++ std::move – Why Wrap Return of rvalue Reference Parameter with std::move()?

c++c++11c++14

I was reading Effective Modern C++ Item 25, on page 172, it has an example to demonstrate that, if you want to move return an rvalue reference parameter, you need to wrap it with std::move(param). As parameter by itself is always an lvalue, if no std::move(), it will be copy returned.

I don't understand. If std::move(param) merely cast the parameter it takes in into an rvalue reference, then what's the difference when param is already an rvalue reference?

Like in the code below:

#include <string>
#include <iostream>
#include <utility>

template<typename T>
class TD;

class Widget {
public:
    explicit Widget(const std::string& name) : name(name) {
        std::cout << "Widget created with name: " << name << ".\n";
    }

    Widget(const Widget& w) : name(w.name) {
        std::cout << "Widget " << name << " just got copied.\n";
    }

    Widget(Widget&& w) : name(std::move(w.name)) {
        std::cout << "Widget " << name << " just got moved.\n";
    }

private:
    std::string name;
};

Widget passThroughMove(Widget&& w) {
    // TD<decltype(w)> wType;
    // TD<decltype(std::move(w))> mwType;
    return std::move(w);
}

Widget passThrough(Widget&& w) {
    return w;
}

int main() {
    Widget w1("w1");
    Widget w2("w2");

    Widget wt1 = passThroughMove(std::move(w1));
    Widget wt2 = passThrough(std::move(w2));

    return 0;
}

It outputs:

Widget created with name: w1.
Widget created with name: w2.
Widget w1 just got moved.
Widget w2 just got copied.

In passThroughMove(Widget&& w), w's type is already rvalue reference, std::move(w) just cast it into rvalue reference again. If I uncomment the TD lines, I can see that of decltype(w) and decltype(std::move(w)) are both Widget &&:

move_parameter.cpp:27:21: error: implicit instantiation of undefined template 'TD<Widget &&>'
    TD<decltype(w)> wType;
                    ^
move_parameter.cpp:28:32: error: implicit instantiation of undefined template 'TD<Widget &&>'
    TD<decltype(std::move(w))> mwType;
                               ^

As both w and std::move(w) are same rvalue reference type, why "return std::move(w)" moves w, while "return w" only copy?

Edit: Thanks for the answers and comments. I got a better understanding now, but not sure if it's accurate. So std::move(w) returns an rvalue reference, just as w itself. But std::move(w) as a function call, it is an rvalue by itself, so it can be moved. While w as a named variable, it is an lvalue by itself, though the type of it is rvalue reference, so it cannot be moved.

Best Answer

The type of an expression is different than the type of a variable, and decltype does both.

decltype(w)

is the the variable w.

decltype((w))

is the type of the expression w (well (w) but those are the same).

If you have a variable of type foo&&, when used in an expression its type is foo& -- it is named, and hence an lvalue.

This makes some sense. foo&& just means it can bind to a temporary. Once it is bound, it has a name and can be used more than once.

Anything that can be used more than once should not be implicitly moved-from.

The only exceptions to this rule that named things are lvalues are the implicit move on return rules. In a few cases, where elision would occur but is blocked for whatever reason, values are implicitly moved. These exceptions do not apply here.