[clang] 5253d91 - [c++20] Determine whether a defaulted comparison should be deleted or
Richard Smith via cfe-commits
cfe-commits at lists.llvm.org
Fri Dec 6 16:33:04 PST 2019
Author: Richard Smith
Date: 2019-12-06T16:32:48-08:00
New Revision: 5253d9138eb31252594f5e14133df731551839c7
URL: https://github.com/llvm/llvm-project/commit/5253d9138eb31252594f5e14133df731551839c7
DIFF: https://github.com/llvm/llvm-project/commit/5253d9138eb31252594f5e14133df731551839c7.diff
LOG: [c++20] Determine whether a defaulted comparison should be deleted or
constexpr.
Added:
clang/test/CXX/class/class.compare/class.compare.default/p3.cpp
clang/test/CXX/class/class.compare/class.eq/p2.cpp
clang/test/CXX/class/class.compare/class.rel/p2.cpp
clang/test/CXX/class/class.compare/class.spaceship/p1.cpp
Modified:
clang/include/clang/AST/ComparisonCategories.h
clang/include/clang/Basic/DiagnosticGroups.td
clang/include/clang/Basic/DiagnosticSemaKinds.td
clang/include/clang/Sema/Overload.h
clang/include/clang/Sema/Sema.h
clang/lib/Sema/SemaDeclCXX.cpp
clang/lib/Sema/SemaExpr.cpp
clang/lib/Sema/SemaOverload.cpp
clang/test/CXX/class/class.compare/class.compare.default/p2.cpp
clang/test/CXX/class/class.compare/class.eq/p1.cpp
clang/test/CXX/class/class.compare/class.rel/p1.cpp
clang/www/cxx_status.html
Removed:
################################################################################
diff --git a/clang/include/clang/AST/ComparisonCategories.h b/clang/include/clang/AST/ComparisonCategories.h
index 9d591cc81495..3dd1db1f7e9b 100644
--- a/clang/include/clang/AST/ComparisonCategories.h
+++ b/clang/include/clang/AST/ComparisonCategories.h
@@ -221,7 +221,6 @@ class ComparisonCategories {
return const_cast<ComparisonCategoryInfo *>(This.lookupInfo(Kind));
}
-private:
const ComparisonCategoryInfo *lookupInfoForType(QualType Ty) const;
private:
diff --git a/clang/include/clang/Basic/DiagnosticGroups.td b/clang/include/clang/Basic/DiagnosticGroups.td
index e999ba10a003..6cb1a4e0700d 100644
--- a/clang/include/clang/Basic/DiagnosticGroups.td
+++ b/clang/include/clang/Basic/DiagnosticGroups.td
@@ -113,7 +113,7 @@ def UndefinedVarTemplate : DiagGroup<"undefined-var-template">;
def UndefinedFuncTemplate : DiagGroup<"undefined-func-template">;
def MissingNoEscape : DiagGroup<"missing-noescape">;
-def DefaultedComparison : DiagGroup<"defaulted-comparison">;
+def DefaultedFunctionDeleted : DiagGroup<"defaulted-function-deleted">;
def DeleteIncomplete : DiagGroup<"delete-incomplete">;
def DeleteNonAbstractNonVirtualDtor : DiagGroup<"delete-non-abstract-non-virtual-dtor">;
def DeleteAbstractNonVirtualDtor : DiagGroup<"delete-abstract-non-virtual-dtor">;
diff --git a/clang/include/clang/Basic/DiagnosticSemaKinds.td b/clang/include/clang/Basic/DiagnosticSemaKinds.td
index a2e4cf51232b..b6586bedd5c8 100644
--- a/clang/include/clang/Basic/DiagnosticSemaKinds.td
+++ b/clang/include/clang/Basic/DiagnosticSemaKinds.td
@@ -4067,7 +4067,10 @@ def err_ovl_deleted_oper : Error<
"overload resolution selected deleted operator '%0'">;
def err_ovl_deleted_special_oper : Error<
"object of type %0 cannot be %select{constructed|copied|moved|assigned|"
- "assigned|destroyed}1 because its %sub{select_special_member_kind}1 is implicitly deleted">;
+ "assigned|destroyed}1 because its %sub{select_special_member_kind}1 is "
+ "implicitly deleted">;
+def err_ovl_deleted_comparison : Error<
+ "object of type %0 cannot be compared because its %1 is implicitly deleted">;
def err_ovl_rewrite_equalequal_not_bool : Error<
"return type %0 of selected 'operator==' function for rewritten "
"'%1' comparison is not 'bool'">;
@@ -8152,7 +8155,7 @@ def err_incorrect_defaulted_consteval : Error<
"cannot be consteval because implicit definition is not constexpr">;
def warn_defaulted_method_deleted : Warning<
"explicitly defaulted %sub{select_special_member_kind}0 is implicitly "
- "deleted">, InGroup<DiagGroup<"defaulted-function-deleted">>;
+ "deleted">, InGroup<DefaultedFunctionDeleted>;
def err_out_of_line_default_deletes : Error<
"defaulting this %sub{select_special_member_kind}0 "
"would delete it after its first declaration">;
@@ -8194,21 +8197,42 @@ def err_defaulted_comparison_non_const : Error<
def err_defaulted_comparison_return_type_not_bool : Error<
"return type for defaulted %sub{select_defaulted_comparison_kind}0 "
"must be 'bool', not %1">;
-def err_defaulted_comparison_reference_member : Error<
- "cannot default %0 in class %1 with reference member">;
-def ext_defaulted_comparison_reference_member : ExtWarn<
- "ISO C++2a does not allow defaulting %0 in class %1 with reference member">,
- InGroup<DefaultedComparison>;
-def note_reference_member : Note<"reference member %0 declared here">;
-def err_defaulted_comparison_union : Error<
- "cannot default %0 in %select{union-like class|union}1 %2">;
-def ext_defaulted_comparison_union : ExtWarn<
- "ISO C++2a does not allow defaulting %0 in "
- "%select{union-like class|union}1 %2">, InGroup<DefaultedComparison>;
-def ext_defaulted_comparison_empty_union : ExtWarn<
- "ISO C++2a does not allow defaulting %0 in "
- "%select{union-like class|union}1 %2 despite it having no variant members">,
- InGroup<DefaultedComparison>;
+def warn_defaulted_comparison_deleted : Warning<
+ "explicitly defaulted %sub{select_defaulted_comparison_kind}0 is implicitly "
+ "deleted">, InGroup<DefaultedFunctionDeleted>;
+def err_non_first_default_compare_deletes : Error<
+ "defaulting this %sub{select_defaulted_comparison_kind}0 "
+ "would delete it after its first declaration">;
+def note_defaulted_comparison_union : Note<
+ "defaulted %0 is implicitly deleted because "
+ "%2 is a %select{union-like class|union}1 with variant members">;
+def note_defaulted_comparison_reference_member : Note<
+ "defaulted %0 is implicitly deleted because "
+ "class %1 has a reference member">;
+def note_defaulted_comparison_ambiguous : Note<
+ "defaulted %0 is implicitly deleted because implied %select{|'==' |'<' }1"
+ "comparison %select{|for member %3 |for base class %3 }2is ambiguous">;
+def note_defaulted_comparison_calls_deleted : Note<
+ "defaulted %0 is implicitly deleted because it would invoke a deleted "
+ "comparison function%select{| for member %2| for base class %2}1">;
+def note_defaulted_comparison_no_viable_function : Note<
+ "defaulted %0 is implicitly deleted because there is no viable comparison "
+ "function%select{| for member %2| for base class %2}1">;
+def note_defaulted_comparison_no_viable_function_synthesized : Note<
+ "three-way comparison cannot be synthesized because there is no viable "
+ "function for %select{'=='|'<'}0 comparison">;
+def note_defaulted_comparison_not_rewritten_callee : Note<
+ "defaulted %0 is implicitly deleted because this non-rewritten comparison "
+ "function would be the best match for the comparison">;
+def err_incorrect_defaulted_comparison_constexpr : Error<
+ "defaulted definition of %sub{select_defaulted_comparison_kind}0 "
+ "cannot be declared %select{constexpr|consteval}1 because it invokes "
+ "a non-constexpr comparison function">;
+def note_defaulted_comparison_not_constexpr : Note<
+ "non-constexpr comparison function would be used to compare "
+ "%select{|member %1|base class %1}0">;
+def note_defaulted_comparison_not_constexpr_here : Note<
+ "non-constexpr comparison function declared here">;
def ext_implicit_exception_spec_mismatch : ExtWarn<
"function previously declared with an %select{explicit|implicit}0 exception "
diff --git a/clang/include/clang/Sema/Overload.h b/clang/include/clang/Sema/Overload.h
index c6012d754db0..e0c3ba13ef54 100644
--- a/clang/include/clang/Sema/Overload.h
+++ b/clang/include/clang/Sema/Overload.h
@@ -935,7 +935,17 @@ class Sema;
}
bool isAcceptableCandidate(const FunctionDecl *FD) {
- return AllowRewrittenCandidates || !isRewrittenOperator(FD);
+ if (!OriginalOperator)
+ return true;
+
+ // For an overloaded operator, we can have candidates with a
diff erent
+ // name in our unqualified lookup set. Make sure we only consider the
+ // ones we're supposed to.
+ OverloadedOperatorKind OO =
+ FD->getDeclName().getCXXOverloadedOperator();
+ return OO && (OO == OriginalOperator ||
+ (AllowRewrittenCandidates &&
+ OO == getRewrittenOverloadedOperator(OriginalOperator)));
}
/// Determine the kind of rewrite that should be performed for this
@@ -1028,6 +1038,12 @@ class Sema;
return Functions.insert(Key).second;
}
+ /// Exclude a function from being considered by overload resolution.
+ void exclude(Decl *F) {
+ isNewCandidate(F, OverloadCandidateParamOrder::Normal);
+ isNewCandidate(F, OverloadCandidateParamOrder::Reversed);
+ }
+
/// Clear out all of the candidates.
void clear(CandidateSetKind CSK);
diff --git a/clang/include/clang/Sema/Sema.h b/clang/include/clang/Sema/Sema.h
index f117cca0a56b..ed1f1370b332 100644
--- a/clang/include/clang/Sema/Sema.h
+++ b/clang/include/clang/Sema/Sema.h
@@ -3310,6 +3310,10 @@ class Sema final {
const UnresolvedSetImpl &Fns,
Expr *input, bool RequiresADL = true);
+ void LookupOverloadedBinOp(OverloadCandidateSet &CandidateSet,
+ OverloadedOperatorKind Op,
+ const UnresolvedSetImpl &Fns,
+ ArrayRef<Expr *> Args, bool RequiresADL = true);
ExprResult CreateOverloadedBinOp(SourceLocation OpLoc,
BinaryOperatorKind Opc,
const UnresolvedSetImpl &Fns,
@@ -5310,6 +5314,9 @@ class Sema final {
InheritedConstructorInfo *ICI = nullptr,
bool Diagnose = false);
+ /// Produce notes explaining why a defaulted function was defined as deleted.
+ void DiagnoseDeletedDefaultedFunction(FunctionDecl *FD);
+
/// Declare the implicit default constructor for the given class.
///
/// \param ClassDecl The class declaration into which the implicit
diff --git a/clang/lib/Sema/SemaDeclCXX.cpp b/clang/lib/Sema/SemaDeclCXX.cpp
index 1561960c99d2..ba516b66608e 100644
--- a/clang/lib/Sema/SemaDeclCXX.cpp
+++ b/clang/lib/Sema/SemaDeclCXX.cpp
@@ -1627,9 +1627,8 @@ static bool CheckConstexprDestructorSubobjects(Sema &SemaRef,
return true;
}
-// CheckConstexprParameterTypes - Check whether a function's parameter types
-// are all literal types. If so, return true. If not, produce a suitable
-// diagnostic and return false.
+/// Check whether a function's parameter types are all literal types. If so,
+/// return true. If not, produce a suitable diagnostic and return false.
static bool CheckConstexprParameterTypes(Sema &SemaRef,
const FunctionDecl *FD,
Sema::CheckConstexprKind Kind) {
@@ -1649,6 +1648,17 @@ static bool CheckConstexprParameterTypes(Sema &SemaRef,
return true;
}
+/// Check whether a function's return type is a literal type. If so, return
+/// true. If not, produce a suitable diagnostic and return false.
+static bool CheckConstexprReturnType(Sema &SemaRef, const FunctionDecl *FD,
+ Sema::CheckConstexprKind Kind) {
+ if (CheckLiteralType(SemaRef, Kind, FD->getLocation(), FD->getReturnType(),
+ diag::err_constexpr_non_literal_return,
+ FD->isConsteval()))
+ return false;
+ return true;
+}
+
/// Get diagnostic %select index for tag kind for
/// record diagnostic message.
/// WARNING: Indexes apply to particular diagnostics only!
@@ -1729,10 +1739,7 @@ bool Sema::CheckConstexprFunctionDefinition(const FunctionDecl *NewFD,
}
// - its return type shall be a literal type;
- QualType RT = NewFD->getReturnType();
- if (CheckLiteralType(*this, Kind, NewFD->getLocation(), RT,
- diag::err_constexpr_non_literal_return,
- NewFD->isConsteval()))
+ if (!CheckConstexprReturnType(*this, NewFD, Kind))
return false;
}
@@ -6391,10 +6398,26 @@ void Sema::CheckCompletedCXXClass(CXXRecordDecl *Record) {
if (HasTrivialABI)
Record->setHasTrivialSpecialMemberForCall();
+ // Explicitly-defaulted secondary comparison functions (!=, <, <=, >, >=).
+ // We check these last because they can depend on the properties of the
+ // primary comparison functions (==, <=>).
+ llvm::SmallVector<FunctionDecl*, 5> DefaultedSecondaryComparisons;
+
+ auto CheckForDefaultedFunction = [&](FunctionDecl *FD) {
+ if (!FD || FD->isInvalidDecl() || !FD->isExplicitlyDefaulted())
+ return;
+
+ DefaultedFunctionKind DFK = getDefaultedFunctionKind(FD);
+ if (DFK.asComparison() == DefaultedComparisonKind::NotEqual ||
+ DFK.asComparison() == DefaultedComparisonKind::Relational)
+ DefaultedSecondaryComparisons.push_back(FD);
+ else
+ CheckExplicitlyDefaultedFunction(FD);
+ };
+
auto CompleteMemberFunction = [&](CXXMethodDecl *M) {
// Check whether the explicitly-defaulted members are valid.
- if (!M->isInvalidDecl() && M->isExplicitlyDefaulted())
- CheckExplicitlyDefaultedFunction(M);
+ CheckForDefaultedFunction(M);
// For an explicitly defaulted or deleted special member, we defer
// determining triviality until the class is complete. That time is now!
@@ -6477,12 +6500,15 @@ void Sema::CheckCompletedCXXClass(CXXRecordDecl *Record) {
// Process any defaulted friends in the member-specification.
if (!Record->isDependentType()) {
for (FriendDecl *D : Record->friends()) {
- auto *FD = dyn_cast_or_null<FunctionDecl>(D->getFriendDecl());
- if (FD && !FD->isInvalidDecl() && FD->isExplicitlyDefaulted())
- CheckExplicitlyDefaultedFunction(FD);
+ CheckForDefaultedFunction(
+ dyn_cast_or_null<FunctionDecl>(D->getFriendDecl()));
}
}
+ // Check the defaulted secondary comparisons after any other member functions.
+ for (FunctionDecl *FD : DefaultedSecondaryComparisons)
+ CheckExplicitlyDefaultedFunction(FD);
+
// ms_struct is a request to use the same ABI rules as MSVC. Check
// whether this class uses any C++ features that are implemented
// completely
diff erently in MSVC, and if so, emit a diagnostic.
@@ -7046,6 +7072,343 @@ bool Sema::CheckExplicitlyDefaultedSpecialMember(CXXMethodDecl *MD,
return HadError;
}
+namespace {
+/// Helper class for building and checking a defaulted comparison.
+///
+/// Defaulted functions are built in two phases:
+///
+/// * First, the set of operations that the function will perform are
+/// identified, and some of them are checked. If any of the checked
+/// operations is invalid in certain ways, the comparison function is
+/// defined as deleted and no body is built.
+/// * Then, if the function is not defined as deleted, the body is built.
+///
+/// This is accomplished by performing two visitation steps over the eventual
+/// body of the function.
+template<typename Derived, typename Result, typename Subobject>
+class DefaultedComparisonVisitor {
+public:
+ using DefaultedComparisonKind = Sema::DefaultedComparisonKind;
+
+ DefaultedComparisonVisitor(Sema &S, CXXRecordDecl *RD, FunctionDecl *FD,
+ DefaultedComparisonKind DCK)
+ : S(S), RD(RD), FD(FD), DCK(DCK) {}
+
+ Result visit() {
+ // The type of an lvalue naming a parameter of this function.
+ QualType ParamLvalType =
+ FD->getParamDecl(0)->getType().getNonReferenceType();
+
+ switch (DCK) {
+ case DefaultedComparisonKind::None:
+ llvm_unreachable("not a defaulted comparison");
+
+ case DefaultedComparisonKind::Equal:
+ case DefaultedComparisonKind::ThreeWay:
+ return getDerived().visitSubobjects(RD, ParamLvalType.getQualifiers());
+
+ case DefaultedComparisonKind::NotEqual:
+ case DefaultedComparisonKind::Relational:
+ return getDerived().visitExpandedSubobject(
+ ParamLvalType, getDerived().getCompleteObject());
+ }
+ }
+
+protected:
+ Derived &getDerived() { return static_cast<Derived&>(*this); }
+
+ Result visitSubobjects(CXXRecordDecl *Record, Qualifiers Quals) {
+ Result R;
+ // C++ [class.compare.default]p5:
+ // The direct base class subobjects of C [...]
+ for (CXXBaseSpecifier &Base : Record->bases())
+ if (R.add(getDerived().visitSubobject(
+ S.Context.getQualifiedType(Base.getType(), Quals),
+ getDerived().getBase(&Base))))
+ return R;
+ // followed by the non-static data members of C [...]
+ for (FieldDecl *Field : Record->fields()) {
+ // Recursively expand anonymous structs.
+ if (Field->isAnonymousStructOrUnion()) {
+ if (R.add(
+ visitSubobjects(Field->getType()->getAsCXXRecordDecl(), Quals)))
+ return R;
+ continue;
+ }
+
+ // Figure out the type of an lvalue denoting this field.
+ Qualifiers FieldQuals = Quals;
+ if (Field->isMutable())
+ FieldQuals.removeConst();
+ QualType FieldType =
+ S.Context.getQualifiedType(Field->getType(), FieldQuals);
+
+ if (R.add(getDerived().visitSubobject(FieldType,
+ getDerived().getField(Field))))
+ return R;
+ }
+ // form a list of subobjects.
+ return R;
+ }
+
+ Result visitSubobject(QualType Type, Subobject Subobj) {
+ // In that list, any subobject of array type is recursively expanded
+ const ArrayType *AT = S.Context.getAsArrayType(Type);
+ if (auto *CAT = dyn_cast_or_null<ConstantArrayType>(AT))
+ return getDerived().visitSubobjectArray(CAT->getElementType(),
+ CAT->getSize(), Subobj);
+ return getDerived().visitExpandedSubobject(Type, Subobj);
+ }
+
+ Result visitSubobjectArray(QualType Type, const llvm::APInt &Size,
+ Subobject Subobj) {
+ return getDerived().visitSubobject(Type, Subobj);
+ }
+
+protected:
+ Sema &S;
+ CXXRecordDecl *RD;
+ FunctionDecl *FD;
+ DefaultedComparisonKind DCK;
+};
+
+/// Information about a defaulted comparison, as determined by
+/// DefaultedComparisonAnalyzer.
+struct DefaultedComparisonInfo {
+ bool Deleted = false;
+ bool Constexpr = true;
+
+ static DefaultedComparisonInfo deleted() { return {true, false}; }
+
+ bool add(const DefaultedComparisonInfo &R) {
+ Deleted |= R.Deleted;
+ Constexpr &= R.Constexpr;
+ return Deleted;
+ }
+};
+
+/// An element in the expanded list of subobjects of a defaulted comparison, as
+/// specified in C++2a [class.compare.default]p4.
+struct DefaultedComparisonSubobject {
+ enum { CompleteObject, Member, Base } Kind;
+ NamedDecl *Decl;
+ SourceLocation Loc;
+};
+
+/// A visitor over the notional body of a defaulted comparison that determines
+/// whether that body would be deleted or constexpr.
+class DefaultedComparisonAnalyzer
+ : public DefaultedComparisonVisitor<DefaultedComparisonAnalyzer,
+ DefaultedComparisonInfo,
+ DefaultedComparisonSubobject> {
+public:
+ enum DiagnosticKind { NoDiagnostics, ExplainDeleted, ExplainConstexpr };
+
+private:
+ DiagnosticKind Diagnose;
+
+public:
+ using Base = DefaultedComparisonVisitor;
+ using Result = DefaultedComparisonInfo;
+ using Subobject = DefaultedComparisonSubobject;
+
+ friend Base;
+
+ DefaultedComparisonAnalyzer(Sema &S, CXXRecordDecl *RD, FunctionDecl *FD,
+ DefaultedComparisonKind DCK,
+ DiagnosticKind Diagnose = NoDiagnostics)
+ : Base(S, RD, FD, DCK), Diagnose(Diagnose) {}
+
+ Result visit() {
+ if ((DCK == DefaultedComparisonKind::Equal ||
+ DCK == DefaultedComparisonKind::ThreeWay) &&
+ RD->hasVariantMembers()) {
+ // C++2a [class.compare.default]p2 [P2002R0]:
+ // A defaulted comparison operator function for class C is defined as
+ // deleted if [...] C has variant members.
+ if (Diagnose == ExplainDeleted) {
+ S.Diag(FD->getLocation(), diag::note_defaulted_comparison_union)
+ << FD << RD->isUnion() << RD;
+ }
+ return Result::deleted();
+ }
+
+ return Base::visit();
+ }
+
+private:
+ Subobject getCompleteObject() {
+ return Subobject{Subobject::CompleteObject, nullptr, FD->getLocation()};
+ }
+
+ Subobject getBase(CXXBaseSpecifier *Base) {
+ return Subobject{Subobject::Base, Base->getType()->getAsCXXRecordDecl(),
+ Base->getBaseTypeLoc()};
+ }
+
+ Subobject getField(FieldDecl *Field) {
+ return Subobject{Subobject::Member, Field, Field->getLocation()};
+ }
+
+ Result visitExpandedSubobject(QualType Type, Subobject Subobj) {
+ // C++2a [class.compare.default]p2 [P2002R0]:
+ // A defaulted <=> or == operator function for class C is defined as
+ // deleted if any non-static data member of C is of reference type
+ if (Type->isReferenceType()) {
+ if (Diagnose == ExplainDeleted) {
+ S.Diag(Subobj.Loc, diag::note_defaulted_comparison_reference_member)
+ << FD << RD;
+ }
+ return Result::deleted();
+ }
+
+ // [...] Let xi be an lvalue denoting the ith element [...]
+ OpaqueValueExpr Xi(FD->getLocation(), Type, VK_LValue);
+ Expr *Args[] = {&Xi, &Xi};
+
+ // All operators start by trying to apply that same operator recursively.
+ OverloadedOperatorKind OO = FD->getOverloadedOperator();
+ assert(OO != OO_None && "not an overloaded operator!");
+ return visitBinaryOperator(OO, Args, Subobj);
+ }
+
+ Result
+ visitBinaryOperator(OverloadedOperatorKind OO, ArrayRef<Expr *> Args,
+ Subobject Subobj,
+ OverloadCandidateSet *SpaceshipCandidates = nullptr) {
+ UnresolvedSet<4> Fns; // FIXME: Track this.
+
+ // Note that there is no need to consider rewritten candidates here if
+ // we've already found there is no viable 'operator<=>' candidate (and are
+ // considering synthesizing a '<=>' from '==' and '<').
+ OverloadCandidateSet CandidateSet(
+ FD->getLocation(), OverloadCandidateSet::CSK_Operator,
+ OverloadCandidateSet::OperatorRewriteInfo(
+ OO, /*AllowRewrittenCandidates=*/!SpaceshipCandidates));
+
+ /// C++2a [class.compare.default]p1 [P2002R0]:
+ /// [...] the defaulted function itself is never a candidate for overload
+ /// resolution [...]
+ CandidateSet.exclude(FD);
+
+ S.LookupOverloadedBinOp(CandidateSet, OO, Fns, Args);
+
+ Result R;
+
+ OverloadCandidateSet::iterator Best;
+ switch (CandidateSet.BestViableFunction(S, FD->getLocation(), Best)) {
+ case OR_Success:
+ // C++2a [class.compare.secondary]p2 [P2002R0]:
+ // The operator function [...] is defined as deleted if [...] the
+ // candidate selected by overload resolution is not a rewritten
+ // candidate.
+ if ((DCK == DefaultedComparisonKind::NotEqual ||
+ DCK == DefaultedComparisonKind::Relational) &&
+ !Best->RewriteKind) {
+ S.Diag(Best->Function->getLocation(),
+ diag::note_defaulted_comparison_not_rewritten_callee)
+ << FD;
+ return Result::deleted();
+ }
+
+ // C++2a [class.compare.default]p3 [P2002R0]:
+ // A defaulted comparison function is constexpr-compatible if [...]
+ // no overlod resolution performed [...] results in a non-constexpr
+ // function.
+ if (FunctionDecl *FD = Best->Function) {
+ assert(!FD->isDeleted() && "wrong overload resolution result");
+ // If it's not constexpr, explain why not.
+ if (Diagnose == ExplainConstexpr && !FD->isConstexpr()) {
+ if (Subobj.Kind != Subobject::CompleteObject)
+ S.Diag(Subobj.Loc, diag::note_defaulted_comparison_not_constexpr)
+ << Subobj.Kind << Subobj.Decl;
+ S.Diag(FD->getLocation(),
+ diag::note_defaulted_comparison_not_constexpr_here);
+ // Bail out after explaining; we don't want any more notes.
+ return Result::deleted();
+ }
+ R.Constexpr &= FD->isConstexpr();
+ }
+
+ // Note that we might be rewriting to a
diff erent operator. That call is
+ // not considered until we come to actually build the comparison function.
+ break;
+
+ case OR_Ambiguous:
+ if (Diagnose == ExplainDeleted) {
+ unsigned Kind = 0;
+ if (FD->getOverloadedOperator() == OO_Spaceship && OO != OO_Spaceship)
+ Kind = OO == OO_EqualEqual ? 1 : 2;
+ CandidateSet.NoteCandidates(
+ PartialDiagnosticAt(
+ Subobj.Loc, S.PDiag(diag::note_defaulted_comparison_ambiguous)
+ << FD << Kind << Subobj.Kind << Subobj.Decl),
+ S, OCD_AmbiguousCandidates, Args);
+ }
+ R = Result::deleted();
+ break;
+
+ case OR_Deleted:
+ if (Diagnose == ExplainDeleted) {
+ if ((DCK == DefaultedComparisonKind::NotEqual ||
+ DCK == DefaultedComparisonKind::Relational) &&
+ !Best->RewriteKind) {
+ S.Diag(Best->Function->getLocation(),
+ diag::note_defaulted_comparison_not_rewritten_callee)
+ << FD;
+ } else {
+ S.Diag(Subobj.Loc,
+ diag::note_defaulted_comparison_calls_deleted)
+ << FD << Subobj.Kind << Subobj.Decl;
+ S.NoteDeletedFunction(Best->Function);
+ }
+ }
+ R = Result::deleted();
+ break;
+
+ case OR_No_Viable_Function:
+ // If there's no usable candidate, we're done unless we can rewrite a
+ // '<=>' in terms of '==' and '<'.
+ if (OO == OO_Spaceship &&
+ S.Context.CompCategories.lookupInfoForType(FD->getReturnType())) {
+ // For any kind of comparison category return type, we need a usable
+ // '==' and a usable '<'.
+ if (!R.add(visitBinaryOperator(OO_EqualEqual, Args, Subobj,
+ &CandidateSet)))
+ R.add(visitBinaryOperator(OO_Less, Args, Subobj, &CandidateSet));
+ break;
+ }
+
+ if (Diagnose == ExplainDeleted) {
+ S.Diag(Subobj.Loc, diag::note_defaulted_comparison_no_viable_function)
+ << FD << Subobj.Kind << Subobj.Decl;
+
+ // For a three-way comparison, list both the candidates for the
+ // original operator and the candidates for the synthesized operator.
+ if (SpaceshipCandidates) {
+ SpaceshipCandidates->NoteCandidates(
+ S, Args,
+ SpaceshipCandidates->CompleteCandidates(S, OCD_AllCandidates,
+ Args, FD->getLocation()));
+ S.Diag(Subobj.Loc,
+ diag::note_defaulted_comparison_no_viable_function_synthesized)
+ << (OO == OO_EqualEqual ? 0 : 1);
+ }
+
+ CandidateSet.NoteCandidates(
+ S, Args,
+ CandidateSet.CompleteCandidates(S, OCD_AllCandidates, Args,
+ FD->getLocation()));
+ }
+ R = Result::deleted();
+ break;
+ }
+
+ return R;
+ }
+};
+}
+
bool Sema::CheckExplicitlyDefaultedComparison(FunctionDecl *FD,
DefaultedComparisonKind DCK) {
assert(DCK != DefaultedComparisonKind::None && "not a defaulted comparison");
@@ -7092,45 +7455,6 @@ bool Sema::CheckExplicitlyDefaultedComparison(FunctionDecl *FD,
assert(FD->getFriendObjectKind() && "expected a friend declaration");
}
- // C++2a [class.compare.default]p2:
- // A defaulted comparison operator function for class C is defined as
- // deleted if any non-static data member of C is of reference type or C is
- // a union-like class.
- llvm::SmallVector<CXXRecordDecl*, 4> Classes(1, RD);
- FieldDecl *ReferenceMember = nullptr;
- bool UnionLike = RD->isUnion();
- while (!Classes.empty()) {
- if (Classes.back()->isUnion())
- UnionLike = true;
- for (FieldDecl *FD : Classes.pop_back_val()->fields()) {
- if (FD->getType()->isReferenceType())
- ReferenceMember = FD;
- if (FD->isAnonymousStructOrUnion())
- Classes.push_back(FD->getType()->getAsCXXRecordDecl());
- }
- }
- // For non-memberwise comparisons, this rule is unjustified, so we permit
- // those cases as an extension.
- bool Memberwise = DCK == DefaultedComparisonKind::Equal ||
- DCK == DefaultedComparisonKind::ThreeWay;
- if (ReferenceMember) {
- Diag(FD->getLocation(),
- Memberwise ? diag::err_defaulted_comparison_reference_member
- : diag::ext_defaulted_comparison_reference_member)
- << FD << RD;
- Diag(ReferenceMember->getLocation(), diag::note_reference_member)
- << ReferenceMember;
- } else if (UnionLike) {
- // If the class actually has no variant members, this rule similarly
- // is unjustified, so we permit those cases too.
- Diag(FD->getLocation(),
- !Memberwise ? diag::ext_defaulted_comparison_union
- : !RD->hasVariantMembers()
- ? diag::ext_defaulted_comparison_empty_union
- : diag::err_defaulted_comparison_union)
- << FD << RD->isUnion() << RD;
- }
-
// C++2a [class.eq]p1, [class.rel]p1:
// A [defaulted comparison other than <=>] shall have a declared return
// type bool.
@@ -7142,20 +7466,77 @@ bool Sema::CheckExplicitlyDefaultedComparison(FunctionDecl *FD,
return true;
}
- // FIXME: Determine whether the function should be defined as deleted.
-
- // C++2a [dcl.fct.def.default]p3:
- // An explicitly-defaulted function [..] may be declared constexpr or
- // consteval only if it would have been implicitly declared constexpr.
- // FIXME: There are no rules governing when these should be constexpr,
- // except for the special case of the injected operator==, for which
- // C++2a [class.compare.default]p3 says:
- // The operator is a constexpr function if its definition would satisfy
- // the requirements for a constexpr function.
- // FIXME: Apply this rule to all defaulted comparisons. The only way this
- // can fail is if the return type of a defaulted operator<=> is not a literal
- // type. We should additionally consider whether any of the operations
- // performed by the comparison invokes a non-constexpr function.
+ // Determine whether the function should be defined as deleted.
+ DefaultedComparisonInfo Info =
+ DefaultedComparisonAnalyzer(*this, RD, FD, DCK).visit();
+
+ bool First = FD == FD->getCanonicalDecl();
+
+ // If we want to delete the function, then do so; there's nothing else to
+ // check in that case.
+ if (Info.Deleted) {
+ if (!First) {
+ // C++11 [dcl.fct.def.default]p4:
+ // [For a] user-provided explicitly-defaulted function [...] if such a
+ // function is implicitly defined as deleted, the program is ill-formed.
+ //
+ // This is really just a consequence of the general rule that you can
+ // only delete a function on its first declaration.
+ Diag(FD->getLocation(), diag::err_non_first_default_compare_deletes)
+ << (int)DCK;
+ DefaultedComparisonAnalyzer(*this, RD, FD, DCK,
+ DefaultedComparisonAnalyzer::ExplainDeleted)
+ .visit();
+ return true;
+ }
+
+ SetDeclDeleted(FD, FD->getLocation());
+ if (!inTemplateInstantiation()) {
+ Diag(FD->getLocation(), diag::warn_defaulted_comparison_deleted)
+ << (int)DCK;
+ DefaultedComparisonAnalyzer(*this, RD, FD, DCK,
+ DefaultedComparisonAnalyzer::ExplainDeleted)
+ .visit();
+ }
+ return false;
+ }
+
+ // FIXME: Deduce the return type now.
+
+ // C++2a [dcl.fct.def.default]p3 [P2002R0]:
+ // An explicitly-defaulted function that is not defined as deleted may be
+ // declared constexpr or consteval only if it is constexpr-compatible.
+ // C++2a [class.compare.default]p3 [P2002R0]:
+ // A defaulted comparison function is constexpr-compatible if it satisfies
+ // the requirements for a constexpr function [...]
+ // The only relevant requirements are that the parameter and return types are
+ // literal types. The remaining conditions are checked by the analyzer.
+ if (FD->isConstexpr()) {
+ if (CheckConstexprReturnType(*this, FD, CheckConstexprKind::Diagnose) &&
+ CheckConstexprParameterTypes(*this, FD, CheckConstexprKind::Diagnose) &&
+ !Info.Constexpr) {
+ Diag(FD->getBeginLoc(),
+ diag::err_incorrect_defaulted_comparison_constexpr)
+ << (int)DCK << FD->isConsteval();
+ DefaultedComparisonAnalyzer(*this, RD, FD, DCK,
+ DefaultedComparisonAnalyzer::ExplainConstexpr)
+ .visit();
+ }
+ }
+
+ // C++2a [dcl.fct.def.default]p3 [P2002R0]:
+ // If a constexpr-compatible function is explicitly defaulted on its first
+ // declaration, it is implicitly considered to be constexpr.
+ // FIXME: Only applying this to the first declaration seems problematic, as
+ // simple reorderings can affect the meaning of the program.
+ if (First) {
+ if (!FD->isConstexpr() && Info.Constexpr)
+ FD->setConstexprKind(CSK_constexpr);
+
+ // FIXME: Set up an implicit exception specification, or if given an
+ // explicit one, check that it matches.
+ }
+
return false;
}
@@ -7763,6 +8144,22 @@ bool Sema::ShouldDeleteSpecialMember(CXXMethodDecl *MD, CXXSpecialMember CSM,
return false;
}
+void Sema::DiagnoseDeletedDefaultedFunction(FunctionDecl *FD) {
+ DefaultedFunctionKind DFK = getDefaultedFunctionKind(FD);
+ assert(DFK && "not a defaultable function");
+ assert(FD->isDefaulted() && FD->isDeleted() && "not defaulted and deleted");
+
+ if (DFK.isSpecialMember()) {
+ ShouldDeleteSpecialMember(cast<CXXMethodDecl>(FD), DFK.asSpecialMember(),
+ nullptr, /*Diagnose=*/true);
+ } else {
+ DefaultedComparisonAnalyzer(
+ *this, cast<CXXRecordDecl>(FD->getLexicalDeclContext()), FD,
+ DFK.asComparison(), DefaultedComparisonAnalyzer::ExplainDeleted)
+ .visit();
+ }
+}
+
/// Perform lookup for a special member of the specified kind, and determine
/// whether it is trivial. If the triviality can be determined without the
/// lookup, skip it. This is intended for use when determining whether a
@@ -15177,6 +15574,16 @@ void Sema::SetDeclDeleted(Decl *Dcl, SourceLocation DelLoc) {
if (Fn->isDeleted())
return;
+ // C++11 [basic.start.main]p3:
+ // A program that defines main as deleted [...] is ill-formed.
+ if (Fn->isMain())
+ Diag(DelLoc, diag::err_deleted_main);
+
+ // C++11 [dcl.fct.def.delete]p4:
+ // A deleted function is implicitly inline.
+ Fn->setImplicitlyInline();
+ Fn->setDeletedAsWritten();
+
// See if we're deleting a function which is already known to override a
// non-deleted virtual function.
if (CXXMethodDecl *MD = dyn_cast<CXXMethodDecl>(Fn)) {
@@ -15193,19 +15600,8 @@ void Sema::SetDeclDeleted(Decl *Dcl, SourceLocation DelLoc) {
// If this function was implicitly deleted because it was defaulted,
// explain why it was deleted.
if (IssuedDiagnostic && MD->isDefaulted())
- ShouldDeleteSpecialMember(MD, getSpecialMember(MD), nullptr,
- /*Diagnose*/true);
+ DiagnoseDeletedDefaultedFunction(MD);
}
-
- // C++11 [basic.start.main]p3:
- // A program that defines main as deleted [...] is ill-formed.
- if (Fn->isMain())
- Diag(DelLoc, diag::err_deleted_main);
-
- // C++11 [dcl.fct.def.delete]p4:
- // A deleted function is implicitly inline.
- Fn->setImplicitlyInline();
- Fn->setDeletedAsWritten();
}
void Sema::SetDeclDefaulted(Decl *Dcl, SourceLocation DefaultLoc) {
diff --git a/clang/lib/Sema/SemaExpr.cpp b/clang/lib/Sema/SemaExpr.cpp
index e2c37f8f5238..5eeeba3c2d12 100644
--- a/clang/lib/Sema/SemaExpr.cpp
+++ b/clang/lib/Sema/SemaExpr.cpp
@@ -98,21 +98,16 @@ static void DiagnoseUnusedOfDecl(Sema &S, NamedDecl *D, SourceLocation Loc) {
/// Emit a note explaining that this function is deleted.
void Sema::NoteDeletedFunction(FunctionDecl *Decl) {
- assert(Decl->isDeleted());
+ assert(Decl && Decl->isDeleted());
- CXXMethodDecl *Method = dyn_cast<CXXMethodDecl>(Decl);
-
- if (Method && Method->isDeleted() && Method->isDefaulted()) {
+ if (Decl->isDefaulted()) {
// If the method was explicitly defaulted, point at that declaration.
- if (!Method->isImplicit())
+ if (!Decl->isImplicit())
Diag(Decl->getLocation(), diag::note_implicitly_deleted);
// Try to diagnose why this special member function was implicitly
// deleted. This might fail, if that reason no longer applies.
- CXXSpecialMember CSM = getSpecialMember(Method);
- if (CSM != CXXInvalid)
- ShouldDeleteSpecialMember(Method, CSM, nullptr, /*Diagnose=*/true);
-
+ DiagnoseDeletedDefaultedFunction(Decl);
return;
}
diff --git a/clang/lib/Sema/SemaOverload.cpp b/clang/lib/Sema/SemaOverload.cpp
index 6f8ad637d9ba..27e1101b482d 100644
--- a/clang/lib/Sema/SemaOverload.cpp
+++ b/clang/lib/Sema/SemaOverload.cpp
@@ -11035,6 +11035,7 @@ CompleteNonViableCandidate(Sema &S, OverloadCandidate *Cand,
unsigned ConvIdx = 0;
unsigned ArgIdx = 0;
ArrayRef<QualType> ParamTypes;
+ bool Reversed = Cand->RewriteKind & CRK_Reversed;
if (Cand->IsSurrogate) {
QualType ConvType
@@ -11048,7 +11049,7 @@ CompleteNonViableCandidate(Sema &S, OverloadCandidate *Cand,
ParamTypes =
Cand->Function->getType()->castAs<FunctionProtoType>()->getParamTypes();
if (isa<CXXMethodDecl>(Cand->Function) &&
- !isa<CXXConstructorDecl>(Cand->Function)) {
+ !isa<CXXConstructorDecl>(Cand->Function) && !Reversed) {
// Conversion 0 is 'this', which doesn't have a corresponding parameter.
ConvIdx = 1;
if (CSK == OverloadCandidateSet::CSK_Operator &&
@@ -11063,7 +11064,6 @@ CompleteNonViableCandidate(Sema &S, OverloadCandidate *Cand,
}
// Fill in the rest of the conversions.
- bool Reversed = Cand->RewriteKind & CRK_Reversed;
for (unsigned ParamIdx = Reversed ? ParamTypes.size() - 1 : 0;
ConvIdx != ConvCount;
++ConvIdx, ++ArgIdx, ParamIdx += (Reversed ? -1 : 1)) {
@@ -12755,6 +12755,70 @@ Sema::CreateOverloadedUnaryOp(SourceLocation OpLoc, UnaryOperatorKind Opc,
return CreateBuiltinUnaryOp(OpLoc, Opc, Input);
}
+/// Perform lookup for an overloaded binary operator.
+void Sema::LookupOverloadedBinOp(OverloadCandidateSet &CandidateSet,
+ OverloadedOperatorKind Op,
+ const UnresolvedSetImpl &Fns,
+ ArrayRef<Expr *> Args, bool PerformADL) {
+ SourceLocation OpLoc = CandidateSet.getLocation();
+
+ OverloadedOperatorKind ExtraOp =
+ CandidateSet.getRewriteInfo().AllowRewrittenCandidates
+ ? getRewrittenOverloadedOperator(Op)
+ : OO_None;
+
+ // Add the candidates from the given function set. This also adds the
+ // rewritten candidates using these functions if necessary.
+ AddNonMemberOperatorCandidates(Fns, Args, CandidateSet);
+
+ // Add operator candidates that are member functions.
+ AddMemberOperatorCandidates(Op, OpLoc, Args, CandidateSet);
+ if (CandidateSet.getRewriteInfo().shouldAddReversed(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))
+ AddMemberOperatorCandidates(ExtraOp, OpLoc, {Args[1], Args[0]},
+ CandidateSet,
+ OverloadCandidateParamOrder::Reversed);
+ }
+
+ // Add candidates from ADL. Per [over.match.oper]p2, this lookup is not
+ // performed for an assignment operator (nor for operator[] nor operator->,
+ // which don't get here).
+ if (Op != OO_Equal && PerformADL) {
+ DeclarationName OpName = Context.DeclarationNames.getCXXOperatorName(Op);
+ AddArgumentDependentLookupCandidates(OpName, OpLoc, Args,
+ /*ExplicitTemplateArgs*/ nullptr,
+ CandidateSet);
+ if (ExtraOp) {
+ DeclarationName ExtraOpName =
+ Context.DeclarationNames.getCXXOperatorName(ExtraOp);
+ AddArgumentDependentLookupCandidates(ExtraOpName, OpLoc, Args,
+ /*ExplicitTemplateArgs*/ nullptr,
+ CandidateSet);
+ }
+ }
+
+ // Add builtin operator candidates.
+ //
+ // FIXME: We don't add any rewritten candidates here. This is strictly
+ // incorrect; a builtin candidate could be hidden by a non-viable candidate,
+ // resulting in our selecting a rewritten builtin candidate. For example:
+ //
+ // enum class E { e };
+ // bool operator!=(E, E) requires false;
+ // bool k = E::e != E::e;
+ //
+ // ... should select the rewritten builtin candidate 'operator==(E, E)'. But
+ // it seems unreasonable to consider rewritten builtin candidates. A core
+ // issue has been filed proposing to removed this requirement.
+ AddBuiltinOperatorCandidates(Op, OpLoc, Args, CandidateSet);
+}
+
/// Create a binary operation that may resolve to an overloaded
/// operator.
///
@@ -12783,7 +12847,6 @@ ExprResult Sema::CreateOverloadedBinOp(SourceLocation OpLoc,
AllowRewrittenCandidates = false;
OverloadedOperatorKind Op = BinaryOperator::getOverloadedOperator(Opc);
- DeclarationName OpName = Context.DeclarationNames.getCXXOperatorName(Op);
// If either side is type-dependent, create an appropriate dependent
// expression.
@@ -12805,6 +12868,7 @@ ExprResult Sema::CreateOverloadedBinOp(SourceLocation OpLoc,
// FIXME: save results of ADL from here?
CXXRecordDecl *NamingClass = nullptr; // lookup ignores member operators
// TODO: provide better source location info in DNLoc component.
+ DeclarationName OpName = Context.DeclarationNames.getCXXOperatorName(Op);
DeclarationNameInfo OpNameInfo(OpName, OpLoc);
UnresolvedLookupExpr *Fn = UnresolvedLookupExpr::Create(
Context, NamingClass, NestedNameSpecifierLoc(), OpNameInfo,
@@ -12838,63 +12902,11 @@ ExprResult Sema::CreateOverloadedBinOp(SourceLocation OpLoc,
if (Opc == BO_PtrMemD)
return CreateBuiltinBinOp(OpLoc, Opc, Args[0], Args[1]);
- // Build an empty overload set.
+ // Build the overload set.
OverloadCandidateSet CandidateSet(
OpLoc, OverloadCandidateSet::CSK_Operator,
OverloadCandidateSet::OperatorRewriteInfo(Op, AllowRewrittenCandidates));
-
- OverloadedOperatorKind ExtraOp =
- AllowRewrittenCandidates ? getRewrittenOverloadedOperator(Op) : OO_None;
-
- // Add the candidates from the given function set. This also adds the
- // rewritten candidates using these functions if necessary.
- AddNonMemberOperatorCandidates(Fns, Args, CandidateSet);
-
- // Add operator candidates that are member functions.
- AddMemberOperatorCandidates(Op, OpLoc, Args, CandidateSet);
- if (CandidateSet.getRewriteInfo().shouldAddReversed(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))
- AddMemberOperatorCandidates(ExtraOp, OpLoc, {Args[1], Args[0]},
- CandidateSet,
- OverloadCandidateParamOrder::Reversed);
- }
-
- // Add candidates from ADL. Per [over.match.oper]p2, this lookup is not
- // performed for an assignment operator (nor for operator[] nor operator->,
- // which don't get here).
- if (Opc != BO_Assign && PerformADL) {
- AddArgumentDependentLookupCandidates(OpName, OpLoc, Args,
- /*ExplicitTemplateArgs*/ nullptr,
- CandidateSet);
- if (ExtraOp) {
- DeclarationName ExtraOpName =
- Context.DeclarationNames.getCXXOperatorName(ExtraOp);
- AddArgumentDependentLookupCandidates(ExtraOpName, OpLoc, Args,
- /*ExplicitTemplateArgs*/ nullptr,
- CandidateSet);
- }
- }
-
- // Add builtin operator candidates.
- //
- // FIXME: We don't add any rewritten candidates here. This is strictly
- // incorrect; a builtin candidate could be hidden by a non-viable candidate,
- // resulting in our selecting a rewritten builtin candidate. For example:
- //
- // enum class E { e };
- // bool operator!=(E, E) requires false;
- // bool k = E::e != E::e;
- //
- // ... should select the rewritten builtin candidate 'operator==(E, E)'. But
- // it seems unreasonable to consider rewritten builtin candidates. A core
- // issue has been filed proposing to removed this requirement.
- AddBuiltinOperatorCandidates(Op, OpLoc, Args, CandidateSet);
+ LookupOverloadedBinOp(CandidateSet, Op, Fns, Args, PerformADL);
bool HadMultipleCandidates = (CandidateSet.size() > 1);
@@ -13150,14 +13162,20 @@ ExprResult Sema::CreateOverloadedBinOp(SourceLocation OpLoc,
case OR_Deleted:
if (isImplicitlyDeleted(Best->Function)) {
- CXXMethodDecl *Method = cast<CXXMethodDecl>(Best->Function);
- Diag(OpLoc, diag::err_ovl_deleted_special_oper)
- << Context.getRecordType(Method->getParent())
- << getSpecialMember(Method);
+ FunctionDecl *DeletedFD = Best->Function;
+ DefaultedFunctionKind DFK = getDefaultedFunctionKind(DeletedFD);
+ if (DFK.isSpecialMember()) {
+ Diag(OpLoc, diag::err_ovl_deleted_special_oper)
+ << Args[0]->getType() << DFK.asSpecialMember();
+ } else {
+ assert(DFK.isComparison());
+ Diag(OpLoc, diag::err_ovl_deleted_comparison)
+ << Args[0]->getType() << DeletedFD;
+ }
// The user probably meant to call this special member. Just
// explain why it's deleted.
- NoteDeletedFunction(Method);
+ NoteDeletedFunction(DeletedFD);
return ExprError();
}
CandidateSet.NoteCandidates(
diff --git a/clang/test/CXX/class/class.compare/class.compare.default/p2.cpp b/clang/test/CXX/class/class.compare/class.compare.default/p2.cpp
index eb4789e31376..bbc9060df305 100644
--- a/clang/test/CXX/class/class.compare/class.compare.default/p2.cpp
+++ b/clang/test/CXX/class/class.compare/class.compare.default/p2.cpp
@@ -1,70 +1,143 @@
// RUN: %clang_cc1 -std=c++2a -verify %s
-struct A {
+struct A1 {
int x;
- int &y; // expected-note 7{{reference member 'y' declared here}}
+ int &y; // expected-note 9{{because class 'A1' has a reference member}}
- bool operator==(const A&) const = default; // expected-error {{cannot default 'operator==' in class 'A' with reference member}}
- bool operator!=(const A&) const = default; // expected-warning {{ISO C++2a does not allow defaulting 'operator!=' in class 'A' with reference member}}
+ bool operator==(const A1&) const = default; // expected-warning {{implicitly deleted}} expected-note 2{{deleted here}}
+ bool operator<=>(const A1&) const = default; // expected-warning {{implicitly deleted}} expected-note 5{{deleted here}}
+};
+struct A2 {
+ int x;
+ int &y;
+
+ bool operator==(const A2&) const;
+ bool operator!=(const A2&) const = default;
- bool operator<=>(const A&) const = default; // expected-error {{cannot default 'operator<=>' in class 'A' with reference member}}
- bool operator<(const A&) const = default; // expected-warning {{ISO C++2a does not allow defaulting 'operator<' in class 'A' with reference member}}
- bool operator<=(const A&) const = default; // expected-warning {{ISO C++2a does not allow defaulting 'operator<=' in class 'A' with reference member}}
- bool operator>(const A&) const = default; // expected-warning {{ISO C++2a does not allow defaulting 'operator>' in class 'A' with reference member}}
- bool operator>=(const A&) const = default; // expected-warning {{ISO C++2a does not allow defaulting 'operator>=' in class 'A' with reference member}}
+ bool operator<=>(const A2&) const;
+ bool operator<(const A2&) const = default;
+ bool operator<=(const A2&) const = default;
+ bool operator>(const A2&) const = default;
+ bool operator>=(const A2&) const = default;
};
+void f(A1 a) {
+ void(a == a); // expected-error {{deleted}}
+ void(a != a); // expected-error {{deleted}}
+ void(a <=> a); // expected-error {{deleted}}
+ void(a < a); // expected-error {{deleted}}
+ void(a <= a); // expected-error {{deleted}}
+ void(a > a); // expected-error {{deleted}}
+ void(a >= a); // expected-error {{deleted}}
+}
+void f(A2 a) {
+ void(a == a);
+ void(a != a);
+ void(a <=> a);
+ void(a < a);
+ void(a <= a);
+ void(a > a);
+ void(a >= a);
+}
-struct B {
+struct B1 {
struct {
int x;
- int &y; // expected-note 7{{reference member 'y' declared here}}
+ int &y; // expected-note 2{{because class 'B1' has a reference member}}
};
- bool operator==(const B&) const = default; // expected-error {{cannot default 'operator==' in class 'B' with reference member}}
- bool operator!=(const B&) const = default; // expected-warning {{ISO C++2a does not allow defaulting 'operator!=' in class 'B' with reference member}}
+ bool operator==(const B1&) const = default; // expected-warning {{implicitly deleted}}
+ bool operator<=>(const B1&) const = default; // expected-warning {{implicitly deleted}}
+};
+
+struct B2 {
+ struct {
+ int x;
+ int &y;
+ };
+
+ bool operator==(const B2&) const;
+ bool operator!=(const B2&) const = default;
+
+ bool operator<=>(const B2&) const;
+ bool operator<(const B2&) const = default;
+ bool operator<=(const B2&) const = default;
+ bool operator>(const B2&) const = default;
+ bool operator>=(const B2&) const = default;
+};
+
+union C1 {
+ int a;
- bool operator<=>(const B&) const = default; // expected-error {{cannot default 'operator<=>' in class 'B' with reference member}}
- bool operator<(const B&) const = default; // expected-warning {{ISO C++2a does not allow defaulting 'operator<' in class 'B' with reference member}}
- bool operator<=(const B&) const = default; // expected-warning {{ISO C++2a does not allow defaulting 'operator<=' in class 'B' with reference member}}
- bool operator>(const B&) const = default; // expected-warning {{ISO C++2a does not allow defaulting 'operator>' in class 'B' with reference member}}
- bool operator>=(const B&) const = default; // expected-warning {{ISO C++2a does not allow defaulting 'operator>=' in class 'B' with reference member}}
+ bool operator==(const C1&) const = default; // expected-warning {{implicitly deleted}} expected-note {{because 'C1' is a union }}
+ bool operator<=>(const C1&) const = default; // expected-warning {{implicitly deleted}} expected-note {{because 'C1' is a union }}
};
-union C {
+union C2 {
int a;
- bool operator==(const C&) const = default; // expected-error {{cannot default 'operator==' in union 'C'}}
- bool operator!=(const C&) const = default; // expected-warning {{ISO C++2a does not allow defaulting 'operator!=' in union 'C'}}
+ bool operator==(const C2&) const;
+ bool operator!=(const C2&) const = default;
- bool operator<=>(const C&) const = default; // expected-error {{cannot default 'operator<=>' in union 'C'}}
- bool operator<(const C&) const = default; // expected-warning {{ISO C++2a does not allow defaulting 'operator<' in union 'C'}}
- bool operator<=(const C&) const = default; // expected-warning {{ISO C++2a does not allow defaulting 'operator<=' in union 'C'}}
- bool operator>(const C&) const = default; // expected-warning {{ISO C++2a does not allow defaulting 'operator>' in union 'C'}}
- bool operator>=(const C&) const = default; // expected-warning {{ISO C++2a does not allow defaulting 'operator>=' in union 'C'}}
+ bool operator<=>(const C2&) const;
+ bool operator<(const C2&) const = default;
+ bool operator<=(const C2&) const = default;
+ bool operator>(const C2&) const = default;
+ bool operator>=(const C2&) const = default;
};
-struct D {
+struct D1 {
union {
int a;
};
- bool operator==(const D&) const = default; // expected-error {{cannot default 'operator==' in union-like class 'D'}}
- bool operator!=(const D&) const = default; // expected-warning {{ISO C++2a does not allow defaulting 'operator!=' in union-like class 'D'}}
+ bool operator==(const D1&) const = default; // expected-warning {{implicitly deleted}} expected-note {{because 'D1' is a union-like class}}
+ bool operator<=>(const D1&) const = default; // expected-warning {{implicitly deleted}} expected-note {{because 'D1' is a union-like class}}
+};
+struct D2 {
+ union {
+ int a;
+ };
- bool operator<=>(const D&) const = default; // expected-error {{cannot default 'operator<=>' in union-like class 'D'}}
- bool operator<(const D&) const = default; // expected-warning {{ISO C++2a does not allow defaulting 'operator<' in union-like class 'D'}}
- bool operator<=(const D&) const = default; // expected-warning {{ISO C++2a does not allow defaulting 'operator<=' in union-like class 'D'}}
- bool operator>(const D&) const = default; // expected-warning {{ISO C++2a does not allow defaulting 'operator>' in union-like class 'D'}}
- bool operator>=(const D&) const = default; // expected-warning {{ISO C++2a does not allow defaulting 'operator>=' in union-like class 'D'}}
+ bool operator==(const D2&) const;
+ bool operator!=(const D2&) const = default;
+
+ bool operator<=>(const D2&) const;
+ bool operator<(const D2&) const = default;
+ bool operator<=(const D2&) const = default;
+ bool operator>(const D2&) const = default;
+ bool operator>=(const D2&) const = default;
};
-union E {
- bool operator==(const E&) const = default; // expected-warning {{ISO C++2a does not allow defaulting 'operator==' in union 'E' despite it having no variant members}}
- bool operator!=(const E&) const = default; // expected-warning {{ISO C++2a does not allow defaulting 'operator!=' in union 'E'}}
+union E1 {
+ bool operator==(const E1&) const = default;
+ bool operator!=(const E1&) const = default;
+
+ bool operator<=>(const E1&) const = default;
+ bool operator<(const E1&) const = default;
+ bool operator<=(const E1&) const = default;
+ bool operator>(const E1&) const = default;
+ bool operator>=(const E1&) const = default;
+};
+union E2 {
+ bool operator==(const E2&) const = default;
+ bool operator!=(const E2&) const = default;
+
+ bool operator<=>(const E2&) const = default;
+ bool operator<(const E2&) const = default;
+ bool operator<=(const E2&) const = default;
+ bool operator>(const E2&) const = default;
+ bool operator>=(const E2&) const = default;
+};
- bool operator<=>(const E&) const = default; // expected-warning {{ISO C++2a does not allow defaulting 'operator<=>' in union 'E' despite it having no variant members}}
- bool operator<(const E&) const = default; // expected-warning {{ISO C++2a does not allow defaulting 'operator<' in union 'E'}}
- bool operator<=(const E&) const = default; // expected-warning {{ISO C++2a does not allow defaulting 'operator<=' in union 'E'}}
- bool operator>(const E&) const = default; // expected-warning {{ISO C++2a does not allow defaulting 'operator>' in union 'E'}}
- bool operator>=(const E&) const = default; // expected-warning {{ISO C++2a does not allow defaulting 'operator>=' in union 'E'}}
+struct F;
+bool operator==(const F&, const F&);
+bool operator!=(const F&, const F&);
+bool operator<=>(const F&, const F&);
+bool operator<(const F&, const F&);
+struct F {
+ union { int a; };
+ friend bool operator==(const F&, const F&) = default; // expected-error {{defaulting this equality comparison operator would delete it after its first declaration}} expected-note {{implicitly deleted because 'F' is a union-like class}}
+ friend bool operator!=(const F&, const F&) = default;
+ friend bool operator<=>(const F&, const F&) = default; // expected-error {{defaulting this three-way comparison operator would delete it after its first declaration}} expected-note {{implicitly deleted because 'F' is a union-like class}}
+ friend bool operator<(const F&, const F&) = default;
};
diff --git a/clang/test/CXX/class/class.compare/class.compare.default/p3.cpp b/clang/test/CXX/class/class.compare/class.compare.default/p3.cpp
new file mode 100644
index 000000000000..f6daaf020590
--- /dev/null
+++ b/clang/test/CXX/class/class.compare/class.compare.default/p3.cpp
@@ -0,0 +1,192 @@
+// This test is for the [class.compare.default]p3 added by P2002R0
+
+// RUN: %clang_cc1 -std=c++2a -verify %s
+
+namespace std {
+ struct strong_ordering {
+ int n;
+ constexpr operator int() const { return n; }
+ static const strong_ordering less, equal, greater;
+ };
+ constexpr strong_ordering strong_ordering::less = {-1};
+ constexpr strong_ordering strong_ordering::equal = {0};
+ constexpr strong_ordering strong_ordering::greater = {1};
+}
+
+struct A {
+ friend bool operator==(const A&, const A&) = default;
+ friend bool operator!=(const A&, const A&) = default;
+
+ friend std::strong_ordering operator<=>(const A&, const A&) = default;
+ friend bool operator<(const A&, const A&) = default;
+ friend bool operator<=(const A&, const A&) = default;
+ friend bool operator>(const A&, const A&) = default;
+ friend bool operator>=(const A&, const A&) = default;
+};
+struct TestA {
+ friend constexpr bool operator==(const A&, const A&);
+ friend constexpr bool operator!=(const A&, const A&);
+
+ friend constexpr std::strong_ordering operator<=>(const A&, const A&);
+ friend constexpr bool operator<(const A&, const A&);
+ friend constexpr bool operator<=(const A&, const A&);
+ friend constexpr bool operator>(const A&, const A&);
+ friend constexpr bool operator>=(const A&, const A&);
+};
+
+// Declaration order doesn't matter, even though the secondary operators need
+// to know whether the primary ones are constexpr.
+struct ReversedA {
+ friend bool operator>=(const ReversedA&, const ReversedA&) = default;
+ friend bool operator>(const ReversedA&, const ReversedA&) = default;
+ friend bool operator<=(const ReversedA&, const ReversedA&) = default;
+ friend bool operator<(const ReversedA&, const ReversedA&) = default;
+ friend std::strong_ordering operator<=>(const ReversedA&, const ReversedA&) = default;
+
+ friend bool operator!=(const ReversedA&, const ReversedA&) = default;
+ friend bool operator==(const ReversedA&, const ReversedA&) = default;
+};
+struct TestReversedA {
+ friend constexpr bool operator>=(const ReversedA&, const ReversedA&);
+ friend constexpr bool operator>(const ReversedA&, const ReversedA&);
+ friend constexpr bool operator<=(const ReversedA&, const ReversedA&);
+ friend constexpr bool operator<(const ReversedA&, const ReversedA&);
+ friend constexpr std::strong_ordering operator<=>(const ReversedA&, const ReversedA&);
+
+ friend constexpr bool operator!=(const ReversedA&, const ReversedA&);
+ friend constexpr bool operator==(const ReversedA&, const ReversedA&);
+};
+
+struct B {
+ A a;
+ friend bool operator==(const B&, const B&) = default;
+ friend bool operator!=(const B&, const B&) = default;
+
+ friend std::strong_ordering operator<=>(const B&, const B&) = default;
+ friend bool operator<(const B&, const B&) = default;
+ friend bool operator<=(const B&, const B&) = default;
+ friend bool operator>(const B&, const B&) = default;
+ friend bool operator>=(const B&, const B&) = default;
+};
+struct TestB {
+ friend constexpr bool operator==(const B&, const B&);
+ friend constexpr bool operator!=(const B&, const B&);
+
+ friend constexpr std::strong_ordering operator<=>(const B&, const B&);
+ friend constexpr bool operator<(const B&, const B&);
+ friend constexpr bool operator<=(const B&, const B&);
+ friend constexpr bool operator>(const B&, const B&);
+ friend constexpr bool operator>=(const B&, const B&);
+};
+
+struct C {
+ friend bool operator==(const C&, const C&); // expected-note {{previous}} expected-note 2{{here}}
+ friend bool operator!=(const C&, const C&) = default; // expected-note {{previous}}
+
+ friend std::strong_ordering operator<=>(const C&, const C&); // expected-note {{previous}} expected-note 2{{here}}
+ friend bool operator<(const C&, const C&) = default; // expected-note {{previous}}
+ friend bool operator<=(const C&, const C&) = default; // expected-note {{previous}}
+ friend bool operator>(const C&, const C&) = default; // expected-note {{previous}}
+ friend bool operator>=(const C&, const C&) = default; // expected-note {{previous}}
+};
+struct TestC {
+ friend constexpr bool operator==(const C&, const C&); // expected-error {{non-constexpr}}
+ friend constexpr bool operator!=(const C&, const C&); // expected-error {{non-constexpr}}
+
+ friend constexpr std::strong_ordering operator<=>(const C&, const C&); // expected-error {{non-constexpr}}
+ friend constexpr bool operator<(const C&, const C&); // expected-error {{non-constexpr}}
+ friend constexpr bool operator<=(const C&, const C&); // expected-error {{non-constexpr}}
+ friend constexpr bool operator>(const C&, const C&); // expected-error {{non-constexpr}}
+ friend constexpr bool operator>=(const C&, const C&); // expected-error {{non-constexpr}}
+};
+
+struct D {
+ A a;
+ C c;
+ A b;
+ friend bool operator==(const D&, const D&) = default; // expected-note {{previous}}
+ friend bool operator!=(const D&, const D&) = default; // expected-note {{previous}}
+
+ friend std::strong_ordering operator<=>(const D&, const D&) = default; // expected-note {{previous}}
+ friend bool operator<(const D&, const D&) = default; // expected-note {{previous}}
+ friend bool operator<=(const D&, const D&) = default; // expected-note {{previous}}
+ friend bool operator>(const D&, const D&) = default; // expected-note {{previous}}
+ friend bool operator>=(const D&, const D&) = default; // expected-note {{previous}}
+};
+struct TestD {
+ friend constexpr bool operator==(const D&, const D&); // expected-error {{non-constexpr}}
+ friend constexpr bool operator!=(const D&, const D&); // expected-error {{non-constexpr}}
+
+ friend constexpr std::strong_ordering operator<=>(const D&, const D&); // expected-error {{non-constexpr}}
+ friend constexpr bool operator<(const D&, const D&); // expected-error {{non-constexpr}}
+ friend constexpr bool operator<=(const D&, const D&); // expected-error {{non-constexpr}}
+ friend constexpr bool operator>(const D&, const D&); // expected-error {{non-constexpr}}
+ friend constexpr bool operator>=(const D&, const D&); // expected-error {{non-constexpr}}
+};
+
+
+struct E {
+ A a;
+ C c; // expected-note 2{{non-constexpr comparison function would be used to compare member 'c'}}
+ A b;
+ friend constexpr bool operator==(const E&, const E&) = default; // expected-error {{cannot be declared constexpr because it invokes a non-constexpr comparison function}}
+ friend constexpr bool operator!=(const E&, const E&) = default;
+
+ friend constexpr std::strong_ordering operator<=>(const E&, const E&) = default; // expected-error {{cannot be declared constexpr because it invokes a non-constexpr comparison function}}
+ friend constexpr bool operator<(const E&, const E&) = default;
+ friend constexpr bool operator<=(const E&, const E&) = default;
+ friend constexpr bool operator>(const E&, const E&) = default;
+ friend constexpr bool operator>=(const E&, const E&) = default;
+};
+
+struct E2 : A, C { // expected-note 2{{non-constexpr comparison function would be used to compare base class 'C'}}
+ friend constexpr bool operator==(const E2&, const E2&) = default; // expected-error {{cannot be declared constexpr because it invokes a non-constexpr comparison function}}
+ friend constexpr bool operator!=(const E2&, const E2&) = default;
+
+ friend constexpr std::strong_ordering operator<=>(const E2&, const E2&) = default; // expected-error {{cannot be declared constexpr because it invokes a non-constexpr comparison function}}
+ friend constexpr bool operator<(const E2&, const E2&) = default;
+ friend constexpr bool operator<=(const E2&, const E2&) = default;
+ friend constexpr bool operator>(const E2&, const E2&) = default;
+ friend constexpr bool operator>=(const E2&, const E2&) = default;
+};
+
+struct F {
+ friend bool operator==(const F&, const F&); // expected-note {{here}}
+ friend constexpr bool operator!=(const F&, const F&) = default; // expected-error {{cannot be declared constexpr because it invokes a non-constexpr comparison function}}
+
+ friend std::strong_ordering operator<=>(const F&, const F&); // expected-note 4{{here}}
+ friend constexpr bool operator<(const F&, const F&) = default; // expected-error {{cannot be declared constexpr because it invokes a non-constexpr comparison function}}
+ friend constexpr bool operator<=(const F&, const F&) = default; // expected-error {{cannot be declared constexpr because it invokes a non-constexpr comparison function}}
+ friend constexpr bool operator>(const F&, const F&) = default; // expected-error {{cannot be declared constexpr because it invokes a non-constexpr comparison function}}
+ friend constexpr bool operator>=(const F&, const F&) = default; // expected-error {{cannot be declared constexpr because it invokes a non-constexpr comparison function}}
+};
+
+// No implicit 'constexpr' if it's not the first declaration.
+// FIXME: This rule creates problems for reordering of declarations; is this
+// really the right model?
+struct G;
+bool operator==(const G&, const G&);
+bool operator!=(const G&, const G&);
+std::strong_ordering operator<=>(const G&, const G&);
+bool operator<(const G&, const G&);
+bool operator<=(const G&, const G&);
+bool operator>(const G&, const G&);
+bool operator>=(const G&, const G&);
+struct G {
+ friend bool operator==(const G&, const G&) = default;
+ friend bool operator!=(const G&, const G&) = default;
+
+ friend std::strong_ordering operator<=>(const G&, const G&) = default;
+ friend bool operator<(const G&, const G&) = default;
+ friend bool operator<=(const G&, const G&) = default;
+ friend bool operator>(const G&, const G&) = default;
+ friend bool operator>=(const G&, const G&) = default;
+};
+bool operator==(const G&, const G&);
+bool operator!=(const G&, const G&);
+
+std::strong_ordering operator<=>(const G&, const G&);
+bool operator<(const G&, const G&);
+bool operator<=(const G&, const G&);
+bool operator>(const G&, const G&);
+bool operator>=(const G&, const G&);
diff --git a/clang/test/CXX/class/class.compare/class.eq/p1.cpp b/clang/test/CXX/class/class.compare/class.eq/p1.cpp
index 622f66cf9281..72f6ad50c368 100644
--- a/clang/test/CXX/class/class.compare/class.eq/p1.cpp
+++ b/clang/test/CXX/class/class.compare/class.eq/p1.cpp
@@ -1,10 +1,12 @@
// RUN: %clang_cc1 -std=c++2a -verify %s
-struct Good {
- bool operator==(const Good&) const = default;
- bool operator!=(const Good&) const = default;
- friend bool operator==(const Good&, const Good&) = default;
- friend bool operator!=(const Good&, const Good&) = default;
+struct Good1 {
+ bool operator==(const Good1&) const = default;
+ bool operator!=(const Good1&) const = default;
+};
+struct Good2 {
+ friend bool operator==(const Good2&, const Good2&) = default;
+ friend bool operator!=(const Good2&, const Good2&) = default;
};
enum Bool : bool {};
diff --git a/clang/test/CXX/class/class.compare/class.eq/p2.cpp b/clang/test/CXX/class/class.compare/class.eq/p2.cpp
new file mode 100644
index 000000000000..1a515dee4900
--- /dev/null
+++ b/clang/test/CXX/class/class.compare/class.eq/p2.cpp
@@ -0,0 +1,40 @@
+// RUN: %clang_cc1 -std=c++2a -verify %s
+
+struct A {};
+struct B { bool operator==(B) const; };
+struct C { int operator==(C) const; };
+struct D {
+ // expected-note at +2 {{candidate function not viable: 'this' argument has type 'const}}
+ // expected-note at +1 {{candidate function (with reversed parameter order) not viable: 1st argument ('const}}
+ bool operator==(D);
+};
+struct E { E(const E&) = delete; int operator==(E) const; };
+struct F { void operator==(F) const; };
+struct G { bool operator==(G) const = delete; }; // expected-note {{deleted here}}
+
+template<typename T> struct X {
+ X();
+ bool operator==(const X&) const = default; // expected-note 3{{deleted here}}
+ T t; // expected-note 2{{because there is no viable comparison function for member 't'}}
+ // expected-note at -1 {{because it would invoke a deleted comparison function for member 't'}}
+};
+
+struct Mutable {
+ bool operator==(const Mutable&) const = default;
+ mutable D d;
+};
+
+void test() {
+ void(X<A>() == X<A>()); // expected-error {{cannot be compared because its 'operator==' is implicitly deleted}}
+ void(X<B>() == X<B>());
+ void(X<C>() == X<C>());
+ void(X<D>() == X<D>()); // expected-error {{cannot be compared because its 'operator==' is implicitly deleted}}
+ void(Mutable() == Mutable());
+
+ // FIXME: Not deleted, but once we start synthesizing comparison function definitions, we should reject this.
+ void(X<E>() == X<E>());
+ // FIXME: Similarly, not deleted under P2002R0, but synthesized body is ill-formed.
+ void(X<F>() == X<F>());
+
+ void(X<G>() == X<G>()); // expected-error {{cannot be compared because its 'operator==' is implicitly deleted}}
+}
diff --git a/clang/test/CXX/class/class.compare/class.rel/p1.cpp b/clang/test/CXX/class/class.compare/class.rel/p1.cpp
index 3797d5f81f56..f61dd4a927d5 100644
--- a/clang/test/CXX/class/class.compare/class.rel/p1.cpp
+++ b/clang/test/CXX/class/class.compare/class.rel/p1.cpp
@@ -1,6 +1,8 @@
// RUN: %clang_cc1 -std=c++2a -verify %s
struct Good {
+ int operator<=>(const Good&) const;
+
bool operator<(const Good&) const = default;
bool operator>(const Good&) const = default;
friend bool operator<=(const Good&, const Good&) = default;
diff --git a/clang/test/CXX/class/class.compare/class.rel/p2.cpp b/clang/test/CXX/class/class.compare/class.rel/p2.cpp
new file mode 100644
index 000000000000..d81c9634131f
--- /dev/null
+++ b/clang/test/CXX/class/class.compare/class.rel/p2.cpp
@@ -0,0 +1,65 @@
+// RUN: %clang_cc1 -std=c++2a -verify %s
+
+namespace Rel {
+ struct A {
+ int operator<=>(A) const;
+ friend bool operator<(const A&, const A&) = default;
+ friend bool operator<=(const A&, const A&) = default;
+ friend bool operator>(const A&, const A&) = default;
+ friend bool operator>=(const A&, const A&) = default;
+ };
+ bool a1 = A() < A();
+ bool a2 = A() <= A();
+ bool a3 = A() > A();
+ bool a4 = A() >= A();
+
+ struct B {
+ bool operator<=>(B) const = delete; // expected-note 4{{deleted here}} expected-note-re 8{{candidate {{.*}} deleted}}
+ friend bool operator<(const B&, const B&) = default; // expected-warning {{implicitly deleted}} expected-note {{because it would invoke a deleted comparison}} expected-note-re {{candidate {{.*}} deleted}}
+ friend bool operator<=(const B&, const B&) = default; // expected-warning {{implicitly deleted}} expected-note {{because it would invoke a deleted comparison}} expected-note-re {{candidate {{.*}} deleted}}
+ friend bool operator>(const B&, const B&) = default; // expected-warning {{implicitly deleted}} expected-note {{because it would invoke a deleted comparison}} expected-note-re {{candidate {{.*}} deleted}}
+ friend bool operator>=(const B&, const B&) = default; // expected-warning {{implicitly deleted}} expected-note {{because it would invoke a deleted comparison}} expected-note-re {{candidate {{.*}} deleted}}
+ };
+ bool b1 = B() < B(); // expected-error {{deleted}}
+ bool b2 = B() <= B(); // expected-error {{deleted}}
+ bool b3 = B() > B(); // expected-error {{deleted}}
+ bool b4 = B() >= B(); // expected-error {{deleted}}
+
+ struct C {
+ friend bool operator<=>(const C&, const C&);
+ friend bool operator<(const C&, const C&); // expected-note {{because this non-rewritten comparison function would be the best match}}
+
+ bool operator<(const C&) const = default; // expected-warning {{implicitly deleted}}
+ bool operator>(const C&) const = default; // OK
+ };
+}
+
+// Under P2002R0, operator!= follows these rules too.
+namespace NotEqual {
+ struct A {
+ bool operator==(A) const;
+ friend bool operator!=(const A&, const A&) = default;
+ };
+ bool a = A() != A();
+
+ struct B {
+ bool operator==(B) const = delete; // expected-note {{deleted here}} expected-note-re 2{{candidate {{.*}} deleted}}
+ friend bool operator!=(const B&, const B&) = default; // expected-warning {{implicitly deleted}} expected-note {{because it would invoke a deleted comparison}} expected-note-re {{candidate {{.*}} deleted}}
+ };
+ bool b = B() != B(); // expected-error {{deleted}}
+
+ struct C {
+ friend bool operator==(const C&, const C&);
+ friend bool operator!=(const C&, const C&); // expected-note {{because this non-rewritten comparison function would be the best match}}
+
+ bool operator!=(const C&) const = default; // expected-warning {{implicitly deleted}}
+ };
+
+ // Ensure we don't go into an infinite loop diagnosing this: the first function
+ // is deleted because it calls the second function, which is deleted because it
+ // calls the first.
+ struct Evil {
+ friend bool operator!=(const Evil&, const Evil&) = default; // expected-warning {{implicitly deleted}} expected-note {{would be the best match}}
+ bool operator!=(const Evil&) const = default; // expected-warning {{implicitly deleted}} expected-note {{would be the best match}}
+ };
+}
diff --git a/clang/test/CXX/class/class.compare/class.spaceship/p1.cpp b/clang/test/CXX/class/class.compare/class.spaceship/p1.cpp
new file mode 100644
index 000000000000..928fe2036156
--- /dev/null
+++ b/clang/test/CXX/class/class.compare/class.spaceship/p1.cpp
@@ -0,0 +1,81 @@
+// RUN: %clang_cc1 -std=c++2a -verify %s
+
+namespace std {
+ struct strong_ordering {
+ int n;
+ constexpr operator int() const { return n; }
+ static const strong_ordering less, equal, greater;
+ };
+ constexpr strong_ordering strong_ordering::less{-1},
+ strong_ordering::equal{0}, strong_ordering::greater{1};
+}
+
+namespace Deletedness {
+ struct A {
+ std::strong_ordering operator<=>(const A&) const;
+ };
+ struct B {
+ bool operator==(const B&) const;
+ bool operator<(const B&) const;
+ };
+ struct C {
+ std::strong_ordering operator<=>(const C&) const = delete; // expected-note {{deleted}}
+ };
+ struct D1 {
+ bool operator==(const D1&) const;
+ std::strong_ordering operator<=>(int) const; // expected-note {{function not viable}} expected-note {{function (with reversed parameter order) not viable}}
+ bool operator<(int) const; // expected-note {{function not viable}}
+ };
+ struct D2 {
+ bool operator<(const D2&) const;
+ std::strong_ordering operator<=>(int) const; // expected-note {{function not viable}} expected-note {{function (with reversed parameter order) not viable}}
+ bool operator==(int) const; // expected-note {{function not viable}}
+ };
+ struct E {
+ bool operator==(const E&) const;
+ bool operator<(const E&) const = delete; // expected-note {{deleted}}
+ };
+ struct F {
+ std::strong_ordering operator<=>(const F&) const; // expected-note {{candidate}}
+ std::strong_ordering operator<=>(F) const; // expected-note {{candidate}}
+ };
+ struct G1 {
+ bool operator==(const G1&) const;
+ void operator<(const G1&) const;
+ };
+ struct G2 {
+ void operator==(const G2&) const;
+ bool operator<(const G2&) const;
+ };
+ struct H {
+ void operator<=>(const H&) const;
+ };
+
+ // expected-note@#base {{deleted comparison function for base class 'C'}}
+ // expected-note@#base {{no viable comparison function for base class 'D1'}}
+ // expected-note@#base {{three-way comparison cannot be synthesized because there is no viable function for '<' comparison}}
+ // expected-note@#base {{no viable comparison function for base class 'D2'}}
+ // expected-note@#base {{three-way comparison cannot be synthesized because there is no viable function for '==' comparison}}
+ // expected-note@#base {{deleted comparison function for base class 'E'}}
+ // expected-note@#base {{implied comparison for base class 'F' is ambiguous}}
+ template<typename T> struct Cmp : T { // #base
+ std::strong_ordering operator<=>(const Cmp&) const = default; // expected-note 5{{here}}
+ };
+
+ void use(...);
+ void f() {
+ use(
+ Cmp<A>() <=> Cmp<A>(),
+ Cmp<B>() <=> Cmp<B>(),
+ Cmp<C>() <=> Cmp<C>(), // expected-error {{deleted}}
+ Cmp<D1>() <=> Cmp<D1>(), // expected-error {{deleted}}
+ Cmp<D2>() <=> Cmp<D2>(), // expected-error {{deleted}}
+ Cmp<E>() <=> Cmp<E>(), // expected-error {{deleted}}
+ Cmp<F>() <=> Cmp<F>(), // expected-error {{deleted}}
+ Cmp<G1>() <=> Cmp<G1>(), // FIXME: ok but synthesized body is ill-formed
+ Cmp<G2>() <=> Cmp<G2>(), // FIXME: ok but synthesized body is ill-formed
+ Cmp<H>() <=> Cmp<H>(), // FIXME: ok but synthesized body is ill-formed
+ 0
+ );
+ }
+}
diff --git a/clang/www/cxx_status.html b/clang/www/cxx_status.html
index 2022402b6950..77089288cc0f 100755
--- a/clang/www/cxx_status.html
+++ b/clang/www/cxx_status.html
@@ -934,18 +934,16 @@ <h2 id="cxx20">C++2a implementation status</h2>
</tr>
<tr> <!-- from Rapperswil -->
<td><a href="https://wg21.link/p1120r0">P1120R0</a></td>
- <td rowspan="2" class="partial" align="center">Partial</td>
+ <td rowspan="4" class="partial" align="center">Partial</td>
</tr>
<tr> <!-- from Kona 2019 -->
<td><a href="https://wg21.link/p1185r2">P1185R2</a></td>
</tr>
<tr> <!-- from Cologne -->
<td><a href="https://wg21.link/p1186r3">P1186R3</a></td>
- <td class="none" align="center">No</td>
</tr>
<tr>
<td><a href="https://wg21.link/p1630r1">P1630R1</a></td>
- <td class="partial" align="center">Partial</td>
</tr>
<tr> <!-- from Belfast -->
<td><a href="https://wg21.link/p1946r0">P1946R0</a></td>
More information about the cfe-commits
mailing list