Because in the expression X(std::forward<T>(arg))
, even if, in the last case, arg
is a reference bound to a temporary, it is still not a temporary. Inside the function body, the compiler cannot ensure that arg
is not bound to an lvalue. Consider what would happen if the move constructor was elided and you would perform this call:
auto x4 = make_X(std::move(x2));
x4
would become an alias for x2
.
The rules for move elision of the return value is described in [class.copy]/32:
[...]This elision of copy/move operations, called copy elision, is permitted in the following circumstances (which may be combined to eliminate multiple copies):
in a return statement in a function with a class return type, when the expression is the name of a non-volatile automatic object (other than a function or catch-clause parameter) with the same cv-unqualified type as the function return type, the copy/move operation can be omitted by constructing the automatic object directly into the function's return value
when a temporary class object that has not been bound to a reference ([class.temporary]) would be copied/moved to a class object with the same cv-unqualified type, the copy/move operation can be omitted by constructing the temporary object directly into the target of the omitted copy/move
In the call make_X(X(1))
copy elision actualy happens, but only once:
- First X(1) creates a temporary that is bound to
arg
.
- Then
X(std::forward<T>(arg))
invokes the move constructor. arg
is not a temporary so the second rule above does not apply.
- Then the result the expression
X(std::forward<T>(arg))
should also be moved to construct the return value but this move is elided.
About your UPDATE, std::forward
cause materialisation of the temporary X(1)
that is bound to an xvalue: the return of std::forward
. This returned xvalue is not a temporary so copy/elision is not anymore applicable.
Again what would happen in this case if move elision occured. (The c++ grammar is not contextual):
auto x7 = std::forward<X>(std::move(x2));
Nota: After I have seen a new answer about C++17 I wanted to add to confusion.
In C++17, the definition of prvalue
is that changed that there are not any more any move constructor to elide inside your example code. Here example of result code of GCC with the option fno-elide-constructors
in C++14 and then in C++17:
#c++ -std=c++14 -fno-elide-constructors | #c++ -std=c++17 -fno-elide-constructors
main: | main:
sub rsp, 24 | sub rsp, 24
mov esi, 1 | mov esi, 1
lea rdi, [rsp+15] | lea rdi, [rsp+12]
call X::X(int) | call X::X(int)
lea rsi, [rsp+15] | lea rdi, [rsp+13]
lea rdi, [rsp+14] | mov esi, 1
call X::X(X&&) | call X::X(int)
lea rsi, [rsp+14] | lea rdi, [rsp+15]
lea rdi, [rsp+11] | mov esi, 1
call X::X(X&&) | call X::X(int)
lea rdi, [rsp+14] | lea rsi, [rsp+15]
mov esi, 1 | lea rdi, [rsp+14]
call X::X(int) | call X::X(X&&)
lea rsi, [rsp+14] | xor eax, eax
lea rdi, [rsp+15] | add rsp, 24
call X::X(X&&) | ret
lea rsi, [rsp+15]
lea rdi, [rsp+12]
call X::X(X&&)
lea rdi, [rsp+13]
mov esi, 1
call X::X(int)
lea rsi, [rsp+13]
lea rdi, [rsp+15]
call X::X(X&&)
lea rsi, [rsp+15]
lea rdi, [rsp+14]
call X::X(X&&)
lea rsi, [rsp+14]
lea rdi, [rsp+15]
call X::X(X&&)
xor eax, eax
add rsp, 24
ret
-std=c++1z
. (For instance, initialization ofx1
andx2
is in C++17 correct even if both copy and move ctors ofX
are deleted.) – Fleshingsauto x2 = X(X(1));
but not this oneauto x3 = make_X(X(1));
– Gliomaauto x2 = X(X(1));
has move as well in VS2017 – Correlateauto x2 = X(X(1));
IMO must not invoke move ctor or even require its existence. – Fleshings