Running the code supplied in this question:
Why move return an rvalue reference parameter need to wrap it with std::move()?
#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;
}
Yields different results depending on the compiler I use. When compiling using the latest Visual Studio (tried both C++14 and C++17) I get the following results:
Widget created with name: w1.
Widget created with name: w2.
Widget w1 just got moved.
Widget w2 just got moved. //<---
Ran and compiled this code online and the results were different. In the provided question the user also receives the same results:
Widget created with name: w1.
Widget created with name: w2.
Widget w1 just got moved.
Widget w2 just got copied. //<---
Why is w2 being moved when using Visual Studio and copied when using various different compilers?
As can be seen in the Microsoft C++ language conformance table section of the Visual Studio C++ documentation, this is Visual Studio implementing
as of Visual Studio 2019 version 16.4, compiler MSVC version 19.24.
The essential parts of P1825R0 has been added to [class.copy.elision]/3 [emphasis mine]:
An implicitly movable entity is a variable of automatic storage duration that is either a non-volatile object or an rvalue reference to a non-volatile object type. In the following copy-initialization contexts, a move operation might be used instead of a copy operation:
- (3.1) If the expression in a
return
([stmt.return]) orco_return
([stmt.return.coroutine]) statement is a (possibly parenthesized) id-expression that names an implicitly movable entity declared in the body or parameter-declaration-clause of the innermost enclosing function or lambda-expression, or- (3.2) ...
overload resolution to select the constructor for the copy or the
return_value
overload to call is first performed as if the expression or operand were an rvalue. [...].
Using godbolt.ms, and the following contrived example
#include <memory>
struct Foo {
Foo(int num) : num(num) {}
Foo(const Foo& f) : num(f.num) {}
Foo(Foo&& f) : num(std::move(f.num)) {}
Foo& operator=(const Foo&) = delete;
Foo& operator=(Foo&&) = delete;
int num;
};
Foo maybeConsumeMyPreciousFoo(Foo&& foo, bool consume_foo) {
if (consume_foo) { return std::move(foo); }
else { return foo; } // "Should not move" (as we'll see this is an invalid assumption)
}
we can inspect, in particular, the generated assemby of the line else { return foo; }
in the maybeConsumeMyPreciousFoo(...)
function;
for MSVC v19.23:
mov rdx, QWORD PTR foo$[rsp]
mov rcx, QWORD PTR __$ReturnUdt$[rsp]
call Foo::Foo(Foo const &) ; Foo::Foo
mov rax, QWORD PTR __$ReturnUdt$[rsp]
and for MSVC v19.24:
mov rdx, QWORD PTR foo$[rsp]
mov rcx, QWORD PTR __$ReturnUdt$[rsp]
call Foo::Foo(Foo &&) ; Foo::Foo
mov rax, QWORD PTR __$ReturnUdt$[rsp]
respectively, showing that the branch, for the latter version, actually moves from the foo
parameter as if it was an rvalue.
GCC:
C++ Standards Support in GCC:
[...]
C++2a Language Features
[...]
DR: More implicit moves (merge P0527R1 and P1155R3)
Available in GCC?: No
Clang:
C++ Support in Clang
[...]
###C++20 implementation status
P1825R0 not even listed.
Finally, cppreference's C++ compiler support page also lists P1825R0 as non-supported for both Clang and GCC.