Reputation: 28803
Is this code valid?
template<bool b>
struct s {
void f() const {
}
static void f() requires b {
}
};
void g() {
s<true>().f();
}
clang says yes, but gcc says no
<source>: In function 'void g()':
<source>:10:20: error: call of overloaded 'f()' is ambiguous
10 | s<true>().f();
| ~~~~~~~~~~~^~
<source>:3:14: note: candidate: 'void s<b>::f() const [with bool b = true]'
3 | void f() const {
| ^
<source>:5:21: note: candidate: 'static void s<b>::f() requires b [with bool b = true]'
5 | static void f() requires b {
| ^
Compiler returned: 1
https://godbolt.org/z/f4Kb68aee
Upvotes: 9
Views: 274
Reputation: 21281
Your code is ill-formed according to C++20 standard class.static.mfct#2:
There shall not be a static and a non-static member function with the same name and the same parameter types ([over.load]).
There is no exception here for the presence of requires
-clause to differentiate member functions, only same name and the same parameter types. And it is exactly our case: the same name is f
, and the same parameter types is empty set.
So Clang and MSVC are wrong in accepting the code. But the diagnostics of GCC is definitely confusing.
With some minor tweaks in the code (removed const
in not-static member function and get its address in the code), Clang and MSVC also show to have big problems with it:
template<bool b>
struct s {
void f() {}
static void f() requires b {}
};
int main() {
s<true>().f();
void (s<true>::*x)() = &s<true>::f;
}
Demo: https://gcc.godbolt.org/z/vdq9j63Gs
Upvotes: 1
Reputation: 303147
If we go through [over.match.best.general], we get:
a viable function
F1
is defined to be a better function than another viable functionF2
if for all argumentsi
,ICSi(F1)
is not a worse conversion sequence thanICSi(F2)
, and then [...]
The only argument is the object argument, and we have earlier that:
If
F
is a static member function,ICS1(F)
is defined such thatICS1(F)
is neither better nor worse thanICS1(G)
for any functionG
, and, symmetrically,ICS1(G)
is neither better nor worse thanICS1(F)
; otherwise,
So the premise holds: all arguments for one function have a conversion sequence no worse than the conversion sequence for the other function. So we move on to our tiebreakers...
- for some argument
j
,ICSj(F1)
is a better conversion sequence thanICSj(F2)
, or, if not that,
The only argument that we could have a better conversion sequence for is the object argument, and as established, that one is equivalent. So this tiebreaker does not apply.
- the context is an initialization by user-defined conversion (see [dcl.init], [over.match.conv], and [over.match.ref]) and [...]
Nope.
- the context is an initialization by conversion function for direct reference binding of a reference to function type, [...]
Nope.
F1
is not a function template specialization andF2
is a function template specialization, or, if not that,
Nope.
F1
andF2
are function template specializations, and the function template forF1
is more specialized than the template forF2
according to the partial ordering rules described in [temp.func.order], or, if not that,
Nope.
F1
andF2
are non-template functions with the same parameter-type-lists, andF1
is more constrained thanF2
according to the partial ordering of constraints described in [temp.constr.order], or if not that,
Aha! In this example, we have non-template functions with the same parameter-type-lists (both are just empty). The static member function is constrained and the non-static member function is not constrained, which is the most trivial kind of "more constrained" (see [temp.constr.order]).
As such, I think that clang (and msvc) are correct to accept the program and gcc is incorrect to reject it. (submitted 103783).
Upvotes: 8