[clang] cafc741 - [c++20] Synthesis of defaulted comparison functions.
Richard Smith via cfe-commits
cfe-commits at lists.llvm.org
Sun Dec 8 23:31:21 PST 2019
Author: Richard Smith
Date: 2019-12-08T23:21:52-08:00
New Revision: cafc7416baf7eecef8ecaf05802f2f7c0da725c0
URL: https://github.com/llvm/llvm-project/commit/cafc7416baf7eecef8ecaf05802f2f7c0da725c0
DIFF: https://github.com/llvm/llvm-project/commit/cafc7416baf7eecef8ecaf05802f2f7c0da725c0.diff
LOG: [c++20] Synthesis of defaulted comparison functions.
Array members are not yet handled. In addition, defaulted comparisons
can't yet find comparison operators by unqualified lookup (only by
member lookup and ADL). These issues will be fixed in follow-on changes.
Added:
clang/test/CXX/class/class.compare/class.compare.default/p5.cpp
clang/test/CXX/class/class.compare/class.eq/p3.cpp
clang/test/CXX/class/class.compare/class.spaceship/p3.cpp
Modified:
clang/include/clang/AST/Decl.h
clang/include/clang/AST/DeclCXX.h
clang/include/clang/Basic/DiagnosticSemaKinds.td
clang/include/clang/Sema/Sema.h
clang/lib/AST/ExprConstant.cpp
clang/lib/Sema/SemaDeclCXX.cpp
clang/lib/Sema/SemaExpr.cpp
clang/lib/Sema/SemaOverload.cpp
clang/lib/Sema/SemaTemplateInstantiate.cpp
clang/test/CXX/class/class.compare/class.compare.default/p2.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
Removed:
################################################################################
diff --git a/clang/include/clang/AST/Decl.h b/clang/include/clang/AST/Decl.h
index f4913540bab4..2d1a30657cbd 100644
--- a/clang/include/clang/AST/Decl.h
+++ b/clang/include/clang/AST/Decl.h
@@ -2115,6 +2115,16 @@ class FunctionDecl : public DeclaratorDecl,
FunctionDeclBits.IsExplicitlyDefaulted = ED;
}
+ /// True if this method is user-declared and was not
+ /// deleted or defaulted on its first declaration.
+ bool isUserProvided() const {
+ auto *DeclAsWritten = this;
+ if (FunctionDecl *Pattern = getTemplateInstantiationPattern())
+ DeclAsWritten = Pattern;
+ return !(DeclAsWritten->isDeleted() ||
+ DeclAsWritten->getCanonicalDecl()->isDefaulted());
+ }
+
/// Whether falling off this function implicitly returns null/zero.
/// If a more specific implicit return value is required, front-ends
/// should synthesize the appropriate return statements.
diff --git a/clang/include/clang/AST/DeclCXX.h b/clang/include/clang/AST/DeclCXX.h
index 0f2018fb9e8c..0043ce1a92dd 100644
--- a/clang/include/clang/AST/DeclCXX.h
+++ b/clang/include/clang/AST/DeclCXX.h
@@ -1995,16 +1995,6 @@ class CXXMethodDecl : public FunctionDecl {
return const_cast<CXXMethodDecl*>(this)->getMostRecentDecl();
}
- /// True if this method is user-declared and was not
- /// deleted or defaulted on its first declaration.
- bool isUserProvided() const {
- auto *DeclAsWritten = this;
- if (auto *Pattern = getTemplateInstantiationPattern())
- DeclAsWritten = cast<CXXMethodDecl>(Pattern);
- return !(DeclAsWritten->isDeleted() ||
- DeclAsWritten->getCanonicalDecl()->isDefaulted());
- }
-
void addOverriddenMethod(const CXXMethodDecl *MD);
using method_iterator = const CXXMethodDecl *const *;
diff --git a/clang/include/clang/Basic/DiagnosticSemaKinds.td b/clang/include/clang/Basic/DiagnosticSemaKinds.td
index dcfc8fb5de9c..939287014d9e 100644
--- a/clang/include/clang/Basic/DiagnosticSemaKinds.td
+++ b/clang/include/clang/Basic/DiagnosticSemaKinds.td
@@ -1732,7 +1732,10 @@ def note_ivar_decl : Note<"instance variable is declared here">;
def note_bitfield_decl : Note<"bit-field is declared here">;
def note_implicit_param_decl : Note<"%0 is an implicit parameter">;
def note_member_synthesized_at : Note<
- "in implicit %sub{select_special_member_kind}0 for %1 "
+ "in %select{implicit|defaulted}0 %sub{select_special_member_kind}1 for %2 "
+ "first required here">;
+def note_comparison_synthesized_at : Note<
+ "in defaulted %sub{select_defaulted_comparison_kind}0 for %1 "
"first required here">;
def err_missing_default_ctor : Error<
"%select{constructor for %1 must explicitly initialize the|"
diff --git a/clang/include/clang/Sema/Sema.h b/clang/include/clang/Sema/Sema.h
index ed1f1370b332..2b0db07e6bac 100644
--- a/clang/include/clang/Sema/Sema.h
+++ b/clang/include/clang/Sema/Sema.h
@@ -3319,7 +3319,12 @@ class Sema final {
const UnresolvedSetImpl &Fns,
Expr *LHS, Expr *RHS,
bool RequiresADL = true,
- bool AllowRewrittenCandidates = true);
+ bool AllowRewrittenCandidates = true,
+ FunctionDecl *DefaultedFn = nullptr);
+ ExprResult BuildSynthesizedThreeWayComparison(SourceLocation OpLoc,
+ const UnresolvedSetImpl &Fns,
+ Expr *LHS, Expr *RHS,
+ FunctionDecl *DefaultedFn);
ExprResult CreateOverloadedArraySubscriptExpr(SourceLocation LLoc,
SourceLocation RLoc,
@@ -6516,6 +6521,8 @@ class Sema final {
bool CheckExplicitlyDefaultedComparison(FunctionDecl *MD,
DefaultedComparisonKind DCK);
+ void DefineDefaultedComparison(SourceLocation Loc, FunctionDecl *FD,
+ DefaultedComparisonKind DCK);
//===--------------------------------------------------------------------===//
// C++ Derived Classes
diff --git a/clang/lib/AST/ExprConstant.cpp b/clang/lib/AST/ExprConstant.cpp
index 3151ec044cb9..5aa151984e51 100644
--- a/clang/lib/AST/ExprConstant.cpp
+++ b/clang/lib/AST/ExprConstant.cpp
@@ -6830,6 +6830,36 @@ class ExprEvaluatorBase
return StmtVisitorTy::Visit(Source);
}
+ bool VisitPseudoObjectExpr(const PseudoObjectExpr *E) {
+ for (const Expr *SemE : E->semantics()) {
+ if (auto *OVE = dyn_cast<OpaqueValueExpr>(SemE)) {
+ // FIXME: We can't handle the case where an OpaqueValueExpr is also the
+ // result expression: there could be two
diff erent LValues that would
+ // refer to the same object in that case, and we can't model that.
+ if (SemE == E->getResultExpr())
+ return Error(E);
+
+ // Unique OVEs get evaluated if and when we encounter them when
+ // emitting the rest of the semantic form, rather than eagerly.
+ if (OVE->isUnique())
+ continue;
+
+ LValue LV;
+ if (!Evaluate(Info.CurrentCall->createTemporary(
+ OVE, getStorageType(Info.Ctx, OVE), false, LV),
+ Info, OVE->getSourceExpr()))
+ return false;
+ } else if (SemE == E->getResultExpr()) {
+ if (!StmtVisitorTy::Visit(SemE))
+ return false;
+ } else {
+ if (!EvaluateIgnoredValue(Info, SemE))
+ return false;
+ }
+ }
+ return true;
+ }
+
bool VisitCallExpr(const CallExpr *E) {
APValue Result;
if (!handleCallExpr(E, Result, nullptr))
diff --git a/clang/lib/Sema/SemaDeclCXX.cpp b/clang/lib/Sema/SemaDeclCXX.cpp
index ba516b66608e..c8b95983f03c 100644
--- a/clang/lib/Sema/SemaDeclCXX.cpp
+++ b/clang/lib/Sema/SemaDeclCXX.cpp
@@ -7085,7 +7085,8 @@ namespace {
///
/// This is accomplished by performing two visitation steps over the eventual
/// body of the function.
-template<typename Derived, typename Result, typename Subobject>
+template<typename Derived, typename ResultList, typename Result,
+ typename Subobject>
class DefaultedComparisonVisitor {
public:
using DefaultedComparisonKind = Sema::DefaultedComparisonKind;
@@ -7094,45 +7095,54 @@ class DefaultedComparisonVisitor {
DefaultedComparisonKind DCK)
: S(S), RD(RD), FD(FD), DCK(DCK) {}
- Result visit() {
+ ResultList visit() {
// The type of an lvalue naming a parameter of this function.
QualType ParamLvalType =
FD->getParamDecl(0)->getType().getNonReferenceType();
+ ResultList Results;
+
switch (DCK) {
case DefaultedComparisonKind::None:
llvm_unreachable("not a defaulted comparison");
case DefaultedComparisonKind::Equal:
case DefaultedComparisonKind::ThreeWay:
- return getDerived().visitSubobjects(RD, ParamLvalType.getQualifiers());
+ getDerived().visitSubobjects(Results, RD, ParamLvalType.getQualifiers());
+ return Results;
case DefaultedComparisonKind::NotEqual:
case DefaultedComparisonKind::Relational:
- return getDerived().visitExpandedSubobject(
- ParamLvalType, getDerived().getCompleteObject());
+ Results.add(getDerived().visitExpandedSubobject(
+ ParamLvalType, getDerived().getCompleteObject()));
+ return Results;
}
}
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 [...]
+ /// Visit the expanded list of subobjects of the given type, as specified in
+ /// C++2a [class.compare.default].
+ ///
+ /// \return \c true if the ResultList object said we're done, \c false if not.
+ bool visitSubobjects(ResultList &Results, CXXRecordDecl *Record,
+ Qualifiers Quals) {
+ // C++2a [class.compare.default]p4:
+ // The direct base class subobjects of C
for (CXXBaseSpecifier &Base : Record->bases())
- if (R.add(getDerived().visitSubobject(
+ if (Results.add(getDerived().visitSubobject(
S.Context.getQualifiedType(Base.getType(), Quals),
getDerived().getBase(&Base))))
- return R;
- // followed by the non-static data members of C [...]
+ return true;
+
+ // 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;
+ if (visitSubobjects(Results, Field->getType()->getAsCXXRecordDecl(),
+ Quals))
+ return true;
continue;
}
@@ -7143,12 +7153,13 @@ class DefaultedComparisonVisitor {
QualType FieldType =
S.Context.getQualifiedType(Field->getType(), FieldQuals);
- if (R.add(getDerived().visitSubobject(FieldType,
- getDerived().getField(Field))))
- return R;
+ if (Results.add(getDerived().visitSubobject(
+ FieldType, getDerived().getField(Field))))
+ return true;
}
+
// form a list of subobjects.
- return R;
+ return false;
}
Result visitSubobject(QualType Type, Subobject Subobj) {
@@ -7199,6 +7210,7 @@ struct DefaultedComparisonSubobject {
/// whether that body would be deleted or constexpr.
class DefaultedComparisonAnalyzer
: public DefaultedComparisonVisitor<DefaultedComparisonAnalyzer,
+ DefaultedComparisonInfo,
DefaultedComparisonInfo,
DefaultedComparisonSubobject> {
public:
@@ -7407,6 +7419,260 @@ class DefaultedComparisonAnalyzer
return R;
}
};
+
+/// A list of statements.
+struct StmtListResult {
+ bool IsInvalid = false;
+ llvm::SmallVector<Stmt*, 16> Stmts;
+
+ bool add(const StmtResult &S) {
+ IsInvalid |= S.isInvalid();
+ if (IsInvalid)
+ return true;
+ Stmts.push_back(S.get());
+ return false;
+ }
+};
+
+/// A visitor over the notional body of a defaulted comparison that synthesizes
+/// the actual body.
+class DefaultedComparisonSynthesizer
+ : public DefaultedComparisonVisitor<DefaultedComparisonSynthesizer,
+ StmtListResult, StmtResult,
+ std::pair<ExprResult, ExprResult>> {
+ SourceLocation Loc;
+
+public:
+ using Base = DefaultedComparisonVisitor;
+ using ExprPair = std::pair<ExprResult, ExprResult>;
+
+ friend Base;
+
+ DefaultedComparisonSynthesizer(Sema &S, CXXRecordDecl *RD, FunctionDecl *FD,
+ DefaultedComparisonKind DCK,
+ SourceLocation BodyLoc)
+ : Base(S, RD, FD, DCK), Loc(BodyLoc) {}
+
+ /// Build a suitable function body for this defaulted comparison operator.
+ StmtResult build() {
+ Sema::CompoundScopeRAII CompoundScope(S);
+
+ StmtListResult Stmts = visit();
+ if (Stmts.IsInvalid)
+ return StmtError();
+
+ ExprResult RetVal;
+ switch (DCK) {
+ case DefaultedComparisonKind::None:
+ llvm_unreachable("not a defaulted comparison");
+
+ case DefaultedComparisonKind::Equal:
+ // C++2a [class.eq]p3:
+ // [...] compar[e] the corresponding elements [...] until the first
+ // index i where xi == yi yields [...] false. If no such index exists,
+ // V is true. Otherwise, V is false.
+ //
+ // Join the comparisons with '&&'s and return the result. Use a right
+ // fold because that short-circuits more naturally.
+ for (Stmt *EAsStmt : llvm::reverse(Stmts.Stmts)) {
+ Expr *E = cast<Expr>(EAsStmt);
+ if (RetVal.isUnset()) {
+ RetVal = E;
+ continue;
+ }
+ RetVal = S.CreateBuiltinBinOp(Loc, BO_LAnd, E, RetVal.get());
+ if (RetVal.isInvalid())
+ return StmtError();
+ }
+ // If no such index exists, V is true.
+ if (RetVal.isUnset())
+ RetVal = S.ActOnCXXBoolLiteral(Loc, tok::kw_true);
+ Stmts.Stmts.clear();
+ break;
+
+ case DefaultedComparisonKind::ThreeWay: {
+ // Per C++2a [class.spaceship]p3, as a fallback add:
+ // return static_cast<R>(std::strong_ordering::equal);
+ QualType StrongOrdering = S.CheckComparisonCategoryType(
+ ComparisonCategoryType::StrongOrdering, Loc);
+ if (StrongOrdering.isNull())
+ return StmtError();
+ VarDecl *EqualVD = S.Context.CompCategories.getInfoForType(StrongOrdering)
+ .getValueInfo(ComparisonCategoryResult::Equal)
+ ->VD;
+ RetVal = S.BuildDeclarationNameExpr(
+ CXXScopeSpec(), DeclarationNameInfo(), EqualVD);
+ if (RetVal.isInvalid())
+ return StmtError();
+ RetVal = buildStaticCastToR(RetVal.get());
+ break;
+ }
+
+ case DefaultedComparisonKind::NotEqual:
+ case DefaultedComparisonKind::Relational:
+ RetVal = cast<Expr>(Stmts.Stmts.pop_back_val());
+ break;
+ }
+
+ // Build the final return statement.
+ if (RetVal.isInvalid())
+ return StmtError();
+ StmtResult ReturnStmt = S.BuildReturnStmt(Loc, RetVal.get());
+ if (ReturnStmt.isInvalid())
+ return StmtError();
+ Stmts.Stmts.push_back(ReturnStmt.get());
+
+ return S.ActOnCompoundStmt(Loc, Loc, Stmts.Stmts, /*IsStmtExpr=*/false);
+ }
+
+private:
+ ExprResult getParam(unsigned I) {
+ ParmVarDecl *PD = FD->getParamDecl(I);
+ return S.BuildDeclarationNameExpr(
+ CXXScopeSpec(), DeclarationNameInfo(PD->getDeclName(), Loc), PD);
+ }
+
+ ExprPair getCompleteObject() {
+ unsigned Param = 0;
+ ExprResult LHS;
+ if (isa<CXXMethodDecl>(FD)) {
+ // LHS is '*this'.
+ LHS = S.ActOnCXXThis(Loc);
+ if (!LHS.isInvalid())
+ LHS = S.CreateBuiltinUnaryOp(Loc, UO_Deref, LHS.get());
+ } else {
+ LHS = getParam(Param++);
+ }
+ ExprResult RHS = getParam(Param++);
+ assert(Param == FD->getNumParams());
+ return {LHS, RHS};
+ }
+
+ ExprPair getBase(CXXBaseSpecifier *Base) {
+ ExprPair Obj = getCompleteObject();
+ if (Obj.first.isInvalid() || Obj.second.isInvalid())
+ return {ExprError(), ExprError()};
+ CXXCastPath Path = {Base};
+ return {S.ImpCastExprToType(Obj.first.get(), Base->getType(),
+ CK_DerivedToBase, VK_LValue, &Path),
+ S.ImpCastExprToType(Obj.second.get(), Base->getType(),
+ CK_DerivedToBase, VK_LValue, &Path)};
+ }
+
+ ExprPair getField(FieldDecl *Field) {
+ ExprPair Obj = getCompleteObject();
+ if (Obj.first.isInvalid() || Obj.second.isInvalid())
+ return {ExprError(), ExprError()};
+
+ DeclAccessPair Found = DeclAccessPair::make(Field, Field->getAccess());
+ DeclarationNameInfo NameInfo(Field->getDeclName(), Loc);
+ return {S.BuildFieldReferenceExpr(Obj.first.get(), /*IsArrow=*/false, Loc,
+ CXXScopeSpec(), Field, Found, NameInfo),
+ S.BuildFieldReferenceExpr(Obj.second.get(), /*IsArrow=*/false, Loc,
+ CXXScopeSpec(), Field, Found, NameInfo)};
+ }
+
+ // FIXME: When expanding a subobject, register a note in the code synthesis
+ // stack to say which subobject we're comparing.
+
+ // FIXME: Build a loop for an array subobject.
+
+ StmtResult visitExpandedSubobject(QualType Type, ExprPair Obj) {
+ UnresolvedSet<4> Fns; // FIXME: Track this.
+
+ if (Obj.first.isInvalid() || Obj.second.isInvalid())
+ return StmtError();
+
+ OverloadedOperatorKind OO = FD->getOverloadedOperator();
+ ExprResult Op = S.CreateOverloadedBinOp(
+ Loc, BinaryOperator::getOverloadedOpcode(OO), Fns,
+ Obj.first.get(), Obj.second.get(), /*PerformADL=*/true,
+ /*AllowRewrittenCandidates=*/true, FD);
+ if (Op.isInvalid())
+ return StmtError();
+
+ switch (DCK) {
+ case DefaultedComparisonKind::None:
+ llvm_unreachable("not a defaulted comparison");
+
+ case DefaultedComparisonKind::Equal:
+ // Per C++2a [class.eq]p2, each comparison is individually contextually
+ // converted to bool.
+ Op = S.PerformContextuallyConvertToBool(Op.get());
+ if (Op.isInvalid())
+ return StmtError();
+ return Op.get();
+
+ case DefaultedComparisonKind::ThreeWay: {
+ // Per C++2a [class.spaceship]p3, form:
+ // if (R cmp = static_cast<R>(op); cmp != 0)
+ // return cmp;
+ QualType R = FD->getReturnType();
+ Op = buildStaticCastToR(Op.get());
+ if (Op.isInvalid())
+ return StmtError();
+
+ // R cmp = ...;
+ IdentifierInfo *Name = &S.Context.Idents.get("cmp");
+ VarDecl *VD =
+ VarDecl::Create(S.Context, S.CurContext, Loc, Loc, Name, R,
+ S.Context.getTrivialTypeSourceInfo(R, Loc), SC_None);
+ S.AddInitializerToDecl(VD, Op.get(), /*DirectInit=*/false);
+ Stmt *InitStmt = new (S.Context) DeclStmt(DeclGroupRef(VD), Loc, Loc);
+
+ // cmp != 0
+ ExprResult VDRef = S.BuildDeclarationNameExpr(
+ CXXScopeSpec(), DeclarationNameInfo(Name, Loc), VD);
+ if (VDRef.isInvalid())
+ return StmtError();
+ llvm::APInt ZeroVal(S.Context.getIntWidth(S.Context.IntTy), 0);
+ Expr *Zero =
+ IntegerLiteral::Create(S.Context, ZeroVal, S.Context.IntTy, Loc);
+ ExprResult Comp = S.CreateOverloadedBinOp(Loc, BO_NE, Fns, VDRef.get(),
+ Zero, true, true, FD);
+ if (Comp.isInvalid())
+ return StmtError();
+ Sema::ConditionResult Cond = S.ActOnCondition(
+ nullptr, Loc, Comp.get(), Sema::ConditionKind::Boolean);
+ if (Cond.isInvalid())
+ return StmtError();
+
+ // return cmp;
+ VDRef = S.BuildDeclarationNameExpr(
+ CXXScopeSpec(), DeclarationNameInfo(Name, Loc), VD);
+ if (VDRef.isInvalid())
+ return StmtError();
+ StmtResult ReturnStmt = S.BuildReturnStmt(Loc, VDRef.get());
+ if (ReturnStmt.isInvalid())
+ return StmtError();
+
+ // if (...)
+ return S.ActOnIfStmt(Loc, /*IsConstexpr=*/false, InitStmt, Cond,
+ ReturnStmt.get(), /*ElseLoc=*/SourceLocation(),
+ /*Else=*/nullptr);
+ }
+
+ case DefaultedComparisonKind::NotEqual:
+ case DefaultedComparisonKind::Relational:
+ // C++2a [class.compare.secondary]p2:
+ // Otherwise, the operator function yields x @ y.
+ return Op.get();
+ }
+ }
+
+ /// Build "static_cast<R>(E)".
+ ExprResult buildStaticCastToR(Expr *E) {
+ QualType R = FD->getReturnType();
+ assert(!R->isUndeducedType() && "type should have been deduced already");
+
+ // Don't bother forming a no-op cast in the common case.
+ if (E->isRValue() && S.Context.hasSameType(E->getType(), R))
+ return E;
+ return S.BuildCXXNamedCast(Loc, tok::kw_static_cast,
+ S.Context.getTrivialTypeSourceInfo(R, Loc), E,
+ SourceRange(Loc, Loc), SourceRange(Loc, Loc));
+ }
+};
}
bool Sema::CheckExplicitlyDefaultedComparison(FunctionDecl *FD,
@@ -7540,6 +7806,43 @@ bool Sema::CheckExplicitlyDefaultedComparison(FunctionDecl *FD,
return false;
}
+void Sema::DefineDefaultedComparison(SourceLocation UseLoc, FunctionDecl *FD,
+ DefaultedComparisonKind DCK) {
+ assert(FD->isDefaulted() && !FD->isDeleted() &&
+ !FD->doesThisDeclarationHaveABody());
+ if (FD->willHaveBody() || FD->isInvalidDecl())
+ return;
+
+ SynthesizedFunctionScope Scope(*this, FD);
+
+ // The exception specification is needed because we are defining the
+ // function.
+ // FIXME: Handle this better. Computing the exception specification will
+ // eventually need the function body.
+ ResolveExceptionSpec(UseLoc, FD->getType()->castAs<FunctionProtoType>());
+
+ // Add a context note for diagnostics produced after this point.
+ Scope.addContextNote(UseLoc);
+
+ // Build and set up the function body.
+ {
+ CXXRecordDecl *RD = cast<CXXRecordDecl>(FD->getLexicalParent());
+ SourceLocation BodyLoc =
+ FD->getEndLoc().isValid() ? FD->getEndLoc() : FD->getLocation();
+ StmtResult Body =
+ DefaultedComparisonSynthesizer(*this, RD, FD, DCK, BodyLoc).build();
+ if (Body.isInvalid()) {
+ FD->setInvalidDecl();
+ return;
+ }
+ FD->setBody(Body.get());
+ FD->markUsed(Context);
+ }
+
+ if (ASTMutationListener *L = getASTMutationListener())
+ L->CompletedImplicitDefinition(FD);
+}
+
void Sema::CheckDelayedMemberExceptionSpecs() {
decltype(DelayedOverridingExceptionSpecChecks) Overriding;
decltype(DelayedEquivalentExceptionSpecChecks) Equivalent;
diff --git a/clang/lib/Sema/SemaExpr.cpp b/clang/lib/Sema/SemaExpr.cpp
index 5eeeba3c2d12..b97352e27e15 100644
--- a/clang/lib/Sema/SemaExpr.cpp
+++ b/clang/lib/Sema/SemaExpr.cpp
@@ -15416,9 +15416,8 @@ static OdrUseContext isOdrUseContext(Sema &SemaRef) {
}
static bool isImplicitlyDefinableConstexprFunction(FunctionDecl *Func) {
- CXXMethodDecl *MD = dyn_cast<CXXMethodDecl>(Func);
return Func->isConstexpr() &&
- (Func->isImplicitlyInstantiable() || (MD && !MD->isUserProvided()));
+ (Func->isImplicitlyInstantiable() || !Func->isUserProvided());
}
/// Mark a function referenced, and check whether it is odr-used
@@ -15566,6 +15565,12 @@ void Sema::MarkFunctionReferenced(SourceLocation Loc, FunctionDecl *Func,
MarkVTableUsed(Loc, MethodDecl->getParent());
}
+ if (Func->isDefaulted() && !Func->isDeleted()) {
+ DefaultedComparisonKind DCK = getDefaultedComparisonKind(Func);
+ if (DCK != DefaultedComparisonKind::None)
+ DefineDefaultedComparison(Loc, Func, DCK);
+ }
+
// Implicit instantiation of function templates and member functions of
// class templates.
if (Func->isImplicitlyInstantiable()) {
diff --git a/clang/lib/Sema/SemaOverload.cpp b/clang/lib/Sema/SemaOverload.cpp
index 27e1101b482d..344e54b7f3fc 100644
--- a/clang/lib/Sema/SemaOverload.cpp
+++ b/clang/lib/Sema/SemaOverload.cpp
@@ -12835,11 +12835,19 @@ void Sema::LookupOverloadedBinOp(OverloadCandidateSet &CandidateSet,
///
/// \param LHS Left-hand argument.
/// \param RHS Right-hand argument.
+/// \param PerformADL Whether to consider operator candidates found by ADL.
+/// \param AllowRewrittenCandidates Whether to consider candidates found by
+/// C++20 operator rewrites.
+/// \param DefaultedFn If we are synthesizing a defaulted operator function,
+/// the function in question. Such a function is never a candidate in
+/// our overload resolution. This also enables synthesizing a three-way
+/// comparison from < and == as described in C++20 [class.spaceship]p1.
ExprResult Sema::CreateOverloadedBinOp(SourceLocation OpLoc,
BinaryOperatorKind Opc,
const UnresolvedSetImpl &Fns, Expr *LHS,
Expr *RHS, bool PerformADL,
- bool AllowRewrittenCandidates) {
+ bool AllowRewrittenCandidates,
+ FunctionDecl *DefaultedFn) {
Expr *Args[2] = { LHS, RHS };
LHS=RHS=nullptr; // Please use only Args instead of LHS/RHS couple
@@ -12906,6 +12914,8 @@ ExprResult Sema::CreateOverloadedBinOp(SourceLocation OpLoc,
OverloadCandidateSet CandidateSet(
OpLoc, OverloadCandidateSet::CSK_Operator,
OverloadCandidateSet::OperatorRewriteInfo(Op, AllowRewrittenCandidates));
+ if (DefaultedFn)
+ CandidateSet.exclude(DefaultedFn);
LookupOverloadedBinOp(CandidateSet, Op, Fns, Args, PerformADL);
bool HadMultipleCandidates = (CandidateSet.size() > 1);
@@ -13113,6 +13123,15 @@ ExprResult Sema::CreateOverloadedBinOp(SourceLocation OpLoc,
if (Opc == BO_Comma)
break;
+ // When defaulting an 'operator<=>', we can try to synthesize a three-way
+ // compare result using '==' and '<'.
+ if (DefaultedFn && Opc == BO_Cmp) {
+ ExprResult E = BuildSynthesizedThreeWayComparison(OpLoc, Fns, Args[0],
+ Args[1], DefaultedFn);
+ if (E.isInvalid() || E.isUsable())
+ return E;
+ }
+
// For class as left operand for assignment or compound assignment
// operator do not fall through to handling in built-in, but report that
// no overloaded assignment operator found
@@ -13194,6 +13213,111 @@ ExprResult Sema::CreateOverloadedBinOp(SourceLocation OpLoc,
return CreateBuiltinBinOp(OpLoc, Opc, Args[0], Args[1]);
}
+ExprResult Sema::BuildSynthesizedThreeWayComparison(
+ SourceLocation OpLoc, const UnresolvedSetImpl &Fns, Expr *LHS, Expr *RHS,
+ FunctionDecl *DefaultedFn) {
+ const ComparisonCategoryInfo *Info =
+ Context.CompCategories.lookupInfoForType(DefaultedFn->getReturnType());
+ // If we're not producing a known comparison category type, we can't
+ // synthesize a three-way comparison. Let the caller diagnose this.
+ if (!Info)
+ return ExprResult((Expr*)nullptr);
+
+ // If we ever want to perform this synthesis more generally, we will need to
+ // apply the temporary materialization conversion to the operands.
+ assert(LHS->isGLValue() && RHS->isGLValue() &&
+ "cannot use prvalue expressions more than once");
+ Expr *OrigLHS = LHS;
+ Expr *OrigRHS = RHS;
+
+ // Replace the LHS and RHS with OpaqueValueExprs; we're going to refer to
+ // each of them multiple times below.
+ LHS = new (Context)
+ OpaqueValueExpr(LHS->getExprLoc(), LHS->getType(), LHS->getValueKind(),
+ LHS->getObjectKind(), LHS);
+ RHS = new (Context)
+ OpaqueValueExpr(RHS->getExprLoc(), RHS->getType(), RHS->getValueKind(),
+ RHS->getObjectKind(), RHS);
+
+ ExprResult Eq = CreateOverloadedBinOp(OpLoc, BO_EQ, Fns, LHS, RHS, true, true,
+ DefaultedFn);
+ if (Eq.isInvalid())
+ return ExprError();
+
+ ExprResult Less;
+ if (Info->isOrdered()) {
+ Less = CreateOverloadedBinOp(OpLoc, BO_LT, Fns, LHS, RHS, true, true,
+ DefaultedFn);
+ if (Less.isInvalid())
+ return ExprError();
+ }
+
+ ExprResult Greater;
+ if (Info->isOrdered()) {
+ Greater = CreateOverloadedBinOp(OpLoc, BO_LT, Fns, RHS, LHS, true, true,
+ DefaultedFn);
+ if (Greater.isInvalid())
+ return ExprError();
+ }
+
+ // Form the list of comparisons we're going to perform.
+ struct Comparison {
+ ExprResult Cmp;
+ ComparisonCategoryResult Result;
+ } Comparisons[4] =
+ { {Eq, Info->isStrong() ? ComparisonCategoryResult::Equal
+ : ComparisonCategoryResult::Equivalent},
+ {Less, ComparisonCategoryResult::Less},
+ {Greater, ComparisonCategoryResult::Greater},
+ {ExprResult(), ComparisonCategoryResult::Unordered},
+ };
+
+ int I;
+ if (Info->isEquality()) {
+ Comparisons[1].Result = Info->isStrong()
+ ? ComparisonCategoryResult::Nonequal
+ : ComparisonCategoryResult::Nonequivalent;
+ I = 1;
+ } else if (!Info->isPartial()) {
+ I = 2;
+ } else {
+ I = 3;
+ }
+
+ // Combine the comparisons with suitable conditional expressions.
+ ExprResult Result;
+ for (; I >= 0; --I) {
+ // Build a reference to the comparison category constant.
+ auto *VI = Info->lookupValueInfo(Comparisons[I].Result);
+ // FIXME: Missing a constant for a comparison category. Diagnose this?
+ if (!VI)
+ return ExprResult((Expr*)nullptr);
+ ExprResult ThisResult =
+ BuildDeclarationNameExpr(CXXScopeSpec(), DeclarationNameInfo(), VI->VD);
+ if (ThisResult.isInvalid())
+ return ExprError();
+
+ // Build a conditional unless this is the final case.
+ if (Result.get()) {
+ Result = ActOnConditionalOp(OpLoc, OpLoc, Comparisons[I].Cmp.get(),
+ ThisResult.get(), Result.get());
+ if (Result.isInvalid())
+ return ExprError();
+ } else {
+ Result = ThisResult;
+ }
+ }
+
+ // Build a PseudoObjectExpr to model the rewriting of an <=> operator, and to
+ // bind the OpaqueValueExprs before they're (repeatedly) used.
+ Expr *SyntacticForm = new (Context)
+ BinaryOperator(OrigLHS, OrigRHS, BO_Cmp, Result.get()->getType(),
+ Result.get()->getValueKind(),
+ Result.get()->getObjectKind(), OpLoc, FPFeatures);
+ Expr *SemanticForm[] = {LHS, RHS, Result.get()};
+ return PseudoObjectExpr::Create(Context, SyntacticForm, SemanticForm, 2);
+}
+
ExprResult
Sema::CreateOverloadedArraySubscriptExpr(SourceLocation LLoc,
SourceLocation RLoc,
diff --git a/clang/lib/Sema/SemaTemplateInstantiate.cpp b/clang/lib/Sema/SemaTemplateInstantiate.cpp
index 2496c9193116..4d54ec17b99e 100644
--- a/clang/lib/Sema/SemaTemplateInstantiate.cpp
+++ b/clang/lib/Sema/SemaTemplateInstantiate.cpp
@@ -672,13 +672,23 @@ void Sema::PrintInstantiationStack() {
break;
case CodeSynthesisContext::DefiningSynthesizedFunction: {
- // FIXME: For synthesized members other than special members, produce a note.
- auto *MD = dyn_cast<CXXMethodDecl>(Active->Entity);
- auto CSM = MD ? getSpecialMember(MD) : CXXInvalid;
- if (CSM != CXXInvalid) {
+ // FIXME: For synthesized functions that are not defaulted,
+ // produce a note.
+ auto *FD = dyn_cast<FunctionDecl>(Active->Entity);
+ DefaultedFunctionKind DFK =
+ FD ? getDefaultedFunctionKind(FD) : DefaultedFunctionKind();
+ if (DFK.isSpecialMember()) {
+ auto *MD = cast<CXXMethodDecl>(FD);
Diags.Report(Active->PointOfInstantiation,
diag::note_member_synthesized_at)
- << CSM << Context.getTagDeclType(MD->getParent());
+ << MD->isExplicitlyDefaulted() << DFK.asSpecialMember()
+ << Context.getTagDeclType(MD->getParent());
+ } else if (DFK.isComparison()) {
+ Diags.Report(Active->PointOfInstantiation,
+ diag::note_comparison_synthesized_at)
+ << (int)DFK.asComparison()
+ << Context.getTagDeclType(
+ cast<CXXRecordDecl>(FD->getLexicalDeclContext()));
}
break;
}
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 bbc9060df305..cdffd445f7ce 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
@@ -14,7 +14,7 @@ struct A2 {
bool operator==(const A2&) const;
bool operator!=(const A2&) const = default;
- bool operator<=>(const A2&) const;
+ int operator<=>(const A2&) const;
bool operator<(const A2&) const = default;
bool operator<=(const A2&) const = default;
bool operator>(const A2&) const = default;
diff --git a/clang/test/CXX/class/class.compare/class.compare.default/p5.cpp b/clang/test/CXX/class/class.compare/class.compare.default/p5.cpp
new file mode 100644
index 000000000000..f863ed09bc63
--- /dev/null
+++ b/clang/test/CXX/class/class.compare/class.compare.default/p5.cpp
@@ -0,0 +1,45 @@
+// RUN: %clang_cc1 -std=c++2a -verify %s
+
+// expected-no-diagnostics
+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};
+}
+
+// Check that we compare subobjects in the right order.
+struct Log {
+ char buff[8] = {};
+ int n = 0;
+ constexpr void add(char c) { buff[n++] = c; }
+ constexpr bool operator==(const char *p) const { return __builtin_strcmp(p, buff) == 0; }
+};
+
+template<char C> struct B {
+ Log *log;
+ constexpr bool operator==(const B&) const { log->add(C); return true; }
+ constexpr std::strong_ordering operator<=>(const B&) const { log->add(C); return {0}; }
+};
+
+struct C : B<'a'>, B<'b'> {
+ B<'c'> c;
+ B<'d'> d;
+ // FIXME: Test arrays once we handle them properly.
+
+ constexpr C(Log *p) : B<'a'>{p}, B<'b'>{p}, c{p}, d{p} {}
+
+ bool operator==(const C&) const = default;
+ std::strong_ordering operator<=>(const C&) const = default;
+};
+
+constexpr bool check(bool which) {
+ Log log;
+ C c(&log);
+ (void)(which ? c == c : c <=> c);
+ return log == "abcd";
+}
+static_assert(check(false));
+static_assert(check(true));
diff --git a/clang/test/CXX/class/class.compare/class.eq/p2.cpp b/clang/test/CXX/class/class.compare/class.eq/p2.cpp
index 1a515dee4900..d53d071f36fe 100644
--- a/clang/test/CXX/class/class.compare/class.eq/p2.cpp
+++ b/clang/test/CXX/class/class.compare/class.eq/p2.cpp
@@ -8,13 +8,16 @@ struct D {
// 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 E {
+ E(const E &) = delete; // expected-note {{deleted}}
+ int operator==(E) const; // expected-note {{passing}}
+};
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}}
+ bool operator==(const X&) const = default; // #x 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'}}
};
@@ -31,10 +34,13 @@ void test() {
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>());
+ // FIXME: We would benefit from a note identifying the member of 'X' we were comparing here and below.
+ // expected-error@#x {{call to deleted constructor of 'E'}}
+ void(X<E>() == X<E>()); // expected-note {{in defaulted equality comparison operator for 'X<E>' first required here}}
+
+ // FIXME: We would benefit from a note pointing at the selected 'operator==' here.
+ // expected-error@#x {{value of type 'void' is not contextually convertible to 'bool'}}
+ void(X<F>() == X<F>()); // expected-note {{in defaulted equality comparison operator for 'X<F>' first required here}}
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.eq/p3.cpp b/clang/test/CXX/class/class.compare/class.eq/p3.cpp
new file mode 100644
index 000000000000..f58b9daa5235
--- /dev/null
+++ b/clang/test/CXX/class/class.compare/class.eq/p3.cpp
@@ -0,0 +1,11 @@
+// RUN: %clang_cc1 -std=c++2a -verify %s
+
+struct A {
+ int a, b, c;
+ bool operator==(const A&) const = default;
+};
+
+static_assert(A{1, 2, 3} == A{1, 2, 3});
+static_assert(A{1, 2, 3} == A{0, 2, 3}); // expected-error {{failed}}
+static_assert(A{1, 2, 3} == A{1, 0, 3}); // expected-error {{failed}}
+static_assert(A{1, 2, 3} == A{1, 2, 0}); // expected-error {{failed}}
diff --git a/clang/test/CXX/class/class.compare/class.rel/p2.cpp b/clang/test/CXX/class/class.compare/class.rel/p2.cpp
index d81c9634131f..2abe7fb8d79c 100644
--- a/clang/test/CXX/class/class.compare/class.rel/p2.cpp
+++ b/clang/test/CXX/class/class.compare/class.rel/p2.cpp
@@ -2,16 +2,23 @@
namespace Rel {
struct A {
- int operator<=>(A) const;
+ int n;
+ constexpr int operator<=>(A a) const { return n - a.n; }
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();
+ static_assert(A{0} < A{1});
+ static_assert(A{1} < A{1}); // expected-error {{failed}}
+ static_assert(A{0} <= A{1});
+ static_assert(A{1} <= A{1});
+ static_assert(A{2} <= A{1}); // expected-error {{failed}}
+ static_assert(A{1} > A{0});
+ static_assert(A{1} > A{1}); // expected-error {{failed}}
+ static_assert(A{1} >= A{0});
+ static_assert(A{1} >= A{1});
+ static_assert(A{1} >= A{2}); // expected-error {{failed}}
struct B {
bool operator<=>(B) const = delete; // expected-note 4{{deleted here}} expected-note-re 8{{candidate {{.*}} deleted}}
@@ -37,10 +44,12 @@ namespace Rel {
// Under P2002R0, operator!= follows these rules too.
namespace NotEqual {
struct A {
- bool operator==(A) const;
+ int n;
+ constexpr bool operator==(A a) const { return n == a.n; }
friend bool operator!=(const A&, const A&) = default;
};
- bool a = A() != A();
+ static_assert(A{1} != A{2});
+ static_assert(A{1} != A{1}); // expected-error {{failed}}
struct B {
bool operator==(B) const = delete; // expected-note {{deleted here}} expected-note-re 2{{candidate {{.*}} deleted}}
diff --git a/clang/test/CXX/class/class.compare/class.spaceship/p1.cpp b/clang/test/CXX/class/class.compare/class.spaceship/p1.cpp
index 928fe2036156..fafa99ff5cfe 100644
--- a/clang/test/CXX/class/class.compare/class.spaceship/p1.cpp
+++ b/clang/test/CXX/class/class.compare/class.spaceship/p1.cpp
@@ -1,13 +1,39 @@
-// RUN: %clang_cc1 -std=c++2a -verify %s
+// RUN: %clang_cc1 -std=c++2a -verify %s -fcxx-exceptions
namespace std {
- struct strong_ordering {
+ struct strong_ordering { // expected-note 3{{candidate}}
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};
+
+ struct weak_ordering {
+ int n;
+ constexpr weak_ordering(int n) : n(n) {}
+ constexpr weak_ordering(strong_ordering o) : n(o.n) {}
+ constexpr operator int() const { return n; }
+ static const weak_ordering less, equivalent, greater;
+ };
+ constexpr weak_ordering weak_ordering::less{-1},
+ weak_ordering::equivalent{0}, weak_ordering::greater{1};
+
+ struct partial_ordering {
+ double d;
+ constexpr partial_ordering(double d) : d(d) {}
+ constexpr partial_ordering(strong_ordering o) : d(o.n) {}
+ constexpr partial_ordering(weak_ordering o) : d(o.n) {}
+ constexpr operator double() const { return d; }
+ static const partial_ordering less, equivalent, greater, unordered;
+ };
+ constexpr partial_ordering partial_ordering::less{-1},
+ partial_ordering::equivalent{0}, partial_ordering::greater{1},
+ partial_ordering::unordered{__builtin_nan("")};
+
+ static_assert(!(partial_ordering::unordered < 0));
+ static_assert(!(partial_ordering::unordered == 0));
+ static_assert(!(partial_ordering::unordered > 0));
}
namespace Deletedness {
@@ -59,7 +85,7 @@ namespace Deletedness {
// 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}}
+ std::strong_ordering operator<=>(const Cmp&) const = default; // #cmp expected-note 5{{here}}
};
void use(...);
@@ -72,10 +98,80 @@ namespace Deletedness {
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
+ // FIXME: The following three errors are not very good.
+ // expected-error@#cmp {{value of type 'void' is not contextually convertible to 'bool'}}
+ Cmp<G1>() <=> Cmp<G1>(), // expected-note-re {{in defaulted three-way comparison operator for '{{.*}}Cmp<{{.*}}G1>' first required here}}j
+ // expected-error@#cmp {{value of type 'void' is not contextually convertible to 'bool'}}
+ Cmp<G2>() <=> Cmp<G2>(), // expected-note-re {{in defaulted three-way comparison operator for '{{.*}}Cmp<{{.*}}G2>' first required here}}j
+ // expected-error@#cmp {{no matching conversion for static_cast from 'void' to 'std::strong_ordering'}}
+ Cmp<H>() <=> Cmp<H>(), // expected-note-re {{in defaulted three-way comparison operator for '{{.*}}Cmp<{{.*}}H>' first required here}}j
0
);
}
}
+
+namespace Synthesis {
+ enum Result { False, True, Mu };
+
+ constexpr bool toBool(Result R) {
+ if (R == Mu) throw "should not ask this question";
+ return R == True;
+ }
+
+ struct Val {
+ Result equal, less;
+ constexpr bool operator==(const Val&) const { return toBool(equal); }
+ constexpr bool operator<(const Val&) const { return toBool(less); }
+ };
+
+ template<typename T> struct Cmp {
+ Val val;
+ friend T operator<=>(const Cmp&, const Cmp&) = default; // expected-note {{deleted}}
+ };
+
+ template<typename T> constexpr auto cmp(Result equal, Result less = Mu, Result reverse_less = Mu) {
+ return Cmp<T>{equal, less} <=> Cmp<T>{Mu, reverse_less};
+ }
+
+ static_assert(cmp<std::strong_ordering>(True) == 0);
+ static_assert(cmp<std::strong_ordering>(False, True) < 0);
+ static_assert(cmp<std::strong_ordering>(False, False) > 0);
+
+ static_assert(cmp<std::weak_ordering>(True) == 0);
+ static_assert(cmp<std::weak_ordering>(False, True) < 0);
+ static_assert(cmp<std::weak_ordering>(False, False) > 0);
+
+ static_assert(cmp<std::partial_ordering>(True) == 0);
+ static_assert(cmp<std::partial_ordering>(False, True) < 0);
+ static_assert(cmp<std::partial_ordering>(False, False, True) > 0);
+ static_assert(!(cmp<std::partial_ordering>(False, False, False) > 0));
+ static_assert(!(cmp<std::partial_ordering>(False, False, False) == 0));
+ static_assert(!(cmp<std::partial_ordering>(False, False, False) < 0));
+
+ // No synthesis is performed for a custom return type, even if it can be
+ // converted from a standard ordering.
+ struct custom_ordering {
+ custom_ordering(std::strong_ordering o);
+ };
+ void f(Cmp<custom_ordering> c) {
+ c <=> c; // expected-error {{deleted}}
+ }
+}
+
+namespace Preference {
+ struct A {
+ A(const A&) = delete; // expected-note {{deleted}}
+ // "usable" candidate that can't actually be called
+ friend void operator<=>(A, A); // expected-note {{passing}}
+ // Callable candidates for synthesis not considered.
+ friend bool operator==(A, A);
+ friend bool operator<(A, A);
+ };
+
+ struct B {
+ B();
+ A a;
+ std::strong_ordering operator<=>(const B&) const = default; // expected-error {{call to deleted constructor of 'Preference::A'}}
+ };
+ bool x = B() < B(); // expected-note {{in defaulted three-way comparison operator for 'Preference::B' first required here}}
+}
diff --git a/clang/test/CXX/class/class.compare/class.spaceship/p3.cpp b/clang/test/CXX/class/class.compare/class.spaceship/p3.cpp
new file mode 100644
index 000000000000..e4be892cd7c5
--- /dev/null
+++ b/clang/test/CXX/class/class.compare/class.spaceship/p3.cpp
@@ -0,0 +1,35 @@
+// 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};
+}
+
+struct A {
+ int a, b, c;
+ std::strong_ordering operator<=>(const A&) const = default;
+};
+
+static_assert(A{1, 2, 3} <= A{1, 2, 3});
+static_assert(A{1, 2, 3} <= A{0, 20, 3}); // expected-error {{failed}}
+static_assert(A{1, 2, 3} <= A{1, 0, 30}); // expected-error {{failed}}
+static_assert(A{1, 2, 3} <= A{1, 2, 0}); // expected-error {{failed}}
+
+struct reverse_compare {
+ int n;
+ constexpr explicit reverse_compare(std::strong_ordering o) : n(-o.n) {}
+ constexpr operator int() const { return n; }
+};
+
+struct B {
+ int a, b, c;
+ friend reverse_compare operator<=>(const B&, const B&) = default;
+};
+static_assert(B{1, 2, 3} >= B{1, 2, 3});
+static_assert(B{1, 2, 3} >= B{0, 20, 3}); // expected-error {{failed}}
+static_assert(B{1, 2, 3} >= B{1, 0, 30}); // expected-error {{failed}}
+static_assert(B{1, 2, 3} >= B{1, 2, 0}); // expected-error {{failed}}
More information about the cfe-commits
mailing list