Description
[temp.over.link]/5 Example 4 demonstrates that the following is well-formed:
template<int I> concept C = true;
template<typename T> struct A {
void f() requires C<42>; // #1
void f() requires true; // OK, different functions
};
This is OK since the two constraints are unevaluated operands and so functional equivalence is considered operationally.
However, changing return types allows us to refer to the two member functions separately:
template<int I> concept C = true;
template<typename T> struct A {
int f() requires C<42> { return 1; } // #1
unsigned f() requires true { return 2u; } // #2
};
int main() {
int (A<int>::*p)() = &A<int>::f;
unsigned (A<int>::*q)() = &A<int>::f;
return (A<int>().*p)() + (A<int>().*q)();
}
There is implementation divergence; clang errors ("definition with same mangled name '_ZN1AIiE1fEv' as another definition"), gcc errors ("Two symbols with same comdat_group are not linked by the same_comdat_group list") and then ices ("symtab_node::verify failed") while MSVC accepts (helped by the ABI mangling return types).
If the return types are changed to auto
(auto f() requires C<42> { return 1; }
etc.), clang accepts erroneously, calling #.2 both times, gcc continues to error and ice, and MSVC rejects C2440 ("initializing': cannot convert from 'overloaded-function' to 'int (__cdecl A::* )(void)'", etc.)
I'm not sure what the right fix is. It seems like it'd be best for #.1 and #.2 to be determined to be incompatible overloads at the time of class template instantiation, but maybe that'd come with other issues.