Skip to content
9 changes: 6 additions & 3 deletions clang/lib/AST/DeclCXX.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1217,6 +1217,9 @@ void CXXRecordDecl::addedMember(Decl *D) {
// those because they are always unnamed.
bool IsZeroSize = Field->isZeroSize(Context);

// P3074
const bool TrivialUnion = Context.getLangOpts().CPlusPlus26 && isUnion();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
const bool TrivialUnion = Context.getLangOpts().CPlusPlus26 && isUnion();
bool TrivialUnion = Context.getLangOpts().CPlusPlus26 && isUnion();

nit (we typically don’t use top-level const for local vars)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why not? I see a lot of them btw.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I thought it was in our coding standards because I’m used to people pointing this out all the time but https://llvm.org/docs/CodingStandards.html doesn’t seem to have anything to say about it.


if (const auto *RecordTy = T->getAs<RecordType>()) {
auto *FieldRec = cast<CXXRecordDecl>(RecordTy->getDecl());
if (FieldRec->getDefinition()) {
Expand Down Expand Up @@ -1277,7 +1280,7 @@ void CXXRecordDecl::addedMember(Decl *D) {
// -- for all the non-static data members of its class that are of
// class type (or array thereof), each such class has a trivial
// default constructor.
if (!FieldRec->hasTrivialDefaultConstructor())
if (!FieldRec->hasTrivialDefaultConstructor() && !TrivialUnion)
data().HasTrivialSpecialMembers &= ~SMF_DefaultConstructor;

// C++0x [class.copy]p13:
Expand Down Expand Up @@ -1315,9 +1318,9 @@ void CXXRecordDecl::addedMember(Decl *D) {
if (!FieldRec->hasTrivialMoveAssignment())
data().HasTrivialSpecialMembers &= ~SMF_MoveAssignment;

if (!FieldRec->hasTrivialDestructor())
if (!FieldRec->hasTrivialDestructor() && !TrivialUnion)
data().HasTrivialSpecialMembers &= ~SMF_Destructor;
if (!FieldRec->hasTrivialDestructorForCall())
if (!FieldRec->hasTrivialDestructorForCall() && !TrivialUnion)
data().HasTrivialSpecialMembersForCall &= ~SMF_Destructor;
if (!FieldRec->hasIrrelevantDestructor())
data().HasIrrelevantDestructor = false;
Expand Down
53 changes: 52 additions & 1 deletion clang/lib/Sema/SemaDeclCXX.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -9543,6 +9543,48 @@ bool SpecialMemberDeletionInfo::shouldDeleteForSubobjectCall(
if (DiagKind == -1)
return false;

if (this->S.Context.getLangOpts().CPlusPlus26 && inUnion() &&
CSM == CXXSpecialMemberKind::Destructor) {
// [class.dtor]/7 In C++26, a destructor for a union X is only deleted under
// the additional conditions that:

// overload resolution to select a constructor to default-initialize an
// object of type X either fails or selects a constructor that is either
// deleted or not trivial, or
// or X has a variant member V of class type M (or possibly
// multi-dimensional array thereof) where V has a default member initializer
// and M has a destructor that is non-trivial,

RecordDecl *Parent = Field->getParent();
while (Parent &&
(Parent->isAnonymousStructOrUnion() ||
(Parent->isUnion() && Parent->getIdentifier() == nullptr))) {
if (auto RD = dyn_cast_or_null<RecordDecl>(Parent->getParent())) {
Parent = RD;
} else {
break;
}
}

auto ParentDecl = dyn_cast<CXXRecordDecl>(Parent);
if (!ParentDecl->isBeingDefined()) {
Sema::SpecialMemberOverloadResult SMOR = S.LookupSpecialMember(
ParentDecl, CXXSpecialMemberKind::DefaultConstructor, false, false,
false, false, false);
if (SMOR.getKind() == Sema::SpecialMemberOverloadResult::Success) {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm guessing this is the wrong way to do this, but I don't know how to do it better.

Also this doesn't exactly implement the wording I quoted above because the wording is just wrong (see future CWG issue here).

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

At least when it comes to getting the parent type, we do something similar in GetEnclosingNamedOrTopAnonRecord() in SemaBoundsSafety.cpp so I don’t think there is a helper for that.

CXXRecordDecl has hasTrivialDefaultConstructor() and friends; you might be able to make use of those here rather than performing a lookup manually (at least the check for !isBeingDefined() would imply to me that the flags that those functions query are accurate), but it’s possible I’m missing something here.

CXXConstructorDecl *Ctor =
dyn_cast<CXXConstructorDecl>(SMOR.getMethod());
if (Ctor->isTrivial()) {
return false;
}

if (!Ctor->isUserProvided() && !Field->hasInClassInitializer()) {
return false;
}
}
}
}

if (Diagnose) {
if (Field) {
S.Diag(Field->getLocation(),
Expand Down Expand Up @@ -9696,6 +9738,10 @@ bool SpecialMemberDeletionInfo::shouldDeleteForField(FieldDecl *FD) {
if (inUnion() && shouldDeleteForVariantPtrAuthMember(FD))
return true;

if (inUnion() && S.Context.getLangOpts().CPlusPlus26 &&
CSM == CXXSpecialMemberKind::DefaultConstructor)
return false;

if (CSM == CXXSpecialMemberKind::DefaultConstructor) {
// For a default constructor, all references must be initialized in-class
// and, if a union, it must have a non-const member.
Expand Down Expand Up @@ -9774,7 +9820,8 @@ bool SpecialMemberDeletionInfo::shouldDeleteForField(FieldDecl *FD) {

// At least one member in each anonymous union must be non-const
if (CSM == CXXSpecialMemberKind::DefaultConstructor &&
AllVariantFieldsAreConst && !FieldRecord->field_empty()) {
AllVariantFieldsAreConst && !FieldRecord->field_empty() &&
!S.Context.getLangOpts().CPlusPlus26) {
if (Diagnose)
S.Diag(FieldRecord->getLocation(),
diag::note_deleted_default_ctor_all_const)
Expand Down Expand Up @@ -9804,6 +9851,10 @@ bool SpecialMemberDeletionInfo::shouldDeleteForAllConstMembers() {
// default constructor. Don't do that.
if (CSM == CXXSpecialMemberKind::DefaultConstructor && inUnion() &&
AllFieldsAreConst) {

if (S.Context.getLangOpts().CPlusPlus26)
return false;

bool AnyFields = false;
for (auto *F : MD->getParent()->fields())
if ((AnyFields = !F->isUnnamedBitField()))
Expand Down
4 changes: 4 additions & 0 deletions clang/test/CXX/drs/cwg6xx.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -922,7 +922,11 @@ namespace cwg667 { // cwg667: 8

struct B { ~B() = delete; };
union C { B b; };
#if __cplusplus > 202302L
static_assert(__is_trivially_destructible(C), "");
#else
static_assert(!__is_trivially_destructible(C), "");
#endif

struct D { D(const D&) = delete; };
struct E : D {};
Expand Down
16 changes: 9 additions & 7 deletions clang/test/CXX/special/class.ctor/p5-0x.cpp
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
// RUN: %clang_cc1 -fsyntax-only -verify %s -std=c++11 -Wno-deprecated-builtins -Wno-defaulted-function-deleted
// RUN: %clang_cc1 -fsyntax-only -verify=expected,cxx11-23 %s -std=c++11 -Wno-deprecated-builtins -Wno-defaulted-function-deleted
// RUN: %clang_cc1 -fsyntax-only -verify=expected,cxx11-23 %s -std=c++23 -Wno-deprecated-builtins -Wno-defaulted-function-deleted
// RUN: %clang_cc1 -fsyntax-only -verify=expected %s -std=c++26 -Wno-deprecated-builtins -Wno-defaulted-function-deleted

struct DefaultedDefCtor1 {};
struct DefaultedDefCtor2 { DefaultedDefCtor2() = default; };
Expand All @@ -23,8 +25,8 @@ int n;

// - X is a union-like class that has a variant member with a non-trivial
// default constructor,
union Deleted1a { UserProvidedDefCtor u; }; // expected-note {{default constructor of 'Deleted1a' is implicitly deleted because variant field 'u' has a non-trivial default constructor}}
Deleted1a d1a; // expected-error {{implicitly-deleted default constructor}}
union Deleted1a { UserProvidedDefCtor u; }; // cxx11-23-note {{default constructor of 'Deleted1a' is implicitly deleted because variant field 'u' has a non-trivial default constructor}}
Deleted1a d1a; // cxx11-23-error {{implicitly-deleted default constructor}}
union NotDeleted1a { DefaultedDefCtor1 nu; };
NotDeleted1a nd1a;
union NotDeleted1b { DefaultedDefCtor2 nu; };
Expand Down Expand Up @@ -86,19 +88,19 @@ NotDeleted3i nd3i;
union Deleted4a {
const int a;
const int b;
const UserProvidedDefCtor c; // expected-note {{because variant field 'c' has a non-trivial default constructor}}
const UserProvidedDefCtor c; // cxx11-23-note {{because variant field 'c' has a non-trivial default constructor}}
};
Deleted4a d4a; // expected-error {{implicitly-deleted default constructor}}
Deleted4a d4a; // cxx11-23-error {{implicitly-deleted default constructor}}
union NotDeleted4a { const int a; int b; };
NotDeleted4a nd4a;

// - X is a non-union class and all members of any anonymous union member are of
// const-qualified type (or array thereof),
struct Deleted5a {
union { const int a; }; // expected-note {{because all data members of an anonymous union member are const-qualified}}
union { const int a; }; // cxx11-23-note {{because all data members of an anonymous union member are const-qualified}}
union { int b; };
};
Deleted5a d5a; // expected-error {{implicitly-deleted default constructor}}
Deleted5a d5a; // cxx11-23-error {{implicitly-deleted default constructor}}
struct NotDeleted5a { union { const int a; int b; }; union { const int c; int d; }; };
NotDeleted5a nd5a;

Expand Down
28 changes: 15 additions & 13 deletions clang/test/CXX/special/class.ctor/p6-0x.cpp
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
// RUN: %clang_cc1 -fsyntax-only -verify %s -std=c++11
// RUN: %clang_cc1 -fsyntax-only -verify=expected,cxx11-23 %s -std=c++11
// RUN: %clang_cc1 -fsyntax-only -verify=expected,cxx26 %s -std=c++26

// Implicitly-defined default constructors are constexpr if the implicit
// definition would be.
Expand All @@ -15,8 +16,9 @@ constexpr NonConstexpr2a nc2a = NonConstexpr2a(); // ok, does not call construct
constexpr int nc2_a = NonConstexpr2().nl.a; // ok
constexpr int nc2a_a = NonConstexpr2a().a; // ok
struct Helper {
friend constexpr NonConstexpr1::NonConstexpr1(); // expected-error {{follows non-constexpr declaration}}
friend constexpr NonConstexpr2::NonConstexpr2(); // expected-error {{follows non-constexpr declaration}}
friend constexpr NonConstexpr1::NonConstexpr1(); // cxx11-23-error {{follows non-constexpr declaration}} cxx26-error {{missing exception specification}}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That ‘is missing exception specification 'noexcept'’ error is weird; I’m also getting that before C++26 and only Clang errors on this; GCC, MSVC, and EDG seem to be fine with it. I don’t think that has anything to do w/ this patch but it’s still strange.

friend constexpr NonConstexpr2::NonConstexpr2(); // cxx11-23-error {{follows non-constexpr declaration}} cxx26-error {{missing exception specification}}

};

struct Constexpr1 {};
Expand All @@ -31,14 +33,14 @@ constexpr Constexpr2 c2 = Constexpr2(); // ok

int n;
struct Member {
Member() : a(n) {}
Member() : a(n) {} // cxx26-note {{here}}
constexpr Member(int&a) : a(a) {}
int &a;
};
struct NonConstexpr4 { // expected-note {{here}}
struct NonConstexpr4 { // cxx11-23-note {{here}} cxx26-note {{non-constexpr constructor}}
Member m;
};
constexpr NonConstexpr4 nc4 = NonConstexpr4(); // expected-error {{constant expression}} expected-note {{non-constexpr constructor 'NonConstexpr4'}}
constexpr NonConstexpr4 nc4 = NonConstexpr4(); // expected-error {{constant expression}} cxx11-23-note {{non-constexpr constructor 'NonConstexpr4'}} cxx26-note {{in call to}}
struct Constexpr3 {
constexpr Constexpr3() : m(n) {}
Member m;
Expand All @@ -53,11 +55,11 @@ constexpr Constexpr4 c4 = Constexpr4(); // ok
// This rule breaks some legal C++98 programs!
struct A {}; // expected-note {{here}}
struct B {
friend A::A(); // expected-error {{non-constexpr declaration of 'A' follows constexpr declaration}}
friend A::A(); // cxx11-23-error {{non-constexpr declaration of 'A' follows constexpr declaration}} cxx26-error {{missing exception specification}}
};

namespace UnionCtors {
union A { // expected-note {{here}}
union A { // cxx11-23-note {{here}}
int a;
int b;
};
Expand All @@ -79,19 +81,19 @@ namespace UnionCtors {
int d = 5;
};
};
struct E { // expected-note {{here}}
struct E { // cxx11-23-note {{here}}
union {
int a;
int b;
};
};

struct Test {
friend constexpr A::A() noexcept; // expected-error {{follows non-constexpr declaration}}
friend constexpr A::A() noexcept; // cxx11-23-error {{follows non-constexpr declaration}}
friend constexpr B::B() noexcept;
friend constexpr C::C() noexcept;
friend constexpr D::D() noexcept;
friend constexpr E::E() noexcept; // expected-error {{follows non-constexpr declaration}}
friend constexpr E::E() noexcept; // cxx11-23-error {{follows non-constexpr declaration}}
};
}

Expand Down Expand Up @@ -122,6 +124,6 @@ namespace PR48763 {

struct G { G(); };
struct H : D { using D::D; H(int); G g; };
union V { H h; }; // expected-note {{field 'h' has a non-trivial default constructor}}
V v; // expected-error {{deleted}}
union V { H h; }; // cxx11-23-note {{field 'h' has a non-trivial default constructor}}
V v; // cxx11-23-error {{deleted}}
}
3 changes: 2 additions & 1 deletion clang/test/CXX/special/class.dtor/p5-0x.cpp
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
// RUN: %clang_cc1 -verify -std=c++11 %s -Wno-defaulted-function-deleted -triple x86_64-linux-gnu
// RUN: %clang_cc1 -verify=expected -std=c++11 %s -Wno-defaulted-function-deleted -triple x86_64-linux-gnu
// RUN: %clang_cc1 -verify=expected -std=c++26 %s -Wno-defaulted-function-deleted -triple x86_64-linux-gnu

struct NonTrivDtor {
~NonTrivDtor();
Expand Down
69 changes: 69 additions & 0 deletions clang/test/CXX/special/class.dtor/p7.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
// RUN: %clang_cc1 -verify -std=c++26 %s -Wno-defaulted-function-deleted -triple x86_64-linux-gnu

struct NonTrivial {
NonTrivial(int) { }
~NonTrivial() { }
};

union U0 {
NonTrivial nt;
int i;
};
U0 u0;

// overload resolution to select a constructor to default-initialize an object of type X either fails
union U1 {
U1(int);
NonTrivial nt; // #1
};
U1 u1(1); // expected-error {{deleted function}} expected-note@#1 {{non-trivial destructor}}

// or selects a constructor that is either deleted or not trivial, or
union U2 {
U2() : nt(2) { }
NonTrivial nt; // #2
};
U2 u2; // expected-error {{deleted function}} expected-note@#2 {{non-trivial destructor}}

union U3 {
U3() = delete;
U3(int);
NonTrivial nt; // #3
};
U3 u3(1); // expected-error {{deleted function}} expected-note@#3 {{non-trivial destructor}}

// or X has a variant member V of class type M (or possibly multi-dimensional array thereof) where V has a default member initializer and M has a destructor that is non-trivial,
union U4 {
NonTrivial nt = 1; // #4
};
U4 u4; // expected-error {{deleted function}} expected-note@#4 {{non-trivial destructor}}

union U5 {
NonTrivial nt;
U5* next = nullptr;
};
U5 u5;

union U6 {
U6() = default;
NonTrivial nt;
U6* next = nullptr;
};
U6 u6;


struct DeletedDtor {
~DeletedDtor() = delete; // expected-note 2 {{deleted here}}
};
union B1 {
B1();
DeletedDtor a; // expected-note {{because field 'a' has a deleted destructor}}
};
B1 b1; // expected-error {{deleted function}}
union B2 {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Some more cases:

union A {
    struct {
        NonTrivial x; // W/ and w/o a default member initialiser.
    };
};
A a;

struct B {
    union {
        NonTrivial x; // as above
    };
};
B b;

union {
    struct {
        union {
            struct {
                union {
                    NonTrivial x; // as above
                };
            };
        };
    };
} c;

struct {
    union {
        struct {
            union {
                struct {
                    NonTrivial x; // as above
                };
            };
        };
    };
} d;

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Alright I'm not handling this case correctly, but I can't figure out why:

union U9 {
    struct {
        NonTrivial x = 1; // #6
    };
} u9; // expected-error {{deleted function}}

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm, at a glance nothing jumps out to me; we do create a FieldDecl for the anonymous struct inside the union so I’d expect this to just handle that case—maybe we’re doing something weird with IndirectFieldDecls somewhere but that’s just a guess.

I did spot a fixme about anonymous unions inside anonymous unions, so er, here’s another gross test case I suppose:

union {
    union {
        NonTrivial x;
    };
} a;

B2();
union { // expected-note {{deleted destructor}}
DeletedDtor a; // expected-note {{because field 'a' has a deleted destructor}}
};
};
B2 b2; // expected-error {{deleted function}}