why different output when run program compiled with gcc and clang

122 views Asked by At

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.

1

There are 1 answers

0
user17732522 On

Originally in C++17 (and before) the correct answer was abb.

The specialization of f used in the last call looks like this:

S f<S>(S&& t) { return t; }

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 abc with -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.