Skip to content

Commit 38b9d31

Browse files
committed
[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
1 parent 956f7f2 commit 38b9d31

File tree

6 files changed

+333
-64
lines changed

6 files changed

+333
-64
lines changed

clang/docs/ReleaseNotes.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -415,6 +415,7 @@ C++20 Feature Support
415415
name is found via ordinary lookup so typedefs are found.
416416
- Implemented `P0634r3 <https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2018/p0634r3.html>`_,
417417
which removes the requirement for the ``typename`` keyword in certain contexts.
418+
- Implemented The Equality Operator You Are Looking For (`P2468 <http://wg21.link/p2468r2>`_).
418419

419420
C++2b Feature Support
420421
^^^^^^^^^^^^^^^^^^^^^

clang/include/clang/Basic/DiagnosticSemaKinds.td

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4703,6 +4703,8 @@ def ext_ovl_ambiguous_oper_binary_reversed : ExtWarn<
47034703
def note_ovl_ambiguous_oper_binary_reversed_self : Note<
47044704
"ambiguity is between a regular call to this operator and a call with the "
47054705
"argument order reversed">;
4706+
def note_ovl_ambiguous_eqeq_reversed_self_non_const : Note<
4707+
"mark 'operator==' as const or add a matching 'operator!=' to resolve the ambiguity">;
47064708
def note_ovl_ambiguous_oper_binary_selected_candidate : Note<
47074709
"candidate function with non-reversed arguments">;
47084710
def note_ovl_ambiguous_oper_binary_reversed_candidate : Note<

clang/include/clang/Sema/Overload.h

Lines changed: 14 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -977,12 +977,16 @@ class Sema;
977977
/// functions to a candidate set.
978978
struct OperatorRewriteInfo {
979979
OperatorRewriteInfo()
980-
: OriginalOperator(OO_None), AllowRewrittenCandidates(false) {}
981-
OperatorRewriteInfo(OverloadedOperatorKind Op, bool AllowRewritten)
982-
: OriginalOperator(Op), AllowRewrittenCandidates(AllowRewritten) {}
980+
: OriginalOperator(OO_None), OpLoc(), AllowRewrittenCandidates(false) {}
981+
OperatorRewriteInfo(OverloadedOperatorKind Op, SourceLocation OpLoc,
982+
bool AllowRewritten)
983+
: OriginalOperator(Op), OpLoc(OpLoc),
984+
AllowRewrittenCandidates(AllowRewritten) {}
983985

984986
/// The original operator as written in the source.
985987
OverloadedOperatorKind OriginalOperator;
988+
/// The source location of the operator.
989+
SourceLocation OpLoc;
986990
/// Whether we should include rewritten candidates in the overload set.
987991
bool AllowRewrittenCandidates;
988992

@@ -1018,22 +1022,23 @@ class Sema;
10181022
CRK = OverloadCandidateRewriteKind(CRK | CRK_Reversed);
10191023
return CRK;
10201024
}
1021-
10221025
/// Determines whether this operator could be implemented by a function
10231026
/// with reversed parameter order.
10241027
bool isReversible() {
10251028
return AllowRewrittenCandidates && OriginalOperator &&
10261029
(getRewrittenOverloadedOperator(OriginalOperator) != OO_None ||
1027-
shouldAddReversed(OriginalOperator));
1030+
allowsReversed(OriginalOperator));
10281031
}
10291032

1030-
/// Determine whether we should consider looking for and adding reversed
1031-
/// candidates for operator Op.
1032-
bool shouldAddReversed(OverloadedOperatorKind Op);
1033+
/// Determine whether reversing parameter order is allowed for operator
1034+
/// Op.
1035+
bool allowsReversed(OverloadedOperatorKind Op);
10331036

