[clang] 38b9d31 - [C++20][Clang] P2468R2 The Equality Operator You Are Looking For

Utkarsh Saxena via cfe-commits cfe-commits at lists.llvm.org
Thu Oct 6 04:22:15 PDT 2022


Author: Utkarsh Saxena
Date: 2022-10-06T13:21:34+02:00
New Revision: 38b9d313e6945804fffc654f849cfa05ba2c713d

URL: https://github.com/llvm/llvm-project/commit/38b9d313e6945804fffc654f849cfa05ba2c713d
DIFF: https://github.com/llvm/llvm-project/commit/38b9d313e6945804fffc654f849cfa05ba2c713d.diff

LOG: [C++20][Clang] P2468R2 The Equality Operator You Are Looking For

Implement
https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2022/p2468r2.html.

Primarily we now accept
```
template<typename T> struct CRTPBase {
  bool operator==(const T&) const;
  bool operator!=(const T&) const;
};
struct CRTP : CRTPBase<CRTP> {};
bool cmp_crtp = CRTP() == CRTP();
bool cmp_crtp2 = CRTP() != CRTP();
```

Differential Revision: https://reviews.llvm.org/D134529

Added: 
    

Modified: 
    clang/docs/ReleaseNotes.rst
    clang/include/clang/Basic/DiagnosticSemaKinds.td
    clang/include/clang/Sema/Overload.h
    clang/lib/Sema/SemaDeclCXX.cpp
    clang/lib/Sema/SemaOverload.cpp
    clang/test/CXX/over/over.match/over.match.funcs/over.match.oper/p3-2a.cpp

Removed: 
    


################################################################################
diff  --git a/clang/docs/ReleaseNotes.rst b/clang/docs/ReleaseNotes.rst
index 93974e89eec14..1652c90337f6e 100644
--- a/clang/docs/ReleaseNotes.rst
+++ b/clang/docs/ReleaseNotes.rst
@@ -415,6 +415,7 @@ C++20 Feature Support
   name is found via ordinary lookup so typedefs are found.
 - Implemented `P0634r3 <https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2018/p0634r3.html>`_,
   which removes the requirement for the ``typename`` keyword in certain contexts.
+- Implemented The Equality Operator You Are Looking For (`P2468 <http://wg21.link/p2468r2>`_).
 
 C++2b Feature Support
 ^^^^^^^^^^^^^^^^^^^^^

diff  --git a/clang/include/clang/Basic/DiagnosticSemaKinds.td b/clang/include/clang/Basic/DiagnosticSemaKinds.td
index fd6651ee5d25f..0c7b64c7a94b4 100644
--- a/clang/include/clang/Basic/DiagnosticSemaKinds.td
+++ b/clang/include/clang/Basic/DiagnosticSemaKinds.td
@@ -4703,6 +4703,8 @@ def ext_ovl_ambiguous_oper_binary_reversed : ExtWarn<
 def note_ovl_ambiguous_oper_binary_reversed_self : Note<
   "ambiguity is between a regular call to this operator and a call with the "
   "argument order reversed">;
+def note_ovl_ambiguous_eqeq_reversed_self_non_const : Note<
+  "mark 'operator==' as const or add a matching 'operator!=' to resolve the ambiguity">;
 def note_ovl_ambiguous_oper_binary_selected_candidate : Note<
   "candidate function with non-reversed arguments">;
 def note_ovl_ambiguous_oper_binary_reversed_candidate : Note<

