See <https://wg21.link/p2468r2>.
FYI, clang trunk now implements this DR. Rough testcase distilled from the paper + some extra tests from the LLVM test coverage of the paper, with just // ERR to mark expected diagnostics (details to be filled only once it is implemented). // P2468R2 - The Equality Operator You Are Looking For // { dg-do compile { target c++20 } } struct A { bool operator== (const A &) { return true; } bool operator!= (const A &) { return false; } }; bool a = A{} != A{}; template <typename T> struct B { bool operator== (const T &) const; bool operator!= (const T &) const; }; struct B1 : B<B1> { }; bool b1 = B1{} == B1{}; bool b2 = B1{} != B1{}; template <bool> struct C { using C1 = C<true>; using C2 = C<false>; C () = default; C (const C2 &); bool operator== (C1) const; bool operator!= (C1) const; }; using C3 = C<false>; bool c = C3{} == C3{}; struct D { D (); D (int *); bool operator== (const D &) const; operator int * () const; }; bool d = nullptr != D{}; // ERR struct E { operator bool () const; }; unsigned char operator== (E, E); unsigned char e = E{} != E{}; // ERR struct F {}; template <typename T> bool operator== (F, T); bool f1 = 0 == F (); template <typename T> bool operator!= (F, T); bool f2 = 0 == F (); // ERR struct G { bool operator== (const G &); }; struct G1 : G { G1 (); G1 (G); bool operator!= (const G &); }; bool g1 = G () == G1 (); bool g2 = G1 () == G (); // ERR struct H {}; template <typename T> bool operator== (H, T); inline namespace H1 { template <typename T> bool operator!= (H, T); } bool h = 0 == H (); template <class T> struct I { int operator== (const double &) const; friend inline int operator== (const double &, const T &) { return 1; } }; struct I1 : I<I1> { }; bool i = I1{} == 0.; // ERR struct J { bool operator== (const J &) const; bool operator!= (const J &) const; }; struct J1 : J { J1 (const J &); bool operator== (const J1 &x) const { return static_cast<const J &> (*this) == x; // ERR } }; struct K { bool operator== (const K &); }; bool k = K{} == K{}; // ERR struct L { bool operator== (const L &) const; }; bool l = L{} == L{}; struct M { bool operator== (M); }; bool m = M () == M (); struct N { virtual bool operator== (const N &) const; }; struct N1 : N { bool operator== (const N &) const override; }; bool n = N1 () == N1 (); // ERR struct O { virtual signed char operator== (const O &) const; signed char operator!= (const O &x) const { return !operator== (x); } }; struct O1 : O { signed char operator== (const O &) const override; }; bool o = O1 () != O1 (); // ERR template <class T> bool foo (T x, T y) requires requires { x == y; } { return x == y; } bool b3 = foo (B1 (), B1 ()); struct P {}; template <typename T, class U = int> bool operator== (P, T); template <class T> bool operator!= (P, T); bool p = 0 == P (); struct Q {}; template <typename T> bool operator== (Q, T); template <typename U> bool operator!= (Q, U); bool q = 0 == Q (); // ERR struct R { template <typename T> bool operator== (const T &); }; bool r = R () == R (); // ERR struct S { template <typename T> bool operator== (const T &) const; bool operator!= (const S &); }; bool s = S () == S (); struct T {}; template <class U = int> bool operator== (T, int); bool operator!= (T, int); bool t = 0 == T (); struct U {}; bool operator== (U, int); bool u1 = 0 == U (); namespace U1 { bool operator!= (U, int); } bool u2 = 0 == U (); using U1::operator!=; bool u3 = 0 == U (); // ERR struct V {}; template <typename T> bool operator== (V, T); bool v1 = 0 == V (); namespace V1 { template <typename T> bool operator!= (V, T); } bool v2 = 0 == V (); using V1::operator!=; bool v3 = 0 == V (); // ERR template <int N> struct W { bool operator== (int) requires (N == 1); bool operator!= (int) requires (N == 2); }; int w = 0 == W<1> (); struct X { bool operator== (X const &); static bool operator!= (X const &, X const &); }; bool x = X () == X (); // ERR
The A-J cases are from the paper to be precise.
As for implementation, I'd say in call.cc (add_candidates) we could do something like: if ((flags & (LOOKUP_REWRITTEN | LOOKUP_REVERSE)) && DECL_OVERLOADED_OPERATOR_IS (fn, EQ_EXPR)) { } before the loop on the overloads and depending on whether fn is in class scope or namespace scope perform lookup (again argument dependent or not?) for ovl_op_identifier (false, NE_EXPR) in the same scope. But am not really sure what to do next if this finds something (supposedly in the loop over the overloads?); I suppose if no template is involved, compare the argument types (what else?), but for templates it is unclear (to me) if it should try to instantiate them and compare types only afterwards, or what exactly should be checked whether they correspond ([basic.scope.scope]). Because e.g. http://eel.is/c++draft/basic.scope.scope#4.3.1 talks about equivalent trailing requires clauses etc.
Created attachment 53770 [details] gcc13-pr106644-wip.patch Untested WIP. Compared to vanilla trunk, this rejects the f2 case which is a2 from the example in the standard. But doesn't handle anything else unfortunately. In the g1 case (c1 from the example in the standard), it is accepted for a wrong reason, decls_match rejects the comparison between B::operator== and C::operator!= because they have different CP_DECL_CONTEXT and so we think that the operator== is a rewrite target when it is not. Shall we use something different than decls_match (say a copy of it that ignores the context and for non-static members the this type too)? The reason it is then accepted is some non-standard handling of the rewritten/reversed candidates in joust? Then g2 case (c2 in the standard) is then incorrectly accepted because of that code in joust or so. Not really sure if we have a way to search just in a single namespace and not all the containing ones, and not sure about the using decls if those are something that should be ignored like in the patch or handled too. The k case in the testcase is I think because of joust too. The u3 case in the testcase is I think about whether using decls count or not. Anyway, the remaining // ERR cases are where clang++ trunk rejects stuff but we with the patch don't.
Implemented in https://gcc.gnu.org/git/?p=gcc.git;a=commitdiff;h=431be04b8b6e31d950ddab340ed866d197d23d4d