Skip to content

Commit 63c5a42

Browse files
[Clang] Fix constexpr-ness on implicitly deleted destructors (#116359)
In C++20, a defaulted but implicitly deleted destructor is constexpr if and only if the class has no virtual base class. This hasn't been changed in C++23 by P2448R2. Constexpr-ness on a deleted destructor affects almost nothing. The `__is_literal` intrinsic is related, while the corresponding `std::is_literal_type(_v)` utility has been removed in C++20. A recently added example in `test/AST/ByteCode/cxx23.cpp` will become valid, and the example is already accepted by GCC. Clang currently behaves correctly in C++23 mode, because the constexpr-ness on defaulted destructor is relaxed by P2448R2. But we should make similar relaxation for an implicitly deleted destructor. Fixes #85550.
1 parent d2b482b commit 63c5a42

File tree

5 files changed

+115
-6
lines changed

5 files changed

+115
-6
lines changed

clang/docs/ReleaseNotes.rst

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -158,6 +158,35 @@ C++ Specific Potentially Breaking Changes
158158

159159
Previously, this code was erroneously accepted.
160160

161+
- Clang will now consider the implicitly deleted destructor of a union or
162+
a non-union class without virtual base class to be ``constexpr`` in C++20
163+
mode (Clang 19 only did so in C++23 mode but the standard specification for
164+
this changed in C++20). (#GH85550)
165+
166+
.. code-block:: c++
167+
168+
struct NonLiteral {
169+
NonLiteral() {}
170+
~NonLiteral() {}
171+
};
172+
173+
template <class T>
174+
struct Opt {
175+
union {
176+
char c;
177+
T data;
178+
};
179+
bool engaged = false;
180+
181+
constexpr Opt() {}
182+
constexpr ~Opt() {
183+
if (engaged)
184+
data.~T();
185+
}
186+
};
187+
188+
// Previously only accepted in C++23 and later, now also accepted in C++20.
189+
consteval void foo() { Opt<NonLiteral>{}; }
161190

162191
ABI Changes in This Version
163192
---------------------------

clang/include/clang/AST/DeclCXX.h

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -890,6 +890,13 @@ class CXXRecordDecl : public RecordDecl {
890890
needsOverloadResolutionForDestructor()) &&
891891
"destructor should not be deleted");
892892
data().DefaultedDestructorIsDeleted = true;
893+
// C++23 [dcl.constexpr]p3.2:
894+
// if the function is a constructor or destructor, its class does not have
895+
// any virtual base classes.
896+
// C++20 [dcl.constexpr]p5:
897+
// The definition of a constexpr destructor whose function-body is
898+
// not = delete shall additionally satisfy...
899+
data().DefaultedDestructorIsConstexpr = data().NumVBases == 0;
893900
}
894901

895902
/// Determine whether this class should get an implicit move

clang/lib/AST/DeclCXX.cpp

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -546,9 +546,9 @@ void CXXRecordDecl::addedClassSubobject(CXXRecordDecl *Subobj) {
546546
data().NeedOverloadResolutionForDestructor = true;
547547
}
548548

549-
// C++2a [dcl.constexpr]p4:
550-
// The definition of a constexpr destructor [shall] satisfy the
551-
// following requirement:
549+
// C++20 [dcl.constexpr]p5:
550+
// The definition of a constexpr destructor whose function-body is not
551+
// = delete shall additionally satisfy the following requirement:
552552
// -- for every subobject of class type or (possibly multi-dimensional)
553553
// array thereof, that class type shall have a constexpr destructor
554554
if (!Subobj->hasConstexprDestructor())
@@ -1213,8 +1213,13 @@ void CXXRecordDecl::addedMember(Decl *D) {
12131213
data().DefaultedCopyAssignmentIsDeleted = true;
12141214
if (FieldRec->hasNonTrivialMoveAssignment())
12151215
data().DefaultedMoveAssignmentIsDeleted = true;
1216-
if (FieldRec->hasNonTrivialDestructor())
1216+
if (FieldRec->hasNonTrivialDestructor()) {
12171217
data().DefaultedDestructorIsDeleted = true;
1218+
// C++20 [dcl.constexpr]p5:
1219+
// The definition of a constexpr destructor whose function-body is
1220+
// not = delete shall additionally satisfy...
1221+
data().DefaultedDestructorIsConstexpr = true;
1222+
}
12181223
}
12191224

12201225
// For an anonymous union member, our overload resolution will perform