diff  --git a/clang/include/clang/Sema/Overload.h b/clang/include/clang/Sema/Overload.h
index eb935e0a8cbf3..29d0fa63369f0 100644
--- a/clang/include/clang/Sema/Overload.h
+++ b/clang/include/clang/Sema/Overload.h
@@ -977,12 +977,16 @@ class Sema;
     /// functions to a candidate set.
     struct OperatorRewriteInfo {
       OperatorRewriteInfo()
-          : OriginalOperator(OO_None), AllowRewrittenCandidates(false) {}
-      OperatorRewriteInfo(OverloadedOperatorKind Op, bool AllowRewritten)
-          : OriginalOperator(Op), AllowRewrittenCandidates(AllowRewritten) {}
+          : OriginalOperator(OO_None), OpLoc(), AllowRewrittenCandidates(false) {}
+      OperatorRewriteInfo(OverloadedOperatorKind Op, SourceLocation OpLoc,
+                          bool AllowRewritten)
+          : OriginalOperator(Op), OpLoc(OpLoc),
+            AllowRewrittenCandidates(AllowRewritten) {}
 
       /// The original operator as written in the source.
       OverloadedOperatorKind OriginalOperator;
+      /// The source location of the operator.
+      SourceLocation OpLoc;
       /// Whether we should include rewritten candidates in the overload set.
       bool AllowRewrittenCandidates;
 
@@ -1018,22 +1022,23 @@ class Sema;
           CRK = OverloadCandidateRewriteKind(CRK | CRK_Reversed);
         return CRK;
       }
-
       /// Determines whether this operator could be implemented by a function
       /// with reversed parameter order.
       bool isReversible() {
         return AllowRewrittenCandidates && OriginalOperator &&
                (getRewrittenOverloadedOperator(OriginalOperator) != OO_None ||
-                shouldAddReversed(OriginalOperator));
+                allowsReversed(OriginalOperator));
       }
 
-      /// Determine whether we should consider looking for and adding reversed
-      /// candidates for operator Op.
-      bool shouldAddReversed(OverloadedOperatorKind Op);
+      /// Determine whether reversing parameter order is allowed for operator
+      /// Op.
+      bool allowsReversed(OverloadedOperatorKind Op);
 
       /// Determine whether we should add a rewritten candidate for \p FD with
       /// reversed parameter order.
-      bool shouldAddReversed(ASTContext &Ctx, const FunctionDecl *FD);
+      /// \param OriginalArgs are the original non reversed arguments.
+      bool shouldAddReversed(Sema &S, ArrayRef<Expr *> OriginalArgs,
+                             FunctionDecl *FD);
     };
 
   private:

diff  --git a/clang/lib/Sema/SemaDeclCXX.cpp b/clang/lib/Sema/SemaDeclCXX.cpp
index a490f1e59dd10..bed281991ab59 100644
--- a/clang/lib/Sema/SemaDeclCXX.cpp
+++ b/clang/lib/Sema/SemaDeclCXX.cpp
@@ -7879,7 +7879,8 @@ class DefaultedComparisonAnalyzer
     OverloadCandidateSet CandidateSet(
         FD->getLocation(), OverloadCandidateSet::CSK_Operator,
         OverloadCandidateSet::OperatorRewriteInfo(
-            OO, /*AllowRewrittenCandidates=*/!SpaceshipCandidates));
+            OO, FD->getLocation(),
+            /*AllowRewrittenCandidates=*/!SpaceshipCandidates));
 
     /// C++2a [class.compare.default]p1 [P2002R0]:
     ///   [...] the defaulted function itself is never a candidate for overload

diff  --git a/clang/lib/Sema/SemaOverload.cpp b/clang/lib/Sema/SemaOverload.cpp
index 6e328ab3f075d..b3f98a0fe843e 100644
--- a/clang/lib/Sema/SemaOverload.cpp
+++ b/clang/lib/Sema/SemaOverload.cpp
@@ -12,14 +12,17 @@
 
 #include "clang/AST/ASTContext.h"
 #include "clang/AST/CXXInheritance.h"
+#include "clang/AST/DeclCXX.h"
 #include "clang/AST/DeclObjC.h"
 #include "clang/AST/DependenceFlags.h"
 #include "clang/AST/Expr.h"
 #include "clang/AST/ExprCXX.h"
 #include "clang/AST/ExprObjC.h"
+#include "clang/AST/Type.h"
 #include "clang/AST/TypeOrdering.h"
 #include "clang/Basic/Diagnostic.h"
 #include "clang/Basic/DiagnosticOptions.h"
+#include "clang/Basic/OperatorKinds.h"
 #include "clang/Basic/PartialDiagnostic.h"
 #include "clang/Basic/SourceManager.h"
 #include "clang/Basic/TargetInfo.h"
