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.
Question
- Can hidden non-template friend functions of class templates (friend declarations where the friends are also defined in the class) be overloaded only by a difference in (mutually exclusive) requires-clauses?
And, thus, which compiler is right here?
- All (ill-formed NDR and/or implementation defined w.r.t. point of instantiation rules),
- GCC
- Clang
- MSVC
- None (example (E) is well-formed)
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.