I came across this example below when watching video about c++:
#include <iostream>
#include <type_traits>
struct S {
S() { std::cout << 'a'; }
S(const S&) { std::cout << 'b'; }
S(S&&) { std::cout << 'c'; }
};
template <typename T>
S f(T&& t) { return t; }
int main() {
S s{};
f(s);
f(std::move(s));
}
At first I thought it is pretty clear that std::move(s) cast s into rvalue reference and the template is instantiated as S f(S&& t) {return t;}. The argument is an rvalue so when passing parameter S(S&&) is called, and the output is 'abc'. The output is 'abc' as expected when compiled with compiler x86-64 clang 15.0.0. But when compiled with x86-64 gcc 12.2, it output 'abb'.
Why it output 'b' instead of 'c' when compiled with gcc? Is this something about UB or implementation dependent? Or something else I do not know?
Edit: It seems when the template is modified to this:
template <typename T>
S f(T&& t) { return std::forward<T>(t); }
gcc start to output 'abc' just as clang(same version as stated above). I don't know how it is related to std::foward. And here is the video I was watching. The example appears about at 54 min.
Originally in C++17 (and before) the correct answer was
abb.The specialization of
fused in the last call looks like this:Normally the name of a variable, regardless of its type, is a lvalue and so overload resolution should choose the copy constructor here to construct the return value.
However, return statements whose operand is a (possibly parenthesized) id-expression have special rules where, under certain circumstances, overload resolution is done first as if the operand was a rvalue, even if it isn't, and only if this overload resolution failed, normal overload resolution would be attempted. If this applied to your situation, then
return t;would still use the move constructor.In C++17 one of the requirements for this special behavior was that id-expression names a local variable which is an object, not a reference. Therefore it wouldn't apply here.
For C++20 this has been changed with P0527 to include local variables of rvalue reference type.
It seems that GCC implemented this change to apply only for C++20 and later modes (you get
abcwith-std=c++20), while Clang seems to have applied it also to older C++ modes as a defect report.The commit message to GCC 11 implementing the change mentions that this should be a defect report against earlier C++ versions as well, but that they want to gather some experience with the impact of this change. It seems they haven't yet implemented for modes below
-std=c++20.For C++23 the rules were further simplified so that there are not two overload resolution phases anymore. Instead id-expressions are now simply xvalue expressions instead of lvalue expressions in these special situations. This change was made by P2266.