@@ -34,6 +37,7 @@
 #include "llvm/ADT/STLExtras.h"
 #include "llvm/ADT/SmallPtrSet.h"
 #include "llvm/ADT/SmallString.h"
+#include "llvm/Support/Casting.h"
 #include <algorithm>
 #include <cstdlib>
 
@@ -890,7 +894,77 @@ llvm::Optional<unsigned> DeductionFailureInfo::getCallArgIndex() {
   }
 }
 
-bool OverloadCandidateSet::OperatorRewriteInfo::shouldAddReversed(
+static bool FunctionsCorrespond(ASTContext &Ctx, const FunctionDecl *X,
+                                const FunctionDecl *Y) {
+  if (!X || !Y)
+    return false;
+  if (X->getNumParams() != Y->getNumParams())
+    return false;
+  for (unsigned I = 0; I < X->getNumParams(); ++I)
+    if (!Ctx.hasSameUnqualifiedType(X->getParamDecl(I)->getType(),
+                                    Y->getParamDecl(I)->getType()))
+      return false;
+  if (auto *FTX = X->getDescribedFunctionTemplate()) {
+    auto *FTY = Y->getDescribedFunctionTemplate();
+    if (!FTY)
+      return false;
+    if (!Ctx.isSameTemplateParameterList(FTX->getTemplateParameters(),
+                                         FTY->getTemplateParameters()))
+      return false;
+  }
+  return true;
+}
+
+static bool shouldAddReversedEqEq(Sema &S, SourceLocation OpLoc,
+                                  Expr *FirstOperand, FunctionDecl *EqFD) {
+  assert(EqFD->getOverloadedOperator() ==
+         OverloadedOperatorKind::OO_EqualEqual);
+  // C++2a [over.match.oper]p4:
+  // A non-template function or function template F named operator== is a
+  // rewrite target with first operand o unless a search for the name operator!=
+  // in the scope S from the instantiation context of the operator expression
+  // finds a function or function template that would correspond
+  // ([basic.scope.scope]) to F if its name were operator==, where S is the
+  // scope of the class type of o if F is a class member, and the namespace
+  // scope of which F is a member otherwise. A function template specialization
+  // named operator== is a rewrite target if its function template is a rewrite
+  // target.
+  DeclarationName NotEqOp = S.Context.DeclarationNames.getCXXOperatorName(
+      OverloadedOperatorKind::OO_ExclaimEqual);
+  if (auto *MD = dyn_cast<CXXMethodDecl>(EqFD)) {
+    // If F is a class member, search scope is class type of first operand.
+    QualType RHS = FirstOperand->getType();
+    auto *RHSRec = RHS->getAs<RecordType>();
+    if (!RHSRec)
+      return true;
+    LookupResult Members(S, NotEqOp, OpLoc,
+                         Sema::LookupNameKind::LookupMemberName);
+    S.LookupQualifiedName(Members, RHSRec->getDecl());
+    Members.suppressDiagnostics();
+    for (NamedDecl *Op : Members)
+      if (FunctionsCorrespond(S.Context, EqFD, Op->getAsFunction()))
+        return false;
+    return true;
+  }
+  // Otherwise the search scope is the namespace scope of which F is a member.
+  LookupResult NonMembers(S, NotEqOp, OpLoc,
+                          Sema::LookupNameKind::LookupOperatorName);
+  S.LookupName(NonMembers,
+               S.getScopeForContext(EqFD->getEnclosingNamespaceContext()));
+  NonMembers.suppressDiagnostics();
+  for (NamedDecl *Op : NonMembers) {
+    auto *FD = Op->getAsFunction();
+    if(auto* UD = dyn_cast<UsingShadowDecl>(Op))
+      FD = UD->getUnderlyingDecl()->getAsFunction();
+    if (FunctionsCorrespond(S.Context, EqFD, FD) &&
+        declaresSameEntity(cast<Decl>(EqFD->getDeclContext()),
+                           cast<Decl>(Op->getDeclContext())))
+      return false;
+  }
+  return true;
+}
+
+bool OverloadCandidateSet::OperatorRewriteInfo::allowsReversed(
     OverloadedOperatorKind Op) {
   if (!AllowRewrittenCandidates)
     return false;
@@ -898,14 +972,21 @@ bool OverloadCandidateSet::OperatorRewriteInfo::shouldAddReversed(
 }
 
 bool OverloadCandidateSet::OperatorRewriteInfo::shouldAddReversed(
-    ASTContext &Ctx, const FunctionDecl *FD) {
-  if (!shouldAddReversed(FD->getDeclName().getCXXOverloadedOperator()))
+    Sema &S, ArrayRef<Expr *> OriginalArgs, FunctionDecl *FD) {
+  auto Op = FD->getOverloadedOperator();
+  if (!allowsReversed(Op))
     return false;
+  if (Op == OverloadedOperatorKind::OO_EqualEqual) {
+    assert(OriginalArgs.size() == 2);
+    if (!shouldAddReversedEqEq(
+            S, OpLoc, /*FirstOperand in reversed args*/ OriginalArgs[1], FD))
+      return false;
+  }
   // Don't bother adding a reversed candidate that can never be a better
   // match than the non-reversed version.
   return FD->getNumParams() != 2 ||
-         !Ctx.hasSameUnqualifiedType(FD->getParamDecl(0)->getType(),
-                                     FD->getParamDecl(1)->getType()) ||
+         !S.Context.hasSameUnqualifiedType(FD->getParamDecl(0)->getType(),
+                                           FD->getParamDecl(1)->getType()) ||
          FD->hasAttr<EnableIfAttr>();
 }
 
@@ -7749,7 +7830,7 @@ void Sema::AddNonMemberOperatorCandidates(
     if (FunTmpl) {
       AddTemplateOverloadCandidate(FunTmpl, F.getPair(), ExplicitTemplateArgs,
                                    FunctionArgs, CandidateSet);
-      if (CandidateSet.getRewriteInfo().shouldAddReversed(Context, FD))
+      if (CandidateSet.getRewriteInfo().shouldAddReversed(*this, Args, FD))
         AddTemplateOverloadCandidate(
             FunTmpl, F.getPair(), ExplicitTemplateArgs,
             {FunctionArgs[1], FunctionArgs[0]}, CandidateSet, false, false,
@@ -7758,7 +7839,7 @@ void Sema::AddNonMemberOperatorCandidates(
       if (ExplicitTemplateArgs)
         continue;
       AddOverloadCandidate(FD, F.getPair(), FunctionArgs, CandidateSet);
-      if (CandidateSet.getRewriteInfo().shouldAddReversed(Context, FD))
+      if (CandidateSet.getRewriteInfo().shouldAddReversed(*this, Args, FD))
         AddOverloadCandidate(FD, F.getPair(),
                              {FunctionArgs[1], FunctionArgs[0]}, CandidateSet,
                              false, false, true, false, ADLCallKind::NotADL,
@@ -7809,12 +7890,17 @@ void Sema::AddMemberOperatorCandidates(OverloadedOperatorKind Op,
     Operators.suppressDiagnostics();
 
     for (LookupResult::iterator Oper = Operators.begin(),
-                             OperEnd = Operators.end();
-         Oper != OperEnd;
-         ++Oper)
+                                OperEnd = Operators.end();
+         Oper != OperEnd; ++Oper) {
+      if (Oper->getAsFunction() &&
+          PO == OverloadCandidateParamOrder::Reversed &&
+          !CandidateSet.getRewriteInfo().shouldAddReversed(
+              *this, {Args[1], Args[0]}, Oper->getAsFunction()))
+        continue;
       AddMethodCandidate(Oper.getPair(), Args[0]->getType(),
                          Args[0]->Classify(Context), Args.slice(1),
                          CandidateSet, /*SuppressUserConversion=*/false, PO);
+    }
   }
 }
 
@@ -9510,7 +9596,7 @@ Sema::AddArgumentDependentLookupCandidates(DeclarationName Name,
           FD, FoundDecl, Args, CandidateSet, /*SuppressUserConversions=*/false,
           PartialOverloading, /*AllowExplicit=*/true,
           /*AllowExplicitConversion=*/false, ADLCallKind::UsesADL);
-      if (CandidateSet.getRewriteInfo().shouldAddReversed(Context, FD)) {
+      if (CandidateSet.getRewriteInfo().shouldAddReversed(*this, Args, FD)) {
         AddOverloadCandidate(
             FD, FoundDecl, {Args[1], Args[0]}, CandidateSet,
             /*SuppressUserConversions=*/false, PartialOverloading,
@@ -9524,7 +9610,7 @@ Sema::AddArgumentDependentLookupCandidates(DeclarationName Name,
           /*SuppressUserConversions=*/false, PartialOverloading,
           /*AllowExplicit=*/true, ADLCallKind::UsesADL);
       if (CandidateSet.getRewriteInfo().shouldAddReversed(
-              Context, FTD->getTemplatedDecl())) {
+              *this, Args, FTD->getTemplatedDecl())) {
         AddTemplateOverloadCandidate(
             FTD, FoundDecl, ExplicitTemplateArgs, {Args[1], Args[0]},
             CandidateSet, /*SuppressUserConversions=*/false, PartialOverloading,
@@ -13637,14 +13723,14 @@ void Sema::LookupOverloadedBinOp(OverloadCandidateSet &CandidateSet,
 
   // Add operator candidates that are member functions.
   AddMemberOperatorCandidates(Op, OpLoc, Args, CandidateSet);
-  if (CandidateSet.getRewriteInfo().shouldAddReversed(Op))
+  if (CandidateSet.getRewriteInfo().allowsReversed(Op))
     AddMemberOperatorCandidates(Op, OpLoc, {Args[1], Args[0]}, CandidateSet,
                                 OverloadCandidateParamOrder::Reversed);
 
   // In C++20, also add any rewritten member candidates.
   if (ExtraOp) {
     AddMemberOperatorCandidates(ExtraOp, OpLoc, Args, CandidateSet);
-    if (CandidateSet.getRewriteInfo().shouldAddReversed(ExtraOp))
+    if (CandidateSet.getRewriteInfo().allowsReversed(ExtraOp))
       AddMemberOperatorCandidates(ExtraOp, OpLoc, {Args[1], Args[0]},
                                   CandidateSet,
                                   OverloadCandidateParamOrder::Reversed);
@@ -13775,9 +13861,9 @@ ExprResult Sema::CreateOverloadedBinOp(SourceLocation OpLoc,
     return CreateBuiltinBinOp(OpLoc, Opc, Args[0], Args[1]);
 
   // Build the overload set.
-  OverloadCandidateSet CandidateSet(
-      OpLoc, OverloadCandidateSet::CSK_Operator,
-      OverloadCandidateSet::OperatorRewriteInfo(Op, AllowRewrittenCandidates));
+  OverloadCandidateSet CandidateSet(OpLoc, OverloadCandidateSet::CSK_Operator,
+                                    OverloadCandidateSet::OperatorRewriteInfo(
+                                        Op, OpLoc, AllowRewrittenCandidates));
   if (DefaultedFn)
     CandidateSet.exclude(DefaultedFn);
   LookupOverloadedBinOp(CandidateSet, Op, Fns, Args, PerformADL);
@@ -13852,6 +13938,22 @@ ExprResult Sema::CreateOverloadedBinOp(SourceLocation OpLoc,
             if (AmbiguousWithSelf) {
               Diag(FnDecl->getLocation(),
                    diag::note_ovl_ambiguous_oper_binary_reversed_self);
+              // Mark member== const or provide matching != to disallow reversed
+              // args. Eg. 
+              // struct S { bool operator==(const S&); }; 
+              // S()==S();
+              if (auto *MD = dyn_cast<CXXMethodDecl>(FnDecl))
+                if (Op == OverloadedOperatorKind::OO_EqualEqual &&
+                    !MD->isConst() &&
+                    Context.hasSameUnqualifiedType(
+                        MD->getThisObjectType(),
+                        MD->getParamDecl(0)->getType().getNonReferenceType()) &&
+                    Context.hasSameUnqualifiedType(MD->getThisObjectType(),
+                                                   Args[0]->getType()) &&
+                    Context.hasSameUnqualifiedType(MD->getThisObjectType(),
+                                                   Args[1]->getType()))
+                  Diag(FnDecl->getLocation(),
+                       diag::note_ovl_ambiguous_eqeq_reversed_self_non_const);
             } else {
               Diag(FnDecl->getLocation(),
                    diag::note_ovl_ambiguous_oper_binary_selected_candidate);

diff  --git a/clang/test/CXX/over/over.match/over.match.funcs/over.match.oper/p3-2a.cpp b/clang/test/CXX/over/over.match/over.match.funcs/over.match.oper/p3-2a.cpp
index 0ba3a1e56ca4f..e5b271cb98724 100644
--- a/clang/test/CXX/over/over.match/over.match.funcs/over.match.oper/p3-2a.cpp
+++ b/clang/test/CXX/over/over.match/over.match.funcs/over.match.oper/p3-2a.cpp
@@ -125,46 +125,204 @@ namespace PR44627 {
   bool b2 = 0 == ADL::type();
 }
 
-// Various C++17 cases that are known to be broken by the C++20 rules.
-namespace problem_cases {
-  // We can have an ambiguity between an operator and its reversed form. This
-  // wasn't intended by the original "consistent comparison" proposal, and we
-  // allow it as extension, picking the non-reversed form.
-  struct A {
-    bool operator==(const A&); // expected-note {{ambiguity is between a regular call to this operator and a call with the argument order reversed}}
-  };
-  bool cmp_non_const = A() == A(); // expected-warning {{ambiguous}}
+namespace P2468R2 {
+// Problem cases prior to P2468R2 but now intentionally rejected.
+struct SymmetricNonConst {
+  bool operator==(const SymmetricNonConst&); // expected-note {{ambiguity is between a regular call to this operator and a call with the argument order reversed}}
+  // expected-note at -1 {{mark 'operator==' as const or add a matching 'operator!=' to resolve the ambiguity}}
+};
+bool cmp_non_const = SymmetricNonConst() == SymmetricNonConst(); // expected-warning {{ambiguous}}
 
-  struct B {
-    virtual bool operator==(const B&) const;
-  };
-  struct D : B {
-    bool operator==(const B&) const override; // expected-note {{operator}}
-  };
-  bool cmp_base_derived = D() == D(); // expected-warning {{ambiguous}}
+struct SymmetricConst {
+  bool operator==(const SymmetricConst&) const;
+};
+bool cmp_const = SymmetricConst() == SymmetricConst();
 
-  template<typename T> struct CRTPBase {
-    bool operator==(const T&) const; // expected-note {{operator}} expected-note {{reversed}}
-    bool operator!=(const T&) const; // expected-note {{non-reversed}}
-  };
-  struct CRTP : CRTPBase<CRTP> {};
-  bool cmp_crtp = CRTP() == CRTP(); // expected-warning-re {{ambiguous despite there being a unique best viable function{{$}}}}}}
-  bool cmp_crtp2 = CRTP() != CRTP(); // expected-warning {{ambiguous despite there being a unique best viable function with non-reversed arguments}}
-
-  // Given a choice between a rewritten and non-rewritten function with the
-  // same parameter types, where the rewritten function is reversed and each
-  // has a better conversion for one of the two arguments, prefer the
-  // non-rewritten one.
-  using UBool = signed char; // ICU uses this.
-  struct ICUBase {
-    virtual UBool operator==(const ICUBase&) const;
-    UBool operator!=(const ICUBase &arg) const { return !operator==(arg); }
-  };
-  struct ICUDerived : ICUBase {
-    UBool operator==(const ICUBase&) const override; // expected-note {{declared here}} expected-note {{ambiguity is between}}
-  };
-  bool cmp_icu = ICUDerived() != ICUDerived(); // expected-warning {{ambiguous}} expected-warning {{'bool', not 'UBool'}}
+struct SymmetricNonConstWithoutConstRef {
+  bool operator==(SymmetricNonConstWithoutConstRef);
+};
+bool cmp_non_const_wo_ref = SymmetricNonConstWithoutConstRef() == SymmetricNonConstWithoutConstRef();
+
+struct B {
+  virtual bool operator==(const B&) const;
+};
+struct D : B {
+  bool operator==(const B&) const override; // expected-note {{operator}}
+};
+bool cmp_base_derived = D() == D(); // expected-warning {{ambiguous}}
+
+// Reversed "3" not used because we find "2".
+// Rewrite != from "3" but warn that "chosen rewritten candidate must return cv-bool".
+using UBool = signed char;
+struct ICUBase {
+  virtual UBool operator==(const ICUBase&) const; // 1.
+  UBool operator!=(const ICUBase &arg) const { return !operator==(arg); } // 2.
+};
+struct ICUDerived : ICUBase {
+  // 3.
+  UBool operator==(const ICUBase&) const override; // expected-note {{declared here}}
+};
+bool cmp_icu = ICUDerived() != ICUDerived(); // expected-warning {{ISO C++20 requires return type of selected 'operator==' function for rewritten '!=' comparison to be 'bool', not 'UBool' (aka 'signed char')}}
+// Accepted by P2468R2.
+// 1
+struct S {
+  bool operator==(const S&) { return true; }
+  bool operator!=(const S&) { return false; }
+};
+bool ts = S{} != S{};
+// 2
+template<typename T> struct CRTPBase {
+  bool operator==(const T&) const;
+  bool operator!=(const T&) const;
+};
+struct CRTP : CRTPBase<CRTP> {};
+bool cmp_crtp = CRTP() == CRTP();
+bool cmp_crtp2 = CRTP() != CRTP();
+// https://github.com/llvm/llvm-project/issues/57711
+namespace issue_57711 {
+template <class T>
+bool compare(T l, T r)
+    requires requires { l == r; } {
+  return l == r;
+}
+
+void test() {
+  compare(CRTP(), CRTP()); // previously this was a hard error (due to SFINAE failure).
+}
+}
+// 3
+template <bool>
+struct GenericIterator {
+  using ConstIterator = GenericIterator<true>;
+  using NonConstIterator = GenericIterator<false>;
+  GenericIterator() = default;
+  GenericIterator(const NonConstIterator&);
+
+  bool operator==(ConstIterator) const;
+  bool operator!=(ConstIterator) const;
+};
+using Iterator = GenericIterator<false>;
+
+bool biter = Iterator{} == Iterator{};
+
+// Intentionally rejected by P2468R2
+struct ImplicitInt {
+  ImplicitInt();
+  ImplicitInt(int*);
+  bool operator==(const ImplicitInt&) const; // expected-note {{candidate function (with reversed parameter order)}}
+  operator int*() const;
+};
+bool implicit_int = nullptr != ImplicitInt{}; // expected-error {{use of overloaded operator '!=' is ambiguous (with operand types 'std::nullptr_t' and 'ImplicitInt')}}
+                                              // expected-note at -1 4 {{built-in candidate operator!=}}
+
+// https://eel.is/c++draft/over.match.oper#example-2
+namespace example {
+struct A {};
+template<typename T> bool operator==(A, T);     // 1. expected-note {{candidate function template not viable: no known conversion from 'int' to 'A' for 1st argument}}
+bool a1 = 0 == A();                             // OK, calls reversed 1
+template<typename T> bool operator!=(A, T);
+bool a2 = 0 == A();  // expected-error {{invalid operands to binary expression ('int' and 'A')}}
+
+struct B {
+  bool operator==(const B&);    // 2
+  // expected-note at -1 {{ambiguity is between a regular call to this operator and a call with the argument order reversed}}
+};
+struct C : B {
+  C();
+  C(B);
+  bool operator!=(const B&);    // 3
+};
+bool c1 = B() == C(); // OK, calls 2; reversed 2 is not a candidate because search for operator!= in C finds 3
+bool c2 = C() == B();  // Search for operator!= inside B never finds 3. expected-warning {{ISO C++20 considers use of overloaded operator '==' (with operand types 'C' and 'B') to be ambiguous despite there being a unique best viable function}}
+
+struct D {};
+template<typename T> bool operator==(D, T);     // 4
+inline namespace N {
+  template<typename T> bool operator!=(D, T);   // 5
+}
+bool d1 = 0 == D();  // OK, calls reversed 4; 5 does not forbid 4 as a rewrite target as "search" does not look inside inline namespaces.
+} // namespace example
+
+namespace template_tests {
+namespace template_head_does_not_match {
+struct A {};
+template<typename T, class U = int> bool operator==(A, T);
+template <class T> bool operator!=(A, T);
+bool x = 0 == A(); // Ok. Use rewritten candidate.
+}
+
+namespace template_with_
diff erent_param_name_are_equivalent {
+struct A {};
+template<typename T> bool operator==(A, T); // expected-note {{candidate function template not viable: no known conversion from 'int' to 'A' for 1st argument}}
+template <typename U> bool operator!=(A, U);
+bool x = 0 == A(); // expected-error {{invalid operands to binary expression ('int' and 'A')}}
+}
+
+namespace template_and_non_template {
+struct A {
+template<typename T> bool operator==(const T&);
+// expected-note at -1{{mark 'operator==' as const or add a matching 'operator!=' to resolve the ambiguity}}
+// expected-note at -2{{ambiguity is between a regular call to this operator and a call with the argument order reversed}}
+};
+bool a = A() == A(); // expected-warning {{ambiguous despite there being a unique best viable function}}
+
+struct B {
+template<typename T> bool operator==(const T&) const;
+bool operator!=(const B&);
+};
+bool b = B() == B(); // ok. No rewrite due to const.
+
+struct C {};
+template <class T=int>
+bool operator==(C, int);
+bool operator!=(C, int);
+bool c = 0 == C(); // Ok. Use rewritten candidate as the non-template 'operator!=' does not correspond to template 'operator=='
+}
+} // template_tests
+
+namespace using_decls {
+namespace simple {
+struct C {};
+bool operator==(C, int); // expected-note {{candidate function not viable: no known conversion from 'int' to 'C' for 1st argument}}
+bool a = 0 == C(); // Ok. Use rewritten candidate.
+namespace other_ns { bool operator!=(C, int); }
+bool b = 0 == C(); // Ok. Use rewritten candidate.
+using other_ns::operator!=;
+bool c = 0 == C(); // Rewrite not possible. expected-error {{invalid operands to binary expression ('int' and 'C')}}
+}
+namespace templated {
+struct C {};
+template<typename T>
+bool operator==(C, T); // expected-note {{candidate function template not viable: no known conversion from 'int' to 'C' for 1st argument}}
+bool a = 0 == C(); // Ok. Use rewritten candidate.
+namespace other_ns { template<typename T> bool operator!=(C, T); }
+bool b = 0 == C(); // Ok. Use rewritten candidate.
+using other_ns::operator!=;
+bool c = 0 == C(); // Rewrite not possible. expected-error {{invalid operands to binary expression ('int' and 'C')}}
+} // templated
+} // using_decls
+
+// FIXME: Match requires clause.
+namespace match_requires_clause {
+template<int x>
+struct A {
+bool operator==(int) requires (x==1); // 1.
+bool operator!=(int) requires (x==2); // 2.
+};
+int a1 = 0 == A<1>(); // Should not find 2 as the requires clause does not match. \
+                      // expected-error {{invalid operands to binary expression ('int' and 'A<1>')}}
+}
+
+namespace static_operators {
+// Verify no crash.
+struct X { 
+  bool operator ==(X const&); // expected-note {{ambiguity is between a regular call}}
+                              // expected-note at -1 {{mark 'operator==' as const or add a matching 'operator!=' to resolve the ambiguity}}
+  static bool operator !=(X const&, X const&); // expected-error {{overloaded 'operator!=' cannot be a static member function}}
+};
+bool x = X() == X(); // expected-warning {{ambiguous}}
 }
+} // namespace P2468R2
 
 #else // NO_ERRORS
 


        


More information about the cfe-commits mailing list