Consider the following class template, which contains two (hidden) friend declarations of the same friend (same function type; see below), which also defines the friend (and the friend is thus inline), but with the definition conditional on (mutually exclusive) requires-clauses:
#include <iostream>
struct Base {};
template<int N>
struct S : public Base {
friend int foo(Base&) requires (N == 1) { return 1; }
friend int foo(Base&) requires (N == 2) { return 3; }
};
[dcl.fct]/8 states that trailing requires-clauses are not part of a function's type [emphasis mine]:
The return type, the parameter-type-list, the ref-qualifier, the cv-qualifier-seq, and the exception specification, but not the default arguments ([dcl.fct.default]) or the trailing requires-clause ([dcl.decl]), are part of the function type.
which means that the two definitions above is an ODR-violation for a case where both definitions are instantiated; if we only focus on a single translation unit, [basic.def.odr]/1 would be violated:
No translation unit shall contain more than one definition of any variable, function, class type, enumeration type, template, default argument for a parameter (for a function in a given scope), or default template argument.
and in a single TU this violation should arguably be diagnosable (no "need" for "ill-formed, NDR"). I'm trying understand the rules for when the definitions above will be instantiated; or if this is entirely implementation-defined (or even ill-formed before reaching instantiation phase).
Both Clang and GCC(1) accepts the following program
// ... as above
// (A)
int main() {
S<1> s1{};
std::cout << foo(s1); // Clang & GCC: 1
}
For programs (B) through (D) below, however, Clang accepts them all whereas GCC rejects them all with a re-definition error:
// (B)
int main() {
S<1> s1{};
S<2> s2{}; // GCC: re-definition error of 'foo'
}
// (C)
int main() {
S<1> s1{};
S<2> s2{}; // GCC: re-definition error of 'foo'
std::cout << foo(s1); // Clang: 1
}
// (D)
template struct S<1>;
template struct S<2>; // GCC: re-definition error of 'foo'
int main() {}
It's only when actually trying to invoke the friend function via ADL on both specializations that Clang actually emits an error
// (E)
int main() {
S<1> s1{};
S<2> s2{}; // GCC: re-definition error of 'foo'
std::cout << foo(s1); // MSVC: ambiguous call
std::cout << foo(s2);
// Clang error: definition with same mangled name
// '_Z3fooR4Base' as another definition
}
and we may note that only MSVC actually reaches a state of seemingly accepting both definitions after which it fails as expect ("ambiguous call").
DEMO.
And, thus, which compiler is right here?
I have not been able to understand what rules that governs when a friend function declaration (of a class template) that is also a definition, is instantiated, particularly when requires-clauses are involved; possibly this is irrelevant, though, if the behaviour of GCC and Clang above are both incorrect.
(1) GCC HEAD 11.0.0, Clang HEAD 12.0.0.
From over#dcl-1,
Two function declarations of the same name refer to the same function if they are in the same scope and have equivalent parameter declarations ([over.load]) and equivalent ([temp.over.link]) trailing requires-clauses, if any ([dcl.decl]).
[Note 1: Since a constraint-expression is an unevaluated operand, equivalence compares the expressions without evaluating them.
[Example 1:
template<int I> concept C = true;
template<typename T> struct A {
void f() requires C<42>; // #1
void f() requires true; // OK, different functions
};
— end example]
— end note]
I understand there is 2 different foo
(so no ODR violations) because of the differing requires clauses.
I think there is issue with all mentioned compilers to not cover this corner case.