[Concepts] Implement overload resolution for destructors (P0848)

This patch implements a necessary part of P0848, the overload resolution for destructors.
It is now possible to overload destructors based on constraints, and the eligible destructor
will be selected at the end of the class.

The approach this patch takes is to perform the overload resolution in Sema::ActOnFields
and to mark the selected destructor using a new property in FunctionDeclBitfields.

CXXRecordDecl::getDestructor is then modified to use this property to return the correct
destructor.

This closes https://github.com/llvm/llvm-project/issues/45614.

Reviewed By: #clang-language-wg, erichkeane

Differential Revision: https://reviews.llvm.org/D126194
This commit is contained in:
Roy Jacobson 2022-06-16 20:52:12 +03:00
parent b911cbdcb9
commit 21eb1af469
17 changed files with 386 additions and 57 deletions

View File

@ -446,6 +446,10 @@ C++20 Feature Support
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
^^^^^^^^^^^^^^^^^^^^^

View File

@ -2251,6 +2251,13 @@ public:
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.

View File

@ -1596,6 +1596,12 @@ class DeclContext {
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 @@ class DeclContext {
};
/// 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 @@ class DeclContext {
/// 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.

View File

@ -1422,6 +1422,19 @@ public:
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.
///

View File

@ -4716,6 +4716,10 @@ def err_bound_member_function : Error<
"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">;

View File

@ -2955,6 +2955,7 @@ FunctionDecl::FunctionDecl(Kind DK, ASTContext &C, DeclContext *DC,
FunctionDeclBits.IsDefaulted = false;
FunctionDeclBits.IsExplicitlyDefaulted = false;
FunctionDeclBits.HasDefaultedFunctionInfo = false;
FunctionDeclBits.IsIneligibleOrNotSelected = false;
FunctionDeclBits.HasImplicitReturnZero = false;
FunctionDeclBits.IsLateTemplateParsed = false;
FunctionDeclBits.ConstexprKind = static_cast<uint64_t>(ConstexprKind);

View File

@ -825,29 +825,11 @@ void CXXRecordDecl::addedMember(Decl *D) {
data().HasInheritedDefaultConstructor = true;
}
// Handle destructors.
if (const auto *DD = dyn_cast<CXXDestructorDecl>(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<CXXMethodDecl>(D)) {
if (const auto *DD = dyn_cast<CXXDestructorDecl>(D))
SMKind |= SMF_Destructor;
if (Method->isCopyAssignmentOperator()) {
SMKind |= SMF_CopyAssignment;
@ -893,31 +875,9 @@ void CXXRecordDecl::addedMember(Decl *D) {
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 @@ void CXXRecordDecl::addedMember(Decl *D) {
// 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::addedMember(Decl *D) {
}
}
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<CXXDestructorDecl>(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 @@ CXXDestructorDecl *CXXRecordDecl::getDestructor() const {
DeclContext::lookup_result R = lookup(Name);
return R.empty() ? nullptr : dyn_cast<CXXDestructorDecl>(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<CXXDestructorDecl>(Decl);
if (DD && !DD->isIneligibleOrNotSelected())
return DD;
}
return nullptr;
}
static bool isDeclContextInNamespace(const DeclContext *DC) {

View File

@ -1681,6 +1681,9 @@ void TextNodeDumper::VisitFunctionDecl(const FunctionDecl *D) {
if (D->isTrivial())
OS << " trivial";
if (D->isIneligibleOrNotSelected())
OS << (isa<CXXDestructorDecl>(D) ? " not_selected" : " ineligible");
if (const auto *FPT = D->getType()->getAs<FunctionProtoType>()) {
FunctionProtoType::ExtProtoInfo EPI = FPT->getExtProtoInfo();
switch (EPI.ExceptionSpec.Type) {

View File

@ -8852,6 +8852,10 @@ static FunctionDecl *CreateNewFunctionDecl(Sema &SemaRef, Declarator &D,
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 @@ void Sema::ActOnLastBitfield(SourceLocation DeclLoc,
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<CXXDestructorDecl>(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<CXXDestructorDecl>(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<CXXDestructorDecl>(OCS.begin()->Function));
}
}
} // namespace
void Sema::ActOnFields(Scope *S, SourceLocation RecLoc, Decl *EnclosingDecl,
ArrayRef<Decl *> Fields, SourceLocation LBrac,
SourceLocation RBrac,
@ -17737,6 +17810,9 @@ void Sema::ActOnFields(Scope *S, SourceLocation RecLoc, Decl *EnclosingDecl,
RecordDecl *Record = dyn_cast<RecordDecl>(EnclosingDecl);
CXXRecordDecl *CXXRecord = dyn_cast<CXXRecordDecl>(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;

View File

@ -6693,7 +6693,7 @@ static bool canPassInRegisters(Sema &S, CXXRecordDecl *D,
return false;
for (const CXXMethodDecl *MD : D->methods()) {
if (MD->isDeleted())
if (MD->isDeleted() || MD->isIneligibleOrNotSelected())
continue;
auto *CD = dyn_cast<CXXConstructorDecl>(MD);

View File

@ -3250,6 +3250,9 @@ Sema::InstantiateClassMembers(SourceLocation PointOfInstantiation,
if (FunctionDecl *Pattern =
Function->getInstantiatedFromMemberFunction()) {
if (Function->isIneligibleOrNotSelected())
continue;
if (Function->getTrailingRequiresClause()) {
ConstraintSatisfaction Satisfaction;
if (CheckFunctionConstraints(Function, Satisfaction) ||

View File

@ -2478,6 +2478,7 @@ Decl *TemplateDeclInstantiator::VisitCXXMethodDecl(
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(

View File

@ -303,7 +303,7 @@ namespace testClassTemplateDecl {
// 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:24, col:30> col:30 implicit referenced class TestClassTemplate
// CHECK-NEXT: | |-AccessSpecDecl 0x{{.+}} <line:[[@LINE-50]]:3, col:9> col:3 public
// CHECK-NEXT: | |-CXXConstructorDecl 0x{{.+}} <line:[[@LINE-50]]:5, col:23> col:5 TestClassTemplate<T> 'void ()'

View File

@ -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 <int N>
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": {}

View File

@ -0,0 +1,77 @@
// RUN: %clang_cc1 -std=c++20 -verify %s
template <int N>
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 <int N>
struct B {
~B() requires(N == 1) = delete; // expected-note {{explicitly marked deleted}}
virtual ~B() = delete; // expected-note {{explicitly marked deleted}}
};
template <int N>
concept CO1 = N == 1;
template <int N>
concept CO2 = N >
0;
template <int N>
struct C {
~C() = delete; // expected-note {{explicitly marked deleted}}
~C() requires(CO1<N>) = delete;
~C() requires(CO1<N> &&CO2<N>) = delete; // expected-note {{explicitly marked deleted}}
};
template <int N>
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 <class T>
concept Foo = requires(T t) {
{t.foo()};
};
template <int N>
struct E {
void foo();
~E();
~E() requires Foo<E> = 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}}
}

View File

@ -49,7 +49,6 @@ struct S {
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 @@ struct S {
void bar() {
WrapsStatics<int>::foo(A{});
S<int>{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<int> s = 1;
// expected-error@-1{{invalid reference to function '~S': constraints not satisfied}}
int a = s;
}

View File

@ -98,7 +98,7 @@ struct S {
template <class>
~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