10341037
/// Determine whether we should add a rewritten candidate for \p FD with
10351038
/// reversed parameter order.
1036-
bool shouldAddReversed(ASTContext &Ctx, const FunctionDecl *FD);
1039+
/// \param OriginalArgs are the original non reversed arguments.
1040+
bool shouldAddReversed(Sema &S, ArrayRef<Expr *> OriginalArgs,
1041+
FunctionDecl *FD);
10371042
};
10381043

10391044
private:

clang/lib/Sema/SemaDeclCXX.cpp

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7879,7 +7879,8 @@ class DefaultedComparisonAnalyzer
78797879
OverloadCandidateSet CandidateSet(
78807880
FD->getLocation(), OverloadCandidateSet::CSK_Operator,
78817881
OverloadCandidateSet::OperatorRewriteInfo(
7882-
OO, /*AllowRewrittenCandidates=*/!SpaceshipCandidates));
7882+
OO, FD->getLocation(),
7883+
/*AllowRewrittenCandidates=*/!SpaceshipCandidates));
78837884

78847885
/// C++2a [class.compare.default]p1 [P2002R0]:
78857886
/// [...] the defaulted function itself is never a candidate for overload

clang/lib/Sema/SemaOverload.cpp

Lines changed: 119 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -12,14 +12,17 @@
1212

1313
#include "clang/AST/ASTContext.h"
1414
#include "clang/AST/CXXInheritance.h"
15+
#include "clang/AST/DeclCXX.h"
1516
#include "clang/AST/DeclObjC.h"
1617
#include "clang/AST/DependenceFlags.h"
1718
#include "clang/AST/Expr.h"
1819
#include "clang/AST/ExprCXX.h"
1920
#include "clang/AST/ExprObjC.h"
21+
#include "clang/AST/Type.h"
2022
#include "clang/AST/TypeOrdering.h"
2123
#include "clang/Basic/Diagnostic.h"
2224
#include "clang/Basic/DiagnosticOptions.h"
25+
#include "clang/Basic/OperatorKinds.h"
2326
#include "clang/Basic/PartialDiagnostic.h"
2427
#include "clang/Basic/SourceManager.h"
2528
#include "clang/Basic/TargetInfo.h"
@@ -34,6 +37,7 @@
3437
#include "llvm/ADT/STLExtras.h"
3538
#include "llvm/ADT/SmallPtrSet.h"
3639
#include "llvm/ADT/SmallString.h"
40+
#include "llvm/Support/Casting.h"
3741
#include <algorithm>
3842
#include <cstdlib>
3943

@@ -890,22 +894,99 @@ llvm::Optional<unsigned> DeductionFailureInfo::getCallArgIndex() {
890894
}
891895
}
892896

