I've happened across the following code, the behaviour of which is diagreed upon by all of GCC, Clang, and MSVC:
#include <concepts>
template<typename T>
auto foo(T);
template<typename U>
struct S {
template<typename T>
friend auto foo(T) {
return U{};
}
};
S<double> s;
static_assert(std::same_as<decltype(foo(42)), double>);
Live demo: https://godbolt.org/z/hK6xhesKM
foo() is declared at global namespace with deduced return type. S<U> provides a definition for foo() via a friend function, where it returns a value of type U.
What I expected is that when S is instantiated with U=double, its definition of foo() gets put in the global namespace and U is substituted in, due to how friend functions work, effectively like this:
template<typename T>
auto foo(T);
S<double> s;
template<typename T>
auto foo(T) {
return double{};
}
Thus I expect foo()'s return type is double, and the following static assertion should pass:
static_assert(std::same_as<decltype(foo(42)), double>);
However, what actually happens is that all three compilers disagree on the behaviour.
GCC passes the static assertion, like I expect.
Clang fails the static assertion, as it seems to believe that foo() returns int:
'std::is_same_v<int, double>' evaluated to false
And MSVC produces a different error entirely:
'U': undeclared identifier
I find it bizarre that all the compilers have different behaviour here for a seemingly simple code example.
The issue does not occur if foo() isn't templated (see demo here), or if it doesn't have deduced return type (see demo here).
Which compiler has the correct behaviour? Or, is the code ill-formed NDR or undefined behaviour? (And, why?)