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.
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.
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.
If you want pass parameter as rvalue reference,use std::move() or just pass rvalue to your function.
An lvalue reference can bind to an lvalue, but not to an rvalue.
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.
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With