David Stone
David Stone

Reputation: 28803

Overloading static and non-static member function with constraint

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

Answers (2)

Fedor
Fedor

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

Barry
Barry

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 function F2 if for all arguments i, ICSi(F1) is not a worse conversion sequence than ICSi(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 that ICS1(F) is neither better nor worse than ICS1(G) for any function G, and, symmetrically, ICS1(G) is neither better nor worse than ICS1(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 than ICSj(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 and F2 is a function template specialization, or, if not that,

Nope.

  • F1 and F2 are function template specializations, and the function template for F1 is more specialized than the template for F2 according to the partial ordering rules described in [temp.func.order], or, if not that,

Nope.

  • F1 and F2 are non-template functions with the same parameter-type-lists, and F1 is more constrained than F2 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

Related Questions