clang/test/AST/ByteCode/cxx23.cpp

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -263,7 +263,7 @@ namespace AnonUnionDtor {
263263
template <class T>
264264
struct opt
265265
{
266-
union { // all20-note {{is not literal}}
266+
union {
267267
char c;
268268
T data;
269269
};
@@ -279,7 +279,7 @@ namespace AnonUnionDtor {
279279
};
280280

281281
consteval void foo() {
282-
opt<A> a; // all20-error {{variable of non-literal type}}
282+
opt<A> a;
283283
}
284284

285285
void bar() { foo(); }

clang/test/SemaCXX/literal-type.cpp

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ static_assert(__is_literal(VectorExt), "fail");
2020
// [...]
2121
// -- a class type that has all of the following properties:
2222
// -- it has a trivial destructor
23+
// [P0784R7 changed the condition to "constexpr destructor" in C++20]
2324
// -- every constructor call and full-expression in the
2425
// brace-or-equal-initializers for non-static data members (if an) is
2526
// a constant expression,
@@ -108,3 +109,70 @@ void test() {
108109

109110
}
110111
#endif
112+
113+
#if __cplusplus >= 201103L
114+
namespace GH85550 {
115+
struct HasDefaultCtorAndNonConstexprDtor {
116+
constexpr HasDefaultCtorAndNonConstexprDtor() = default;
117+
~HasDefaultCtorAndNonConstexprDtor() {}
118+
};
119+
120+
union UnionWithNonLiteralMember {
121+
HasDefaultCtorAndNonConstexprDtor x;
122+
int y;
123+
124+
constexpr UnionWithNonLiteralMember() : x{} {}
125+
};
126+
#if __cplusplus >= 202002L
127+
static_assert(__is_literal(UnionWithNonLiteralMember), "fail");
128+
#else
129+
static_assert(!__is_literal(UnionWithNonLiteralMember), "fail");
130+
#endif
131+
132+
union UnionWithNonLiteralMemberExplicitDtor1 {
133+
HasDefaultCtorAndNonConstexprDtor x;
134+
int y;
135+
// expected-note@-2 {{destructor of 'UnionWithNonLiteralMemberExplicitDtor1' is implicitly deleted because variant field 'x' has a non-trivial destructor}}
136+
137+
constexpr UnionWithNonLiteralMemberExplicitDtor1() : x{} {}
138+
~UnionWithNonLiteralMemberExplicitDtor1() = default; // expected-warning {{explicitly defaulted destructor is implicitly deleted}}
139+
// expected-note@-1 {{replace 'default' with 'delete'}}
140+
};
141+
#if __cplusplus >= 202002L
142+
static_assert(__is_literal(UnionWithNonLiteralMemberExplicitDtor1), "fail");
143+
#else
144+
static_assert(!__is_literal(UnionWithNonLiteralMemberExplicitDtor1), "fail");
145+
#endif
146+
147+
union UnionWithNonLiteralMemberExplicitDtor2 {
148+
HasDefaultCtorAndNonConstexprDtor x;
149+
int y;
150+
151+
constexpr UnionWithNonLiteralMemberExplicitDtor2() : x{} {}
152+
~UnionWithNonLiteralMemberExplicitDtor2() = delete;
153+
};
154+
static_assert(!__is_literal(UnionWithNonLiteralMemberExplicitDtor2), "fail");
155+
156+
#if __cplusplus >= 202002L
157+
union UnionWithNonLiteralMemberConstexprDtor1 {
158+
HasDefaultCtorAndNonConstexprDtor x;
159+
int y;
160+
// expected-note@-2 {{destructor of 'UnionWithNonLiteralMemberConstexprDtor1' is implicitly deleted because variant field 'x' has a non-trivial destructor}}
161+
162+
constexpr UnionWithNonLiteralMemberConstexprDtor1() : x{} {}
163+
constexpr ~UnionWithNonLiteralMemberConstexprDtor1() = default; // expected-warning {{explicitly defaulted destructor is implicitly deleted}}
164+
// expected-note@-1 {{replace 'default' with 'delete'}}
165+
};
166+
static_assert(__is_literal(UnionWithNonLiteralMemberConstexprDtor1), "fail");
167+
168+
union UnionWithNonLiteralMemberConstexprDtor2 {
169+
HasDefaultCtorAndNonConstexprDtor x;
170+
int y;
171+
172+
constexpr UnionWithNonLiteralMemberConstexprDtor2() : x{} {}
173+
constexpr ~UnionWithNonLiteralMemberConstexprDtor2() = delete;
174+
};
175+
static_assert(__is_literal(UnionWithNonLiteralMemberConstexprDtor2), "fail");
176+
#endif
177+
}
178+
#endif

0 commit comments

Comments
 (0)