[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