893-
bool OverloadCandidateSet::OperatorRewriteInfo::shouldAddReversed(
897+
static bool FunctionsCorrespond(ASTContext &Ctx, const FunctionDecl *X,
898+
const FunctionDecl *Y) {
899+
if (!X || !Y)
900+
return false;
901+
if (X->getNumParams() != Y->getNumParams())
902+
return false;
903+
for (unsigned I = 0; I < X->getNumParams(); ++I)
904+
if (!Ctx.hasSameUnqualifiedType(X->getParamDecl(I)->getType(),
905+
Y->getParamDecl(I)->getType()))
906+
return false;
907+
if (auto *FTX = X->getDescribedFunctionTemplate()) {
908+
auto *FTY = Y->getDescribedFunctionTemplate();
909+
if (!FTY)
910+
return false;
911+
if (!Ctx.isSameTemplateParameterList(FTX->getTemplateParameters(),
912+
FTY->getTemplateParameters()))
913+
return false;
914+
}
915+
return true;
916+
}
917+
918+
static bool shouldAddReversedEqEq(Sema &S, SourceLocation OpLoc,
919+
Expr *FirstOperand, FunctionDecl *EqFD) {
920+
assert(EqFD->getOverloadedOperator() ==
921+
OverloadedOperatorKind::OO_EqualEqual);
922+
// C++2a [over.match.oper]p4:
923+
// A non-template function or function template F named operator== is a
924+
// rewrite target with first operand o unless a search for the name operator!=
925+
// in the scope S from the instantiation context of the operator expression
926+
// finds a function or function template that would correspond
927+
// ([basic.scope.scope]) to F if its name were operator==, where S is the
928+
// scope of the class type of o if F is a class member, and the namespace
929+
// scope of which F is a member otherwise. A function template specialization
930+
// named operator== is a rewrite target if its function template is a rewrite
931+
// target.
932+
DeclarationName NotEqOp = S.Context.DeclarationNames.getCXXOperatorName(
933+
OverloadedOperatorKind::OO_ExclaimEqual);
934+
if (auto *MD = dyn_cast<CXXMethodDecl>(EqFD)) {
935+
// If F is a class member, search scope is class type of first operand.
936+
QualType RHS = FirstOperand->getType();
937+
auto *RHSRec = RHS->getAs<RecordType>();
938+
if (!RHSRec)
939+
return true;
940+
LookupResult Members(S, NotEqOp, OpLoc,
941+
Sema::LookupNameKind::LookupMemberName);
942+
S.LookupQualifiedName(Members, RHSRec->getDecl());
943+
Members.suppressDiagnostics();
944+
for (NamedDecl *Op : Members)
945+
if (FunctionsCorrespond(S.Context, EqFD, Op->getAsFunction()))
946+
return false;
947+
return true;
948+
}
949+
// Otherwise the search scope is the namespace scope of which F is a member.
950+
LookupResult NonMembers(S, NotEqOp, OpLoc,
951+
Sema::LookupNameKind::LookupOperatorName);
952+
S.LookupName(NonMembers,
953+
S.getScopeForContext(EqFD->getEnclosingNamespaceContext()));
954+
NonMembers.suppressDiagnostics();
955+
for (NamedDecl *Op : NonMembers) {
956+
auto *FD = Op->getAsFunction();
957+
if(auto* UD = dyn_cast<UsingShadowDecl>(Op))
958+
FD = UD->getUnderlyingDecl()->getAsFunction();
959+
if (FunctionsCorrespond(S.Context, EqFD, FD) &&
960+
declaresSameEntity(cast<Decl>(EqFD->getDeclContext()),
961+
cast<Decl>(Op->getDeclContext())))
962+
return false;
963+
}
964+
return true;
965+
}
966+
967+
bool OverloadCandidateSet::OperatorRewriteInfo::allowsReversed(
894968
OverloadedOperatorKind Op) {
895969
if (!AllowRewrittenCandidates)
896970
return false;
897971
return Op == OO_EqualEqual || Op == OO_Spaceship;
898972
}
899973

900974
bool OverloadCandidateSet::OperatorRewriteInfo::shouldAddReversed(
901-
ASTContext &Ctx, const FunctionDecl *FD) {
902-
if (!shouldAddReversed(FD->getDeclName().getCXXOverloadedOperator()))
975+
Sema &S, ArrayRef<Expr *> OriginalArgs, FunctionDecl *FD) {
976+
auto Op = FD->getOverloadedOperator();
977+
if (!allowsReversed(Op))
903978
return false;
979+
if (Op == OverloadedOperatorKind::OO_EqualEqual) {
980+
assert(OriginalArgs.size() == 2);
981+
if (!shouldAddReversedEqEq(
982+
S, OpLoc, /*FirstOperand in reversed args*/ OriginalArgs[1], FD))
983+
return false;
984+
}
904985
// Don't bother adding a reversed candidate that can never be a better
905986
// match than the non-reversed version.
906987
return FD->getNumParams() != 2 ||
907-
!Ctx.hasSameUnqualifiedType(FD->getParamDecl(0)->getType(),
908-
FD->getParamDecl(1)->getType()) ||
988+
!S.Context.hasSameUnqualifiedType(FD->getParamDecl(0)->getType(),
989+
FD->getParamDecl(1)->getType()) ||
909990
FD->hasAttr<EnableIfAttr>();
910991
}
911992

@@ -7749,7 +7830,7 @@ void Sema::AddNonMemberOperatorCandidates(
77497830
if (FunTmpl) {
77507831
AddTemplateOverloadCandidate(FunTmpl, F.getPair(), ExplicitTemplateArgs,
77517832
FunctionArgs, CandidateSet);
7752-
if (CandidateSet.getRewriteInfo().shouldAddReversed(Context, FD))
7833+
if (CandidateSet.getRewriteInfo().shouldAddReversed(*this, Args, FD))
77537834
AddTemplateOverloadCandidate(
77547835
FunTmpl, F.getPair(), ExplicitTemplateArgs,
77557836
{FunctionArgs[1], FunctionArgs[0]}, CandidateSet, false, false,
@@ -7758,7 +7839,7 @@ void Sema::AddNonMemberOperatorCandidates(
77587839
if (ExplicitTemplateArgs)
77597840
continue;
77607841
AddOverloadCandidate(FD, F.getPair(), FunctionArgs, CandidateSet);
7761-
if (CandidateSet.getRewriteInfo().shouldAddReversed(Context, FD))
7842+
if (CandidateSet.getRewriteInfo().shouldAddReversed(*this, Args, FD))
77627843
AddOverloadCandidate(FD, F.getPair(),
77637844
{FunctionArgs[1], FunctionArgs[0]}, CandidateSet,
77647845
false, false, true, false, ADLCallKind::NotADL,
@@ -7809,12 +7890,17 @@ void Sema::AddMemberOperatorCandidates(OverloadedOperatorKind Op,
78097890
Operators.suppressDiagnostics();
78107891

78117892
for (LookupResult::iterator Oper = Operators.begin(),
7812-
OperEnd = Operators.end();
7813-
Oper != OperEnd;
7814-
++Oper)
7893+
OperEnd = Operators.end();
7894+
Oper != OperEnd; ++Oper) {
7895+
if (Oper->getAsFunction() &&
7896+
PO == OverloadCandidateParamOrder::Reversed &&
7897+
!CandidateSet.getRewriteInfo().shouldAddReversed(
7898+
*this, {Args[1], Args[0]}, Oper->getAsFunction()))
7899+
continue;
78157900
AddMethodCandidate(Oper.getPair(), Args[0]->getType(),
78167901
Args[0]->Classify(Context), Args.slice(1),
78177902
CandidateSet, /*SuppressUserConversion=*/false, PO);
7903+
}
78187904
}
78197905
}
78207906

@@ -9510,7 +9596,7 @@ Sema::AddArgumentDependentLookupCandidates(DeclarationName Name,
95109596
FD, FoundDecl, Args, CandidateSet, /*SuppressUserConversions=*/false,
95119597
PartialOverloading, /*AllowExplicit=*/true,
95129598
/*AllowExplicitConversion=*/false, ADLCallKind::UsesADL);
9513-
if (CandidateSet.getRewriteInfo().shouldAddReversed(Context, FD)) {
9599+
if (CandidateSet.getRewriteInfo().shouldAddReversed(*this, Args, FD)) {
95149600
AddOverloadCandidate(
95159601
FD, FoundDecl, {Args[1], Args[0]}, CandidateSet,
95169602
/*SuppressUserConversions=*/false, PartialOverloading,
@@ -9524,7 +9610,7 @@ Sema::AddArgumentDependentLookupCandidates(DeclarationName Name,
95249610
/*SuppressUserConversions=*/false, PartialOverloading,
95259611
/*AllowExplicit=*/true, ADLCallKind::UsesADL);
95269612
if (CandidateSet.getRewriteInfo().shouldAddReversed(
9527-
Context, FTD->getTemplatedDecl())) {
9613+
*this, Args, FTD->getTemplatedDecl())) {
95289614
AddTemplateOverloadCandidate(
95299615
FTD, FoundDecl, ExplicitTemplateArgs, {Args[1], Args[0]},
95309616
CandidateSet, /*SuppressUserConversions=*/false, PartialOverloading,
@@ -13637,14 +13723,14 @@ void Sema::LookupOverloadedBinOp(OverloadCandidateSet &CandidateSet,
1363713723

1363813724
// Add operator candidates that are member functions.
1363913725
AddMemberOperatorCandidates(Op, OpLoc, Args, CandidateSet);
13640-
if (CandidateSet.getRewriteInfo().shouldAddReversed(Op))
13726+
if (CandidateSet.getRewriteInfo().allowsReversed(Op))
1364113727
AddMemberOperatorCandidates(Op, OpLoc, {Args[1], Args[0]}, CandidateSet,
1364213728
OverloadCandidateParamOrder::Reversed);
1364313729

1364413730
// In C++20, also add any rewritten member candidates.
1364513731
if (ExtraOp) {
1364613732
AddMemberOperatorCandidates(ExtraOp, OpLoc, Args, CandidateSet);
13647-
if (CandidateSet.getRewriteInfo().shouldAddReversed(ExtraOp))
13733+
if (CandidateSet.getRewriteInfo().allowsReversed(ExtraOp))
1364813734
AddMemberOperatorCandidates(ExtraOp, OpLoc, {Args[1], Args[0]},
1364913735
CandidateSet,
1365013736
OverloadCandidateParamOrder::Reversed);
@@ -13775,9 +13861,9 @@ ExprResult Sema::CreateOverloadedBinOp(SourceLocation OpLoc,
1377513861
return CreateBuiltinBinOp(OpLoc, Opc, Args[0], Args[1]);
1377613862

1377713863
// Build the overload set.
13778-
OverloadCandidateSet CandidateSet(
13779-
OpLoc, OverloadCandidateSet::CSK_Operator,
13780-
OverloadCandidateSet::OperatorRewriteInfo(Op, AllowRewrittenCandidates));
13864+
OverloadCandidateSet CandidateSet(OpLoc, OverloadCandidateSet::CSK_Operator,
13865+
OverloadCandidateSet::OperatorRewriteInfo(
13866+
Op, OpLoc, AllowRewrittenCandidates));
1378113867
if (DefaultedFn)
1378213868
CandidateSet.exclude(DefaultedFn);
1378313869
LookupOverloadedBinOp(CandidateSet, Op, Fns, Args, PerformADL);
@@ -13852,6 +13938,22 @@ ExprResult Sema::CreateOverloadedBinOp(SourceLocation OpLoc,
1385213938
if (AmbiguousWithSelf) {
1385313939
Diag(FnDecl->getLocation(),
1385413940
diag::note_ovl_ambiguous_oper_binary_reversed_self);
13941+
// Mark member== const or provide matching != to disallow reversed
13942+
// args. Eg.
13943+
// struct S { bool operator==(const S&); };
13944+
// S()==S();
13945+
if (auto *MD = dyn_cast<CXXMethodDecl>(FnDecl))
13946+
if (Op == OverloadedOperatorKind::OO_EqualEqual &&
13947+
!MD->isConst() &&
13948+
Context.hasSameUnqualifiedType(
13949+
MD->getThisObjectType(),
13950+
MD->getParamDecl(0)->getType().getNonReferenceType()) &&
13951+
Context.hasSameUnqualifiedType(MD->getThisObjectType(),
13952+
Args[0]->getType()) &&
13953+
Context.hasSameUnqualifiedType(MD->getThisObjectType(),
13954+
Args[1]->getType()))
13955+
Diag(FnDecl->getLocation(),
13956+
diag::note_ovl_ambiguous_eqeq_reversed_self_non_const);
1385513957
} else {
1385613958
Diag(FnDecl->getLocation(),
1385713959
diag::note_ovl_ambiguous_oper_binary_selected_candidate);

0 commit comments

Comments
 (0)