From 5253d9138eb31252594f5e14133df731551839c7 Mon Sep 17 00:00:00 2001 From: Richard Smith Date: Wed, 6 Nov 2019 12:03:12 -0800 Subject: [PATCH] [c++20] Determine whether a defaulted comparison should be deleted or constexpr. --- clang/include/clang/AST/ComparisonCategories.h | 1 - clang/include/clang/Basic/DiagnosticGroups.td | 2 +- clang/include/clang/Basic/DiagnosticSemaKinds.td | 58 ++- clang/include/clang/Sema/Overload.h | 18 +- clang/include/clang/Sema/Sema.h | 7 + clang/lib/Sema/SemaDeclCXX.cpp | 550 ++++++++++++++++++--- clang/lib/Sema/SemaExpr.cpp | 13 +- clang/lib/Sema/SemaOverload.cpp | 142 +++--- .../class.compare/class.compare.default/p2.cpp | 157 ++++-- .../class.compare/class.compare.default/p3.cpp | 192 +++++++ clang/test/CXX/class/class.compare/class.eq/p1.cpp | 12 +- clang/test/CXX/class/class.compare/class.eq/p2.cpp | 40 ++ .../test/CXX/class/class.compare/class.rel/p1.cpp | 2 + .../test/CXX/class/class.compare/class.rel/p2.cpp | 65 +++ .../CXX/class/class.compare/class.spaceship/p1.cpp | 81 +++ clang/www/cxx_status.html | 4 +- 16 files changed, 1126 insertions(+), 218 deletions(-) create mode 100644 clang/test/CXX/class/class.compare/class.compare.default/p3.cpp create mode 100644 clang/test/CXX/class/class.compare/class.eq/p2.cpp create mode 100644 clang/test/CXX/class/class.compare/class.rel/p2.cpp create mode 100644 clang/test/CXX/class/class.compare/class.spaceship/p1.cpp diff --git a/clang/include/clang/AST/ComparisonCategories.h b/clang/include/clang/AST/ComparisonCategories.h index 9d591cc..3dd1db1 100644 --- a/clang/include/clang/AST/ComparisonCategories.h +++ b/clang/include/clang/AST/ComparisonCategories.h @@ -221,7 +221,6 @@ public: return const_cast(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 e999ba1..6cb1a4e 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 a2e4cf5..b6586be 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>; + "deleted">, InGroup; 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; -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; -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; +def warn_defaulted_comparison_deleted : Warning< + "explicitly defaulted %sub{select_defaulted_comparison_kind}0 is implicitly " + "deleted">, InGroup; +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 c6012d7..e0c3ba1 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 different + // 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 f117cca..ed1f137 100644 --- a/clang/include/clang/Sema/Sema.h +++ b/clang/include/clang/Sema/Sema.h @@ -3310,6 +3310,10 @@ public: const UnresolvedSetImpl &Fns, Expr *input, bool RequiresADL = true); + void LookupOverloadedBinOp(OverloadCandidateSet &CandidateSet, + OverloadedOperatorKind Op, + const UnresolvedSetImpl &Fns, + ArrayRef Args, bool RequiresADL = true); ExprResult CreateOverloadedBinOp(SourceLocation OpLoc, BinaryOperatorKind Opc, const UnresolvedSetImpl &Fns, @@ -5310,6 +5314,9 @@ public: 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 1561960..ba516b6 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 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(D->getFriendDecl()); - if (FD && !FD->isInvalidDecl() && FD->isExplicitlyDefaulted()) - CheckExplicitlyDefaultedFunction(FD); + CheckForDefaultedFunction( + dyn_cast_or_null(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 differently 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 +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(*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(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 { +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 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 different 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 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(FD), DFK.asSpecialMember(), + nullptr, /*Diagnose=*/true); + } else { + DefaultedComparisonAnalyzer( + *this, cast(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(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 e2c37f8..5eeeba3 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(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 6f8ad63..27e1101 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 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()->getParamTypes(); if (isa(Cand->Function) && - !isa(Cand->Function)) { + !isa(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 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(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 eb4789e..bbc9060 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 0000000..f6daaf0 --- /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 622f66c..72f6ad5 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 0000000..1a515de --- /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@+2 {{candidate function not viable: 'this' argument has type 'const}} + // expected-note@+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 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@-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() == X()); // expected-error {{cannot be compared because its 'operator==' is implicitly deleted}} + void(X() == X()); + void(X() == X()); + void(X() == X()); // 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() == X()); + // FIXME: Similarly, not deleted under P2002R0, but synthesized body is ill-formed. + void(X() == X()); + + void(X() == X()); // 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 3797d5f..f61dd4a 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 0000000..d81c963 --- /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 0000000..928fe20 --- /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 struct Cmp : T { // #base + std::strong_ordering operator<=>(const Cmp&) const = default; // expected-note 5{{here}} + }; + + void use(...); + void f() { + use( + Cmp() <=> Cmp(), + Cmp() <=> Cmp(), + Cmp() <=> Cmp(), // expected-error {{deleted}} + Cmp() <=> Cmp(), // expected-error {{deleted}} + Cmp() <=> Cmp(), // expected-error {{deleted}} + Cmp() <=> Cmp(), // expected-error {{deleted}} + Cmp() <=> Cmp(), // expected-error {{deleted}} + Cmp() <=> Cmp(), // FIXME: ok but synthesized body is ill-formed + Cmp() <=> Cmp(), // FIXME: ok but synthesized body is ill-formed + Cmp() <=> Cmp(), // FIXME: ok but synthesized body is ill-formed + 0 + ); + } +} diff --git a/clang/www/cxx_status.html b/clang/www/cxx_status.html index 2022402..7708928 100755 --- a/clang/www/cxx_status.html +++ b/clang/www/cxx_status.html @@ -934,18 +934,16 @@ as the draft C++2a standard evolves. P1120R0 - Partial + Partial P1185R2 P1186R3 - No P1630R1 - Partial P1946R0 -- 2.7.4