Skip to content

[Clang] Fix constexpr-ness on implicitly deleted destructors #116359

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 6 commits into from
Nov 28, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 29 additions & 0 deletions clang/docs/ReleaseNotes.rst
Original file line number Diff line number Diff line change
Expand Up @@ -158,6 +158,35 @@ C++ Specific Potentially Breaking Changes

Previously, this code was erroneously accepted.

- Clang will now consider the implicitly deleted destructor of a union or
a non-union class without virtual base class to be ``constexpr`` in C++20
mode (Clang 19 only did so in C++23 mode but the standard specification for
this changed in C++20). (#GH85550)

.. code-block:: c++

struct NonLiteral {
NonLiteral() {}
~NonLiteral() {}
};

template <class T>
struct Opt {
union {
char c;
T data;
};
bool engaged = false;

constexpr Opt() {}
constexpr ~Opt() {
if (engaged)
data.~T();
}
};

// Previously only accepted in C++23 and later, now also accepted in C++20.
consteval void foo() { Opt<NonLiteral>{}; }

ABI Changes in This Version
---------------------------
Expand Down
7 changes: 7 additions & 0 deletions clang/include/clang/AST/DeclCXX.h
Original file line number Diff line number Diff line change
Expand Up @@ -890,6 +890,13 @@ class CXXRecordDecl : public RecordDecl {
needsOverloadResolutionForDestructor()) &&
"destructor should not be deleted");
data().DefaultedDestructorIsDeleted = true;
// C++23 [dcl.constexpr]p3.2:
// if the function is a constructor or destructor, its class does not have
// any virtual base classes.
// C++20 [dcl.constexpr]p5:
// The definition of a constexpr destructor whose function-body is
// not = delete shall additionally satisfy...
data().DefaultedDestructorIsConstexpr = data().NumVBases == 0;
}

/// Determine whether this class should get an implicit move
Expand Down
13 changes: 9 additions & 4 deletions clang/lib/AST/DeclCXX.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -546,9 +546,9 @@ void CXXRecordDecl::addedClassSubobject(CXXRecordDecl *Subobj) {
data().NeedOverloadResolutionForDestructor = true;
}

// C++2a [dcl.constexpr]p4:
// The definition of a constexpr destructor [shall] satisfy the
// following requirement:
// C++20 [dcl.constexpr]p5:
// The definition of a constexpr destructor whose function-body is not
// = delete shall additionally satisfy the following requirement:
// -- for every subobject of class type or (possibly multi-dimensional)
// array thereof, that class type shall have a constexpr destructor
if (!Subobj->hasConstexprDestructor())
Expand Down Expand Up @@ -1213,8 +1213,13 @@ void CXXRecordDecl::addedMember(Decl *D) {
data().DefaultedCopyAssignmentIsDeleted = true;
if (FieldRec->hasNonTrivialMoveAssignment())
data().DefaultedMoveAssignmentIsDeleted = true;
if (FieldRec->hasNonTrivialDestructor())
if (FieldRec->hasNonTrivialDestructor()) {
data().DefaultedDestructorIsDeleted = true;
// C++20 [dcl.constexpr]p5:
// The definition of a constexpr destructor whose function-body is
// not = delete shall additionally satisfy...
data().DefaultedDestructorIsConstexpr = true;
}
}

// For an anonymous union member, our overload resolution will perform
Expand Down
4 changes: 2 additions & 2 deletions clang/test/AST/ByteCode/cxx23.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -263,7 +263,7 @@ namespace AnonUnionDtor {
template <class T>
struct opt
{
union { // all20-note {{is not literal}}
union {
char c;
T data;
};
Expand All @@ -279,7 +279,7 @@ namespace AnonUnionDtor {
};

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

void bar() { foo(); }
Expand Down
68 changes: 68 additions & 0 deletions clang/test/SemaCXX/literal-type.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ static_assert(__is_literal(VectorExt), "fail");
// [...]
// -- a class type that has all of the following properties:
// -- it has a trivial destructor
// [P0784R7 changed the condition to "constexpr destructor" in C++20]
// -- every constructor call and full-expression in the
// brace-or-equal-initializers for non-static data members (if an) is
// a constant expression,
Expand Down Expand Up @@ -108,3 +109,70 @@ void test() {

}
#endif

#if __cplusplus >= 201103L
namespace GH85550 {
struct HasDefaultCtorAndNonConstexprDtor {
constexpr HasDefaultCtorAndNonConstexprDtor() = default;
~HasDefaultCtorAndNonConstexprDtor() {}
};

union UnionWithNonLiteralMember {
HasDefaultCtorAndNonConstexprDtor x;
int y;

constexpr UnionWithNonLiteralMember() : x{} {}
};
#if __cplusplus >= 202002L
static_assert(__is_literal(UnionWithNonLiteralMember), "fail");
#else
static_assert(!__is_literal(UnionWithNonLiteralMember), "fail");
#endif

union UnionWithNonLiteralMemberExplicitDtor1 {
HasDefaultCtorAndNonConstexprDtor x;
int y;
// expected-note@-2 {{destructor of 'UnionWithNonLiteralMemberExplicitDtor1' is implicitly deleted because variant field 'x' has a non-trivial destructor}}

constexpr UnionWithNonLiteralMemberExplicitDtor1() : x{} {}
~UnionWithNonLiteralMemberExplicitDtor1() = default; // expected-warning {{explicitly defaulted destructor is implicitly deleted}}
// expected-note@-1 {{replace 'default' with 'delete'}}
};
#if __cplusplus >= 202002L
static_assert(__is_literal(UnionWithNonLiteralMemberExplicitDtor1), "fail");
#else
static_assert(!__is_literal(UnionWithNonLiteralMemberExplicitDtor1), "fail");
#endif

union UnionWithNonLiteralMemberExplicitDtor2 {
HasDefaultCtorAndNonConstexprDtor x;
int y;

constexpr UnionWithNonLiteralMemberExplicitDtor2() : x{} {}
~UnionWithNonLiteralMemberExplicitDtor2() = delete;
};
static_assert(!__is_literal(UnionWithNonLiteralMemberExplicitDtor2), "fail");

#if __cplusplus >= 202002L
union UnionWithNonLiteralMemberConstexprDtor1 {
HasDefaultCtorAndNonConstexprDtor x;
int y;
// expected-note@-2 {{destructor of 'UnionWithNonLiteralMemberConstexprDtor1' is implicitly deleted because variant field 'x' has a non-trivial destructor}}

constexpr UnionWithNonLiteralMemberConstexprDtor1() : x{} {}
constexpr ~UnionWithNonLiteralMemberConstexprDtor1() = default; // expected-warning {{explicitly defaulted destructor is implicitly deleted}}
// expected-note@-1 {{replace 'default' with 'delete'}}
};
static_assert(__is_literal(UnionWithNonLiteralMemberConstexprDtor1), "fail");

union UnionWithNonLiteralMemberConstexprDtor2 {
HasDefaultCtorAndNonConstexprDtor x;
int y;

constexpr UnionWithNonLiteralMemberConstexprDtor2() : x{} {}
constexpr ~UnionWithNonLiteralMemberConstexprDtor2() = delete;
};
static_assert(__is_literal(UnionWithNonLiteralMemberConstexprDtor2), "fail");
#endif
}
#endif