diff --git a/clang/docs/ReleaseNotes.rst b/clang/docs/ReleaseNotes.rst --- a/clang/docs/ReleaseNotes.rst +++ b/clang/docs/ReleaseNotes.rst @@ -446,6 +446,10 @@ that can be used for such compatibility. The demangler now demangles symbols with named module attachment. +- As per "Conditionally Trivial Special Member Functions" (P0848), it is + now possible to overload destructors using concepts. Note that the rest + of the paper about other special member functions is not yet implemented. + C++2b Feature Support ^^^^^^^^^^^^^^^^^^^^^ diff --git a/clang/include/clang/AST/Decl.h b/clang/include/clang/AST/Decl.h --- a/clang/include/clang/AST/Decl.h +++ b/clang/include/clang/AST/Decl.h @@ -2251,6 +2251,13 @@ DeclAsWritten->getCanonicalDecl()->isDefaulted()); } + bool isIneligibleOrNotSelected() const { + return FunctionDeclBits.IsIneligibleOrNotSelected; + } + void setIneligibleOrNotSelected(bool II) { + FunctionDeclBits.IsIneligibleOrNotSelected = II; + } + /// 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/DeclBase.h b/clang/include/clang/AST/DeclBase.h --- a/clang/include/clang/AST/DeclBase.h +++ b/clang/include/clang/AST/DeclBase.h @@ -1596,6 +1596,12 @@ uint64_t IsDefaulted : 1; uint64_t IsExplicitlyDefaulted : 1; uint64_t HasDefaultedFunctionInfo : 1; + + /// For member functions of complete types, whether this is an ineligible + /// special member function or an unselected destructor. See + /// [class.mem.special]. + uint64_t IsIneligibleOrNotSelected : 1; + uint64_t HasImplicitReturnZero : 1; uint64_t IsLateTemplateParsed : 1; @@ -1631,7 +1637,7 @@ }; /// Number of non-inherited bits in FunctionDeclBitfields. - enum { NumFunctionDeclBits = 27 }; + enum { NumFunctionDeclBits = 28 }; /// Stores the bits used by CXXConstructorDecl. If modified /// NumCXXConstructorDeclBits and the accessor @@ -1643,12 +1649,12 @@ /// For the bits in FunctionDeclBitfields. uint64_t : NumFunctionDeclBits; - /// 24 bits to fit in the remaining available space. + /// 23 bits to fit in the remaining available space. /// Note that this makes CXXConstructorDeclBitfields take /// exactly 64 bits and thus the width of NumCtorInitializers /// will need to be shrunk if some bit is added to NumDeclContextBitfields, /// NumFunctionDeclBitfields or CXXConstructorDeclBitfields. - uint64_t NumCtorInitializers : 21; + uint64_t NumCtorInitializers : 20; uint64_t IsInheritingConstructor : 1; /// Whether this constructor has a trail-allocated explicit specifier. diff --git a/clang/include/clang/AST/DeclCXX.h b/clang/include/clang/AST/DeclCXX.h --- a/clang/include/clang/AST/DeclCXX.h +++ b/clang/include/clang/AST/DeclCXX.h @@ -1422,6 +1422,19 @@ return isLiteral() && data().StructuralIfLiteral; } + /// Notify the class that this destructor is now selected. + /// + /// Important properties of the class depend on destructor properties. Since + /// C++20, it is possible to have multiple destructor declarations in a class + /// out of which one will be selected at the end. + /// This is called separately from addedMember because it has to be deferred + /// to the completion of the class. + void addedSelectedDestructor(CXXDestructorDecl *DD); + + /// Notify the class that an eligible SMF has been added. + /// This updates triviality and destructor based properties of the class accordingly. + void addedEligibleSpecialMemberFunction(const CXXMethodDecl *MD, unsigned SMKind); + /// If this record is an instantiation of a member class, /// retrieves the member class from which it was instantiated. /// diff --git a/clang/include/clang/Basic/DiagnosticSemaKinds.td b/clang/include/clang/Basic/DiagnosticSemaKinds.td --- a/clang/include/clang/Basic/DiagnosticSemaKinds.td +++ b/clang/include/clang/Basic/DiagnosticSemaKinds.td @@ -4716,6 +4716,10 @@ "reference to non-static member function must be called" "%select{|; did you mean to call it with no arguments?}0">; def note_possible_target_of_call : Note<"possible target for call">; +def err_no_viable_destructor : Error< + "no viable destructor found for class %0">; +def err_ambiguous_destructor : Error< + "destructor of class %0 is ambiguous">; def err_ovl_no_viable_object_call : Error< "no matching function for call to object of type %0">; diff --git a/clang/lib/AST/Decl.cpp b/clang/lib/AST/Decl.cpp --- a/clang/lib/AST/Decl.cpp +++ b/clang/lib/AST/Decl.cpp @@ -2955,6 +2955,7 @@ FunctionDeclBits.IsDefaulted = false; FunctionDeclBits.IsExplicitlyDefaulted = false; FunctionDeclBits.HasDefaultedFunctionInfo = false; + FunctionDeclBits.IsIneligibleOrNotSelected = false; FunctionDeclBits.HasImplicitReturnZero = false; FunctionDeclBits.IsLateTemplateParsed = false; FunctionDeclBits.ConstexprKind = static_cast(ConstexprKind); diff --git a/clang/lib/AST/DeclCXX.cpp b/clang/lib/AST/DeclCXX.cpp --- a/clang/lib/AST/DeclCXX.cpp +++ b/clang/lib/AST/DeclCXX.cpp @@ -825,29 +825,11 @@ data().HasInheritedDefaultConstructor = true; } - // Handle destructors. - if (const auto *DD = dyn_cast(D)) { - SMKind |= SMF_Destructor; - - if (DD->isUserProvided()) - data().HasIrrelevantDestructor = false; - // If the destructor is explicitly defaulted and not trivial or not public - // or if the destructor is deleted, we clear HasIrrelevantDestructor in - // finishedDefaultedOrDeletedMember. - - // C++11 [class.dtor]p5: - // A destructor is trivial if [...] the destructor is not virtual. - if (DD->isVirtual()) { - data().HasTrivialSpecialMembers &= ~SMF_Destructor; - data().HasTrivialSpecialMembersForCall &= ~SMF_Destructor; - } - - if (DD->isNoReturn()) - data().IsAnyDestructorNoReturn = true; - } - // Handle member functions. if (const auto *Method = dyn_cast(D)) { + if (const auto *DD = dyn_cast(D)) + SMKind |= SMF_Destructor; + if (Method->isCopyAssignmentOperator()) { SMKind |= SMF_CopyAssignment; @@ -893,31 +875,9 @@ data().HasTrivialSpecialMembersForCall &= data().DeclaredSpecialMembers | ~SMKind; - if (!Method->isImplicit() && !Method->isUserProvided()) { - // This method is user-declared but not user-provided. We can't work out - // whether it's trivial yet (not until we get to the end of the class). - // We'll handle this method in finishedDefaultedOrDeletedMember. - } else if (Method->isTrivial()) { - data().HasTrivialSpecialMembers |= SMKind; - data().HasTrivialSpecialMembersForCall |= SMKind; - } else if (Method->isTrivialForCall()) { - data().HasTrivialSpecialMembersForCall |= SMKind; - data().DeclaredNonTrivialSpecialMembers |= SMKind; - } else { - data().DeclaredNonTrivialSpecialMembers |= SMKind; - // If this is a user-provided function, do not set - // DeclaredNonTrivialSpecialMembersForCall here since we don't know - // yet whether the method would be considered non-trivial for the - // purpose of calls (attribute "trivial_abi" can be dropped from the - // class later, which can change the special method's triviality). - if (!Method->isUserProvided()) - data().DeclaredNonTrivialSpecialMembersForCall |= SMKind; - } - // Note when we have declared a declared special member, and suppress the // implicit declaration of this special member. data().DeclaredSpecialMembers |= SMKind; - if (!Method->isImplicit()) { data().UserDeclaredSpecialMembers |= SMKind; @@ -934,6 +894,12 @@ // This is an extension in C++03. data().PlainOldData = false; } + // We delay updating destructor relevant properties until + // addedSelectedDestructor. + // FIXME: Defer this for the other special member functions as well. + if (!Method->isIneligibleOrNotSelected()) { + addedEligibleSpecialMemberFunction(Method, SMKind); + } } return; @@ -1393,6 +1359,54 @@ } } +void CXXRecordDecl::addedSelectedDestructor(CXXDestructorDecl *DD) { + DD->setIneligibleOrNotSelected(false); + addedEligibleSpecialMemberFunction(DD, SMF_Destructor); +} + +void CXXRecordDecl::addedEligibleSpecialMemberFunction(const CXXMethodDecl *MD, + unsigned SMKind) { + if (const auto *DD = dyn_cast(MD)) { + if (DD->isUserProvided()) + data().HasIrrelevantDestructor = false; + // If the destructor is explicitly defaulted and not trivial or not public + // or if the destructor is deleted, we clear HasIrrelevantDestructor in + // finishedDefaultedOrDeletedMember. + + // C++11 [class.dtor]p5: + // A destructor is trivial if [...] the destructor is not virtual. + if (DD->isVirtual()) { + data().HasTrivialSpecialMembers &= ~SMF_Destructor; + data().HasTrivialSpecialMembersForCall &= ~SMF_Destructor; + } + + if (DD->isNoReturn()) + data().IsAnyDestructorNoReturn = true; + } + + if (!MD->isImplicit() && !MD->isUserProvided()) { + // This method is user-declared but not user-provided. We can't work + // out whether it's trivial yet (not until we get to the end of the + // class). We'll handle this method in + // finishedDefaultedOrDeletedMember. + } else if (MD->isTrivial()) { + data().HasTrivialSpecialMembers |= SMKind; + data().HasTrivialSpecialMembersForCall |= SMKind; + } else if (MD->isTrivialForCall()) { + data().HasTrivialSpecialMembersForCall |= SMKind; + data().DeclaredNonTrivialSpecialMembers |= SMKind; + } else { + data().DeclaredNonTrivialSpecialMembers |= SMKind; + // If this is a user-provided function, do not set + // DeclaredNonTrivialSpecialMembersForCall here since we don't know + // yet whether the method would be considered non-trivial for the + // purpose of calls (attribute "trivial_abi" can be dropped from the + // class later, which can change the special method's triviality). + if (!MD->isUserProvided()) + data().DeclaredNonTrivialSpecialMembersForCall |= SMKind; + } +} + void CXXRecordDecl::finishedDefaultedOrDeletedMember(CXXMethodDecl *D) { assert(!D->isImplicit() && !D->isUserProvided()); @@ -1895,7 +1909,14 @@ DeclContext::lookup_result R = lookup(Name); - return R.empty() ? nullptr : dyn_cast(R.front()); + // If a destructor was marked as not selected, we skip it. We don't always + // have a selected destructor: dependent types, unnamed structs. + for (auto *Decl : R) { + auto* DD = dyn_cast(Decl); + if (DD && !DD->isIneligibleOrNotSelected()) + return DD; + } + return nullptr; } static bool isDeclContextInNamespace(const DeclContext *DC) { diff --git a/clang/lib/AST/TextNodeDumper.cpp b/clang/lib/AST/TextNodeDumper.cpp --- a/clang/lib/AST/TextNodeDumper.cpp +++ b/clang/lib/AST/TextNodeDumper.cpp @@ -1681,6 +1681,9 @@ if (D->isTrivial()) OS << " trivial"; + if (D->isIneligibleOrNotSelected()) + OS << (isa(D) ? " not_selected" : " ineligible"); + if (const auto *FPT = D->getType()->getAs()) { FunctionProtoType::ExtProtoInfo EPI = FPT->getExtProtoInfo(); switch (EPI.ExceptionSpec.Type) { diff --git a/clang/lib/Sema/SemaDecl.cpp b/clang/lib/Sema/SemaDecl.cpp --- a/clang/lib/Sema/SemaDecl.cpp +++ b/clang/lib/Sema/SemaDecl.cpp @@ -8852,6 +8852,10 @@ SemaRef.getCurFPFeatures().isFPConstrained(), isInline, /*isImplicitlyDeclared=*/false, ConstexprKind, TrailingRequiresClause); + // User defined destructors start as not selected if the class definition is still + // not done. + if (Record->isBeingDefined()) + NewDD->setIneligibleOrNotSelected(true); // If the destructor needs an implicit exception specification, set it // now. FIXME: It'd be nice to be able to create the right type to start @@ -17711,6 +17715,75 @@ AllIvarDecls.push_back(Ivar); } +namespace { +/// [class.dtor]p4: +/// At the end of the definition of a class, overload resolution is +/// performed among the prospective destructors declared in that class with +/// an empty argument list to select the destructor for the class, also +/// known as the selected destructor. +/// +/// We do the overload resolution here, then mark the selected constructor in the AST. +/// Later CXXRecordDecl::getDestructor() will return the selected constructor. +void ComputeSelectedDestructor(Sema &S, CXXRecordDecl *Record) { + if (!Record->hasUserDeclaredDestructor()) { + return; + } + + SourceLocation Loc = Record->getLocation(); + OverloadCandidateSet OCS(Loc, OverloadCandidateSet::CSK_Normal); + + for (auto *Decl : Record->decls()) { + if (auto *DD = dyn_cast(Decl)) { + if (DD->isInvalidDecl()) + continue; + S.AddOverloadCandidate(DD, DeclAccessPair::make(DD, DD->getAccess()), {}, + OCS); + assert(DD->isIneligibleOrNotSelected() && "Selecting a destructor but a destructor was already selected."); + } + } + + if (OCS.empty()) { + return; + } + OverloadCandidateSet::iterator Best; + unsigned Msg = 0; + OverloadCandidateDisplayKind DisplayKind; + + switch (OCS.BestViableFunction(S, Loc, Best)) { + case OR_Success: + case OR_Deleted: + Record->addedSelectedDestructor(dyn_cast(Best->Function)); + break; + + case OR_Ambiguous: + Msg = diag::err_ambiguous_destructor; + DisplayKind = OCD_AmbiguousCandidates; + break; + + case OR_No_Viable_Function: + Msg = diag::err_no_viable_destructor; + DisplayKind = OCD_AllCandidates; + break; + } + + if (Msg) { + // OpenCL have got their own thing going with destructors. It's slightly broken, + // but we allow it. + if (!S.LangOpts.OpenCL) { + PartialDiagnostic Diag = S.PDiag(Msg) << Record; + OCS.NoteCandidates(PartialDiagnosticAt(Loc, Diag), S, DisplayKind, {}); + Record->setInvalidDecl(); + } + // It's a bit hacky: At this point we've raised an error but we want the + // rest of the compiler to continue somehow working. However almost + // everything we'll try to do with the class will depend on there being a + // destructor. So let's pretend the first one is selected and hope for the + // best. + Record->addedSelectedDestructor(dyn_cast(OCS.begin()->Function)); + } +} +} // namespace + void Sema::ActOnFields(Scope *S, SourceLocation RecLoc, Decl *EnclosingDecl, ArrayRef Fields, SourceLocation LBrac, SourceLocation RBrac, @@ -17737,6 +17810,9 @@ RecordDecl *Record = dyn_cast(EnclosingDecl); CXXRecordDecl *CXXRecord = dyn_cast(EnclosingDecl); + if (CXXRecord && !CXXRecord->isDependentType()) + ComputeSelectedDestructor(*this, CXXRecord); + // Start counting up the number of named members; make sure to include // members of anonymous structs and unions in the total. unsigned NumNamedMembers = 0; diff --git a/clang/lib/Sema/SemaDeclCXX.cpp b/clang/lib/Sema/SemaDeclCXX.cpp --- a/clang/lib/Sema/SemaDeclCXX.cpp +++ b/clang/lib/Sema/SemaDeclCXX.cpp @@ -6693,7 +6693,7 @@ return false; for (const CXXMethodDecl *MD : D->methods()) { - if (MD->isDeleted()) + if (MD->isDeleted() || MD->isIneligibleOrNotSelected()) continue; auto *CD = dyn_cast(MD); diff --git a/clang/lib/Sema/SemaTemplateInstantiate.cpp b/clang/lib/Sema/SemaTemplateInstantiate.cpp --- a/clang/lib/Sema/SemaTemplateInstantiate.cpp +++ b/clang/lib/Sema/SemaTemplateInstantiate.cpp @@ -3250,6 +3250,9 @@ if (FunctionDecl *Pattern = Function->getInstantiatedFromMemberFunction()) { + if (Function->isIneligibleOrNotSelected()) + continue; + if (Function->getTrailingRequiresClause()) { ConstraintSatisfaction Satisfaction; if (CheckFunctionConstraints(Function, Satisfaction) || diff --git a/clang/lib/Sema/SemaTemplateInstantiateDecl.cpp b/clang/lib/Sema/SemaTemplateInstantiateDecl.cpp --- a/clang/lib/Sema/SemaTemplateInstantiateDecl.cpp +++ b/clang/lib/Sema/SemaTemplateInstantiateDecl.cpp @@ -2478,6 +2478,7 @@ SemaRef.Context, Record, StartLoc, NameInfo, T, TInfo, Destructor->UsesFPIntrin(), Destructor->isInlineSpecified(), false, Destructor->getConstexprKind(), TrailingRequiresClause); + Method->setIneligibleOrNotSelected(true); Method->setRangeEnd(Destructor->getEndLoc()); Method->setDeclName(SemaRef.Context.DeclarationNames.getCXXDestructorName( SemaRef.Context.getCanonicalType( diff --git a/clang/test/AST/ast-dump-decl.cpp b/clang/test/AST/ast-dump-decl.cpp --- a/clang/test/AST/ast-dump-decl.cpp +++ b/clang/test/AST/ast-dump-decl.cpp @@ -303,7 +303,7 @@ // CHECK-NEXT: | | |-MoveConstructor // CHECK-NEXT: | | |-CopyAssignment simple trivial has_const_param needs_implicit implicit_has_const_param // CHECK-NEXT: | | |-MoveAssignment -// CHECK-NEXT: | | `-Destructor non_trivial user_declared +// CHECK-NEXT: | | `-Destructor // CHECK-NEXT: | |-CXXRecordDecl 0x{{.+}} col:30 implicit referenced class TestClassTemplate // CHECK-NEXT: | |-AccessSpecDecl 0x{{.+}} col:3 public // CHECK-NEXT: | |-CXXConstructorDecl 0x{{.+}} col:5 TestClassTemplate 'void ()' diff --git a/clang/test/AST/overloaded-destructors.cpp b/clang/test/AST/overloaded-destructors.cpp new file mode 100644 --- /dev/null +++ b/clang/test/AST/overloaded-destructors.cpp @@ -0,0 +1,118 @@ +// RUN: %clang_cc1 -std=c++20 -triple x86_64-pc-linux -ast-dump=json %s | FileCheck %s +// RUN: %clang_cc1 -std=c++20 -triple x86_64-pc-win32 -ast-dump=json %s | FileCheck %s -check-prefixes=CHECK,WIN32 + +// This test validates that we compute correct AST properties of classes after choosing +// their destructor when doing destructor overload resolution with concepts. + +template +struct A { + ~A() requires(N == 1) = default; + ~A() requires(N == 2) = delete; + ~A() requires(N == 3); + constexpr ~A() requires(N == 4); + +private: + ~A() requires(N == 5) = default; +}; + + +template struct A<1>; +// CHECK: "kind": "ClassTemplateSpecializationDecl", +// CHECK: "definitionData": { +// CHECK-NEXT: "canConstDefaultInit": true, +// CHECK-NEXT: "canPassInRegisters": true, +// CHECK-NEXT: "copyAssign": { + +// CHECK: "dtor": { +// CHECK-NEXT: "irrelevant": true, +// CHECK-NEXT: "trivial": true, +// CHECK-NEXT: "userDeclared": true +// CHECK-NEXT: }, +// CHECK-NEXT: "hasConstexprNonCopyMoveConstructor": true, +// CHECK-NEXT: "isAggregate": true, +// CHECK-NEXT: "isEmpty": true, +// CHECK-NEXT: "isLiteral": true, +// CHECK-NEXT: "isStandardLayout": true, +// CHECK-NEXT: "isTrivial": true, +// CHECK-NEXT: "isTriviallyCopyable": true, +// CHECK-NEXT: "moveAssign": {}, +// CHECK-NEXT: "moveCtor": {} + +template struct A<2>; +// CHECK: "kind": "ClassTemplateSpecializationDecl", +// CHECK: "definitionData": { +// CHECK-NEXT: "canConstDefaultInit": true, +// CHECK-NEXT: "canPassInRegisters": true, +// CHECK-NEXT: "copyAssign": { + +// CHECK: "dtor": { +// CHECK-NEXT: "trivial": true, +// CHECK-NEXT: "userDeclared": true +// CHECK-NEXT: }, +// CHECK-NEXT: "hasConstexprNonCopyMoveConstructor": true, +// CHECK-NEXT: "isAggregate": true, +// CHECK-NEXT: "isEmpty": true, +// CHECK-NEXT: "isStandardLayout": true, +// CHECK-NEXT: "isTrivial": true, +// CHECK-NEXT: "isTriviallyCopyable": true, +// CHECK-NEXT: "moveAssign": {}, +// CHECK-NEXT: "moveCtor": {} + +template struct A<3>; +// CHECK: "kind": "ClassTemplateSpecializationDecl", +// CHECK: "definitionData": { +// CHECK-NEXT: "canConstDefaultInit": true, +// WIN32-NEXT: "canPassInRegisters": true, +// CHECK-NEXT: "copyAssign": { + +// CHECK: "dtor": { +// CHECK-NEXT: "nonTrivial": true, +// CHECK-NEXT: "userDeclared": true +// CHECK-NEXT: }, +// CHECK-NEXT: "hasConstexprNonCopyMoveConstructor": true, +// CHECK-NEXT: "isAggregate": true, +// CHECK-NEXT: "isEmpty": true, +// CHECK-NEXT: "isStandardLayout": true, +// CHECK-NEXT: "moveAssign": {}, +// CHECK-NEXT: "moveCtor": {} + +template struct A<4>; +// CHECK: "kind": "ClassTemplateSpecializationDecl", +// CHECK: "definitionData": { +// CHECK-NEXT: "canConstDefaultInit": true, +// WIN32-NEXT: "canPassInRegisters": true, +// CHECK-NEXT: "copyAssign": { + +// CHECK: "dtor": { +// CHECK-NEXT: "nonTrivial": true, +// CHECK-NEXT: "userDeclared": true +// CHECK-NEXT: }, +// CHECK-NEXT: "hasConstexprNonCopyMoveConstructor": true, +// CHECK-NEXT: "isAggregate": true, +// CHECK-NEXT: "isEmpty": true, +// CHECK-NEXT: "isLiteral": true, +// CHECK-NEXT: "isStandardLayout": true, +// CHECK-NEXT: "moveAssign": {}, +// CHECK-NEXT: "moveCtor": {} + +template struct A<5>; +// CHECK: "kind": "ClassTemplateSpecializationDecl", +// CHECK: "definitionData": { +// CHECK-NEXT: "canConstDefaultInit": true, +// CHECK-NEXT: "canPassInRegisters": true, +// CHECK-NEXT: "copyAssign": { + +// CHECK: "dtor": { +// CHECK-NEXT: "trivial": true, +// CHECK-NEXT: "userDeclared": true +// CHECK-NEXT: }, +// CHECK-NEXT: "hasConstexprNonCopyMoveConstructor": true, +// CHECK-NEXT: "isAggregate": true, +// CHECK-NEXT: "isEmpty": true, +// CHECK-NEXT: "isLiteral": true, +// CHECK-NEXT: "isStandardLayout": true, +// CHECK-NEXT: "isTrivial": true, +// CHECK-NEXT: "isTriviallyCopyable": true, +// CHECK-NEXT: "moveAssign": {}, +// CHECK-NEXT: "moveCtor": {} + diff --git a/clang/test/CXX/class/class.dtor/p4.cpp b/clang/test/CXX/class/class.dtor/p4.cpp new file mode 100644 --- /dev/null +++ b/clang/test/CXX/class/class.dtor/p4.cpp @@ -0,0 +1,77 @@ +// RUN: %clang_cc1 -std=c++20 -verify %s + +template +struct A { + ~A() = delete; // expected-note {{explicitly marked deleted}} + ~A() requires(N == 1) = delete; // expected-note {{explicitly marked deleted}} +}; + +// FIXME: We should probably make it illegal to mix virtual and non-virtual methods +// this way. See CWG2488 and some discussion in https://gcc.gnu.org/bugzilla/show_bug.cgi?id=105699. +template +struct B { + ~B() requires(N == 1) = delete; // expected-note {{explicitly marked deleted}} + virtual ~B() = delete; // expected-note {{explicitly marked deleted}} +}; + +template +concept CO1 = N == 1; + +template +concept CO2 = N > +0; + +template +struct C { + ~C() = delete; // expected-note {{explicitly marked deleted}} + ~C() requires(CO1) = delete; + ~C() requires(CO1 &&CO2) = delete; // expected-note {{explicitly marked deleted}} +}; + +template +struct D { + ~D() requires(N != 0) = delete; // expected-note {{explicitly marked deleted}} + // expected-note@-1 {{candidate function has been explicitly deleted}} + // expected-note@-2 {{candidate function not viable: constraints not satisfied}} + // expected-note@-3 {{evaluated to false}} + ~D() requires(N == 1) = delete; + // expected-note@-1 {{candidate function has been explicitly deleted}} + // expected-note@-2 {{candidate function not viable: constraints not satisfied}} + // expected-note@-3 {{evaluated to false}} +}; + +template +concept Foo = requires(T t) { + {t.foo()}; +}; + +template +struct E { + void foo(); + ~E(); + ~E() requires Foo = delete; // expected-note {{explicitly marked deleted}} +}; + +template struct A<1>; +template struct A<2>; +template struct B<1>; +template struct B<2>; +template struct C<1>; +template struct C<2>; +template struct D<0>; // expected-error {{no viable destructor found for class 'D<0>'}} expected-note {{in instantiation of template}} +template struct D<1>; // expected-error {{destructor of class 'D<1>' is ambiguous}} expected-note {{in instantiation of template}} +template struct D<2>; +template struct E<1>; + +int main() { + A<1> a1; // expected-error {{attempt to use a deleted function}} + A<2> a2; // expected-error {{attempt to use a deleted function}} + B<1> b1; // expected-error {{attempt to use a deleted function}} + B<2> b2; // expected-error {{attempt to use a deleted function}} + C<1> c1; // expected-error {{attempt to use a deleted function}} + C<2> c2; // expected-error {{attempt to use a deleted function}} + D<0> d0; + D<1> d1; + D<2> d2; // expected-error {{attempt to use a deleted function}} + E<1> e1; // expected-error {{attempt to use a deleted function}} +} diff --git a/clang/test/CXX/over/over.match/over.match.viable/p3.cpp b/clang/test/CXX/over/over.match/over.match.viable/p3.cpp --- a/clang/test/CXX/over/over.match/over.match.viable/p3.cpp +++ b/clang/test/CXX/over/over.match/over.match.viable/p3.cpp @@ -49,7 +49,6 @@ S(A) requires false; S(double) requires true; ~S() requires false; - // expected-note@-1 2{{because 'false' evaluated to false}} ~S() requires true; operator int() requires true; operator int() requires false; @@ -58,11 +57,7 @@ void bar() { WrapsStatics::foo(A{}); S{1.}.foo(A{}); - // expected-error@-1{{invalid reference to function '~S': constraints not satisfied}} - // Note - this behavior w.r.t. constrained dtors is a consequence of current - // wording, which does not invoke overload resolution when a dtor is called. - // P0848 is set to address this issue. + S s = 1; - // expected-error@-1{{invalid reference to function '~S': constraints not satisfied}} int a = s; } diff --git a/clang/test/SemaTemplate/destructor-template.cpp b/clang/test/SemaTemplate/destructor-template.cpp --- a/clang/test/SemaTemplate/destructor-template.cpp +++ b/clang/test/SemaTemplate/destructor-template.cpp @@ -98,7 +98,7 @@ template ~S(); // expected-error{{destructor cannot be declared as a template}} }; -struct T : S { // expected-note{{destructor of 'T' is implicitly deleted because base class 'PR38671::S' has no destructor}} - ~T() = default; // expected-warning{{explicitly defaulted destructor is implicitly deleted}} +struct T : S { + ~T() = default; }; } // namespace PR38671