Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why move return an rvalue reference parameter need to wrap it with std::move()?

Tags:

c++

c++11

c++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.

like image 218
Leira Hua Avatar asked Oct 22 '16 02:10

Leira Hua


People also ask

Why do we need std :: move?

std::move is used to indicate that an object t may be "moved from", i.e. allowing the efficient transfer of resources from t to another object. In particular, std::move produces an xvalue expression that identifies its argument t . It is exactly equivalent to a static_cast to an rvalue reference type.

Why do we need rvalue references?

Rvalue references is a small technical extension to the C++ language. Rvalue references allow programmers to avoid logically unnecessary copying and to provide perfect forwarding functions. They are primarily meant to aid in the design of higer performance and more robust libraries.

How do you pass rvalue reference to a function?

If you want pass parameter as rvalue reference,use std::move() or just pass rvalue to your function.

Can lvalue bind to rvalue reference?

An lvalue reference can bind to an lvalue, but not to an rvalue.


2 Answers

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.

like image 150
Yakk - Adam Nevraumont Avatar answered Oct 13 '22 01:10

Yakk - Adam Nevraumont


In passThroughMove(Widget&& w), w's type is already rvalue reference, std::move(w) just cast it into rvalue reference again.

So std::move(w) returns an rvalue reference, just as w itself.

No, std::move(w) casts to rvalue, while rvalue references are lvalues.

Both functions passThroughMove and passThrough return by value. They differ, however, in the way they create internally such return value. Internally, passThroughMove creates its return value by move. A new Widget object (the return value) is created by moving into it, that's the effect of std::move on the return value. passThrough on the other hand creates its own return value by copy.

The fact that the assignment

Widget wt2 = passThrough(std::move(w2));

is done from an rvalue does not change the fact that passThrough is forced to create its return value by copy.

In the output of the code you see the effect of the above semantics plus RVO. Without RVO both assignments should result into two additional move-constructions, which are optimized away.

like image 45
Emerald Weapon Avatar answered Oct 13 '22 01:10

Emerald Weapon