[clang] [Clang] Implement P3074R7: trivial unions (C++26) (PR #185886)
Peng Xie via cfe-commits
cfe-commits at lists.llvm.org
Wed Mar 11 06:56:59 PDT 2026
https://github.com/love1angel created https://github.com/llvm/llvm-project/pull/185886
P3074R7 makes union special member functions trivial by default in C++26, regardless of variant member triviality. A defaulted destructor for a union is now defined as deleted only if its default constructor is user-provided (or deleted/ambiguous), or if a variant member has both a default member initializer and a non-trivial destructor.
The pre-existing rule that a deleted or inaccessible member destructor causes the union destructor to be deleted (p7.2) is preserved.
This patch does not implement the implicit-lifetime-start semantics from P3074R7 p4, which was reworked by P3726R1 into __builtin_start_lifetime.
Defines __cpp_trivial_union=202502L (bumped to 202602L by P3726R1).
Close #127868
Related #146815
ping @brevzin
>From 4e18e6d0ef88f3c5f723d6e9725140f5fd167e10 Mon Sep 17 00:00:00 2001
From: Peng Xie <helianthus547 at gmail.com>
Date: Wed, 11 Mar 2026 14:45:40 +0800
Subject: [PATCH] [Clang] Implement P3074R7: trivial unions (C++26)
P3074R7 makes union special member functions trivial by default in
C++26, regardless of variant member triviality. A defaulted destructor
for a union is now defined as deleted only if its default constructor
is user-provided (or deleted/ambiguous), or if a variant member has
both a default member initializer and a non-trivial destructor.
The pre-existing rule that a deleted or inaccessible member destructor
causes the union destructor to be deleted (p7.2) is preserved.
This patch does not implement the implicit-lifetime-start semantics
from P3074R7 p4, which was reworked by P3726R1 into
__builtin_start_lifetime.
Defines __cpp_trivial_union=202502L (bumped to 202602L by P3726R1).
---
.../clang/Basic/DiagnosticSemaKinds.td | 6 +
clang/lib/AST/DeclCXX.cpp | 29 +-
clang/lib/Frontend/InitPreprocessor.cpp | 4 +
clang/lib/Sema/SemaDeclCXX.cpp | 135 +++++++++-
clang/test/CXX/drs/cwg14xx.cpp | 60 +++--
clang/test/CXX/special/class.ctor/p5-0x.cpp | 16 +-
clang/test/CXX/special/class.ctor/p6-0x.cpp | 31 +--
clang/test/CXX/special/class.dtor/p5-0x.cpp | 33 +--
clang/test/SemaCXX/cxx26-trivial-union.cpp | 251 ++++++++++++++++++
9 files changed, 496 insertions(+), 69 deletions(-)
create mode 100644 clang/test/SemaCXX/cxx26-trivial-union.cpp
diff --git a/clang/include/clang/Basic/DiagnosticSemaKinds.td b/clang/include/clang/Basic/DiagnosticSemaKinds.td
index 0c25eb2443d5e..0cb33bc9511c3 100644
--- a/clang/include/clang/Basic/DiagnosticSemaKinds.td
+++ b/clang/include/clang/Basic/DiagnosticSemaKinds.td
@@ -6406,6 +6406,12 @@ def note_enforce_read_only_placement : Note<"type was declared read-only here">;
def note_deleted_dtor_no_operator_delete : Note<
"virtual destructor requires an unambiguous, accessible 'operator delete'">;
+def note_deleted_dtor_default_ctor : Note<
+ "destructor of union %0 is implicitly deleted because "
+ "%select{it has no default constructor|"
+ "its default constructor is a deleted function|"
+ "overload resolution to default-initialize it is ambiguous|"
+ "its default constructor is not trivial}1">;
def note_deleted_special_member_class_subobject : Note<
"%select{default constructor of|copy constructor of|move constructor of|"
"copy assignment operator of|move assignment operator of|destructor of|"
diff --git a/clang/lib/AST/DeclCXX.cpp b/clang/lib/AST/DeclCXX.cpp
index 083c53e28cb91..6cb90e701fff4 100644
--- a/clang/lib/AST/DeclCXX.cpp
+++ b/clang/lib/AST/DeclCXX.cpp
@@ -1180,7 +1180,11 @@ void CXXRecordDecl::addedMember(Decl *D) {
// C++11 [class]p5:
// A default constructor is trivial if [...] no non-static data member
// of its class has a brace-or-equal-initializer.
- data().HasTrivialSpecialMembers &= ~SMF_DefaultConstructor;
+ // P3074R7 [class.default.ctor]p3:
+ // In C++26, a union's default constructor is always trivial,
+ // even with brace-or-equal-initializers.
+ if (!(isUnion() && getASTContext().getLangOpts().CPlusPlus26))
+ data().HasTrivialSpecialMembers &= ~SMF_DefaultConstructor;
// C++11 [dcl.init.aggr]p1:
// An aggregate is a [...] class with [...] no
@@ -1239,7 +1243,11 @@ void CXXRecordDecl::addedMember(Decl *D) {
if (FieldRec->hasNonTrivialMoveAssignment())
data().DefaultedMoveAssignmentIsDeleted = true;
if (FieldRec->hasNonTrivialDestructor()) {
- data().DefaultedDestructorIsDeleted = true;
+ // P3074R7: In C++26, the destructor of a union is not deleted
+ // merely because a variant member has a non-trivial destructor.
+ // Deletion is determined later by Sema based on the new rules.
+ if (!Context.getLangOpts().CPlusPlus26)
+ data().DefaultedDestructorIsDeleted = true;
// C++20 [dcl.constexpr]p5:
// The definition of a constexpr destructor whose function-body is
// not = delete shall additionally satisfy...
@@ -1267,7 +1275,12 @@ 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())
+ // P3074R7 [class.default.ctor]p3:
+ // In C++26, "either X is a union or" for all non-variant
+ // non-static data members [...] each such class has a trivial
+ // default constructor.
+ if (!FieldRec->hasTrivialDefaultConstructor() &&
+ !(isUnion() && Context.getLangOpts().CPlusPlus26))
data().HasTrivialSpecialMembers &= ~SMF_DefaultConstructor;
// C++0x [class.copy]p13:
@@ -1305,9 +1318,15 @@ void CXXRecordDecl::addedMember(Decl *D) {
if (!FieldRec->hasTrivialMoveAssignment())
data().HasTrivialSpecialMembers &= ~SMF_MoveAssignment;
- if (!FieldRec->hasTrivialDestructor())
+ // P3074R7 [class.dtor]p8:
+ // In C++26, "either X is a union or" for all non-variant
+ // non-static data members [...] each such class has a trivial
+ // destructor.
+ if (!FieldRec->hasTrivialDestructor() &&
+ !(isUnion() && Context.getLangOpts().CPlusPlus26))
data().HasTrivialSpecialMembers &= ~SMF_Destructor;
- if (!FieldRec->hasTrivialDestructorForCall())
+ if (!FieldRec->hasTrivialDestructorForCall() &&
+ !(isUnion() && Context.getLangOpts().CPlusPlus26))
data().HasTrivialSpecialMembersForCall &= ~SMF_Destructor;
if (!FieldRec->hasIrrelevantDestructor())
data().HasIrrelevantDestructor = false;
diff --git a/clang/lib/Frontend/InitPreprocessor.cpp b/clang/lib/Frontend/InitPreprocessor.cpp
index 1ccd74314f373..a00b34625510b 100644
--- a/clang/lib/Frontend/InitPreprocessor.cpp
+++ b/clang/lib/Frontend/InitPreprocessor.cpp
@@ -746,6 +746,10 @@ static void InitializeCPlusPlusFeatureTestMacros(const LangOptions &LangOpts,
Builder.defineMacro("__cpp_variadic_friend", "202403L");
Builder.defineMacro("__cpp_trivial_relocatability", "202502L");
+ // C++26 features.
+ if (LangOpts.CPlusPlus26)
+ Builder.defineMacro("__cpp_trivial_union", "202502L");
+
if (LangOpts.Char8)
Builder.defineMacro("__cpp_char8_t", "202207L");
Builder.defineMacro("__cpp_impl_destroying_delete", "201806L");
diff --git a/clang/lib/Sema/SemaDeclCXX.cpp b/clang/lib/Sema/SemaDeclCXX.cpp
index 2ae6e5de0e3ee..c316eca5fc72e 100644
--- a/clang/lib/Sema/SemaDeclCXX.cpp
+++ b/clang/lib/Sema/SemaDeclCXX.cpp
@@ -9615,6 +9615,12 @@ bool SpecialMemberDeletionInfo::shouldDeleteForSubobjectCall(
if (SMOR.getKind() == Sema::SpecialMemberOverloadResult::NoMemberOrDeleted) {
if (CSM == CXXSpecialMemberKind::DefaultConstructor && Field &&
Field->getParent()->isUnion()) {
+ // P3074R7: In C++26, a union's defaulted default constructor is never
+ // deleted due to a variant member with a non-trivial default
+ // constructor. The old [class.default.ctor]p2 union-specific bullets
+ // are removed.
+ if (S.getLangOpts().CPlusPlus26)
+ return false;
// [class.default.ctor]p2:
// A defaulted default constructor for class X is defined as deleted if
// - X is a union that has a variant member with a non-trivial default
@@ -9637,6 +9643,11 @@ bool SpecialMemberDeletionInfo::shouldDeleteForSubobjectCall(
// destructor is never actually called, but is semantically checked as
// if it were.
if (CSM == CXXSpecialMemberKind::DefaultConstructor) {
+ // P3074R7: In C++26, a union's defaulted default constructor is never
+ // deleted due to a variant member with a non-trivial default
+ // constructor.
+ if (S.getLangOpts().CPlusPlus26)
+ return false;
// [class.default.ctor]p2:
// A defaulted default constructor for class X is defined as deleted if
// - X is a union that has a variant member with a non-trivial default
@@ -9645,6 +9656,13 @@ bool SpecialMemberDeletionInfo::shouldDeleteForSubobjectCall(
const auto *RD = cast<CXXRecordDecl>(Field->getParent());
if (!RD->hasInClassInitializer())
DiagKind = NonTrivialDecl;
+ } else if (CSM == CXXSpecialMemberKind::Destructor &&
+ S.getLangOpts().CPlusPlus26) {
+ // P3074R7 [class.dtor]p7: In C++26, a union's destructor is not
+ // deleted merely because a variant member has a non-trivial destructor.
+ // Deletion is determined by the new union-specific rules in
+ // ShouldDeleteSpecialMember.
+ return false;
} else {
DiagKind = NonTrivialDecl;
}
@@ -9806,6 +9824,12 @@ bool SpecialMemberDeletionInfo::shouldDeleteForField(FieldDecl *FD) {
if (inUnion() && shouldDeleteForVariantPtrAuthMember(FD))
return true;
+ // P3074R7: In C++26, a union's defaulted default constructor is trivially
+ // defined and never deleted due to variant member properties.
+ if (inUnion() && S.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.
@@ -9884,7 +9908,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.getLangOpts().CPlusPlus26) {
if (Diagnose)
S.Diag(FieldRecord->getLocation(),
diag::note_deleted_default_ctor_all_const)
@@ -9914,6 +9939,8 @@ bool SpecialMemberDeletionInfo::shouldDeleteForAllConstMembers() {
// default constructor. Don't do that.
if (CSM == CXXSpecialMemberKind::DefaultConstructor && inUnion() &&
AllFieldsAreConst) {
+ if (S.getLangOpts().CPlusPlus26)
+ return false;
bool AnyFields = false;
for (auto *F : MD->getParent()->fields())
if ((AnyFields = !F->isUnnamedBitField()))
@@ -10034,6 +10061,100 @@ bool Sema::ShouldDeleteSpecialMember(CXXMethodDecl *MD,
SpecialMemberDeletionInfo SMI(*this, MD, CSM, ICI, Diagnose);
+ // P3074R7 [class.dtor]p7.x:
+ // In C++26, a defaulted destructor for a union X is defined as deleted if:
+ // (7.x.1) 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
+ // (7.x.2) 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.
+ // Note: p7.2 (deleted/inaccessible member dtor) still applies to unions.
+ // We handle 7.x.1 and 7.x.2 here and fall through to the normal
+ // per-member visit for p7.2 checking. shouldDeleteForSubobjectCall
+ // already returns false for non-trivial-but-not-deleted dtors in C++26
+ // unions, so only truly deleted/inaccessible dtors will cause deletion
+ // through the member visit.
+ if (getLangOpts().CPlusPlus26 && RD->isUnion() &&
+ CSM == CXXSpecialMemberKind::Destructor) {
+ // Check (7.x.1): overload resolution for default initialization.
+ SpecialMemberOverloadResult SMOR = LookupSpecialMember(
+ RD, CXXSpecialMemberKind::DefaultConstructor,
+ /*ConstArg=*/false, /*VolatileArg=*/false, /*RValueThis=*/false,
+ /*ConstThis=*/false, /*VolatileThis=*/false);
+ bool CtorOK = false;
+ if (SMOR.getKind() == SpecialMemberOverloadResult::Success) {
+ auto *Ctor = cast<CXXConstructorDecl>(SMOR.getMethod());
+ // In C++26, union default ctors are trivial unless user-provided
+ // (P3074R7 [class.default.ctor]p3). We use !isUserProvided() rather
+ // than isTrivial() because the triviality flag may not be set yet for
+ // explicitly defaulted ctors at the point DeclareImplicitDestructor
+ // runs during class completion.
+ CtorOK = !Ctor->isDeleted() && !Ctor->isUserProvided();
+ } else if (SMOR.getKind() ==
+ SpecialMemberOverloadResult::NoMemberOrDeleted) {
+ if (!SMOR.getMethod()) {
+ // No default constructor exists (e.g. suppressed by user-declared
+ // constructors, or no viable candidate for 0 args). The union
+ // cannot be default-initialized, so rule 7.x.1 does not apply.
+ CtorOK = true;
+ }
+ // else: a deleted default ctor was selected → CtorOK stays false.
+ }
+ // Ambiguous → CtorOK stays false.
+ if (!CtorOK) {
+ // 7.x.1: OR is ambiguous, selects a deleted ctor, or selects a
+ // non-trivial (user-provided) ctor.
+ if (Diagnose) {
+ unsigned Reason;
+ if (SMOR.getKind() == SpecialMemberOverloadResult::Ambiguous)
+ Reason = 2; // ambiguous
+ else if (SMOR.getKind() ==
+ SpecialMemberOverloadResult::NoMemberOrDeleted) {
+ auto *Ctor = SMOR.getMethod();
+ Reason = (Ctor && Ctor->isDeleted()) ? 1 : 0;
+ } else {
+ Reason = 3; // not trivial
+ }
+ Diag(RD->getLocation(), diag::note_deleted_dtor_default_ctor)
+ << RD << Reason;
+ }
+ return true;
+ }
+ // Ctor is OK. Check (7.x.2): walk variant members (including through
+ // anonymous structs) for DMI + non-trivial dtor.
+ std::function<bool(const CXXRecordDecl *)> HasDMINonTrivDtor =
+ [&](const CXXRecordDecl *Record) -> bool {
+ for (const auto *FD : Record->fields()) {
+ if (FD->hasInClassInitializer()) {
+ QualType FT = Context.getBaseElementType(FD->getType());
+ if (const auto *FR = FT->getAsCXXRecordDecl()) {
+ if (FR->hasNonTrivialDestructor()) {
+ if (Diagnose)
+ Diag(FD->getLocation(),
+ diag::note_deleted_special_member_class_subobject)
+ << getSpecialMember(MD) << RD << /*IsField*/ true << FD
+ << /*NonTrivialDecl*/ 4 << /*IsDtorCallInCtor*/ false
+ << /*IsObjCPtr*/ false;
+ return true;
+ }
+ }
+ }
+ if (FD->isAnonymousStructOrUnion()) {
+ if (const auto *SubRD = FD->getType()->getAsCXXRecordDecl()) {
+ if (HasDMINonTrivDtor(SubRD))
+ return true;
+ }
+ }
+ }
+ return false;
+ };
+ if (HasDMINonTrivDtor(RD))
+ return true;
+ // Neither 7.x.1 nor 7.x.2 triggered. Fall through to the normal
+ // per-member visit for p7.2 (deleted/inaccessible dtor) checking.
+ }
+
// Per DR1611, do not consider virtual bases of constructors of abstract
// classes, since we are not going to construct them.
// Per DR1658, do not consider virtual bases of destructors of abstract
@@ -10467,7 +10588,17 @@ bool Sema::SpecialMemberIsTrivial(CXXMethodDecl *MD, CXXSpecialMemberKind CSM,
// -- for all of the non-static data members of its class that are of class
// type (or array thereof), each such class has a trivial [default
// constructor or destructor]
- if (!checkTrivialClassMembers(*this, RD, CSM, ConstArg, TAH, Diagnose))
+ //
+ // P3074R7 [class.default.ctor]p3, [class.dtor]p8:
+ // In C++26, "either X is a union or" the above member checks apply.
+ // For unions, default constructor and destructor are trivial regardless
+ // of member triviality.
+ if (RD->isUnion() && getLangOpts().CPlusPlus26 &&
+ (CSM == CXXSpecialMemberKind::DefaultConstructor ||
+ CSM == CXXSpecialMemberKind::Destructor)) {
+ // Union default ctor and destructor are trivial in C++26 per P3074.
+ // Skip member triviality checks.
+ } else if (!checkTrivialClassMembers(*this, RD, CSM, ConstArg, TAH, Diagnose))
return false;
// C++11 [class.dtor]p5:
diff --git a/clang/test/CXX/drs/cwg14xx.cpp b/clang/test/CXX/drs/cwg14xx.cpp
index dba1850ce8df9..fa9b866d6bfcd 100644
--- a/clang/test/CXX/drs/cwg14xx.cpp
+++ b/clang/test/CXX/drs/cwg14xx.cpp
@@ -1,18 +1,18 @@
// RUN: %clang_cc1 -std=c++98 %s -verify=expected -fexceptions -fcxx-exceptions -pedantic-errors
-// RUN: %clang_cc1 -std=c++11 %s -verify=expected,cxx11-17,since-cxx11, -fexceptions -fcxx-exceptions -pedantic-errors
-// RUN: %clang_cc1 -std=c++14 %s -verify=expected,cxx14-17,cxx11-17,since-cxx11,since-cxx14 -fexceptions -fcxx-exceptions -pedantic-errors
-// RUN: %clang_cc1 -std=c++17 %s -verify=expected,cxx14-17,cxx11-17,since-cxx11,since-cxx14 -fexceptions -fcxx-exceptions -pedantic-errors
-// RUN: %clang_cc1 -std=c++20 %s -verify=expected,since-cxx11,since-cxx14,since-cxx20 -fexceptions -fcxx-exceptions -pedantic-errors
-// RUN: %clang_cc1 -std=c++23 %s -verify=expected,since-cxx11,since-cxx14,since-cxx20 -fexceptions -fcxx-exceptions -pedantic-errors
-// RUN: %clang_cc1 -std=c++2c %s -verify=expected,since-cxx11,since-cxx14,since-cxx20 -fexceptions -fcxx-exceptions -pedantic-errors
+// RUN: %clang_cc1 -std=c++11 %s -verify=expected,cxx11-17,since-cxx11,precxx26 -fexceptions -fcxx-exceptions -pedantic-errors
+// RUN: %clang_cc1 -std=c++14 %s -verify=expected,cxx14-17,cxx11-17,since-cxx11,since-cxx14,precxx26 -fexceptions -fcxx-exceptions -pedantic-errors
+// RUN: %clang_cc1 -std=c++17 %s -verify=expected,cxx14-17,cxx11-17,since-cxx11,since-cxx14,precxx26 -fexceptions -fcxx-exceptions -pedantic-errors
+// RUN: %clang_cc1 -std=c++20 %s -verify=expected,since-cxx11,since-cxx14,since-cxx20,precxx26 -fexceptions -fcxx-exceptions -pedantic-errors
+// RUN: %clang_cc1 -std=c++23 %s -verify=expected,since-cxx11,since-cxx14,since-cxx20,precxx26 -fexceptions -fcxx-exceptions -pedantic-errors
+// RUN: %clang_cc1 -std=c++2c %s -verify=expected,since-cxx11,since-cxx14,since-cxx20,since-cxx26 -fexceptions -fcxx-exceptions -pedantic-errors
// RUN: %clang_cc1 -std=c++98 %s -verify=expected -fexceptions -fcxx-exceptions -pedantic-errors -fexperimental-new-constant-interpreter
-// RUN: %clang_cc1 -std=c++11 %s -verify=expected,cxx11-17,since-cxx11, -fexceptions -fcxx-exceptions -pedantic-errors -fexperimental-new-constant-interpreter
-// RUN: %clang_cc1 -std=c++14 %s -verify=expected,cxx14-17,cxx11-17,since-cxx11,since-cxx14 -fexceptions -fcxx-exceptions -pedantic-errors -fexperimental-new-constant-interpreter
-// RUN: %clang_cc1 -std=c++17 %s -verify=expected,cxx14-17,cxx11-17,since-cxx11,since-cxx14 -fexceptions -fcxx-exceptions -pedantic-errors -fexperimental-new-constant-interpreter
-// RUN: %clang_cc1 -std=c++20 %s -verify=expected,since-cxx11,since-cxx14,since-cxx20 -fexceptions -fcxx-exceptions -pedantic-errors -fexperimental-new-constant-interpreter
-// RUN: %clang_cc1 -std=c++23 %s -verify=expected,since-cxx11,since-cxx14,since-cxx20 -fexceptions -fcxx-exceptions -pedantic-errors -fexperimental-new-constant-interpreter
-// RUN: %clang_cc1 -std=c++2c %s -verify=expected,since-cxx11,since-cxx14,since-cxx20 -fexceptions -fcxx-exceptions -pedantic-errors -fexperimental-new-constant-interpreter
+// RUN: %clang_cc1 -std=c++11 %s -verify=expected,cxx11-17,since-cxx11,precxx26 -fexceptions -fcxx-exceptions -pedantic-errors -fexperimental-new-constant-interpreter
+// RUN: %clang_cc1 -std=c++14 %s -verify=expected,cxx14-17,cxx11-17,since-cxx11,since-cxx14,precxx26 -fexceptions -fcxx-exceptions -pedantic-errors -fexperimental-new-constant-interpreter
+// RUN: %clang_cc1 -std=c++17 %s -verify=expected,cxx14-17,cxx11-17,since-cxx11,since-cxx14,precxx26 -fexceptions -fcxx-exceptions -pedantic-errors -fexperimental-new-constant-interpreter
+// RUN: %clang_cc1 -std=c++20 %s -verify=expected,since-cxx11,since-cxx14,since-cxx20,precxx26 -fexceptions -fcxx-exceptions -pedantic-errors -fexperimental-new-constant-interpreter
+// RUN: %clang_cc1 -std=c++23 %s -verify=expected,since-cxx11,since-cxx14,since-cxx20,precxx26 -fexceptions -fcxx-exceptions -pedantic-errors -fexperimental-new-constant-interpreter
+// RUN: %clang_cc1 -std=c++2c %s -verify=expected,since-cxx11,since-cxx14,since-cxx20,since-cxx26 -fexceptions -fcxx-exceptions -pedantic-errors -fexperimental-new-constant-interpreter
namespace cwg1413 { // cwg1413: 12
template<int> struct Check {
@@ -128,7 +128,7 @@ struct A {
namespace cwg1460 { // cwg1460: 3.5
#if __cplusplus >= 201103L
namespace DRExample {
- union A {
+ union A { // #cwg1460-DRExample-A
union {};
// since-cxx11-error at -1 {{declaration does not declare anything}}
union {};
@@ -136,6 +136,8 @@ namespace cwg1460 { // cwg1460: 3.5
constexpr A() {}
};
constexpr A a = A();
+ // since-cxx26-error at -1 {{attempt to use a deleted function}}
+ // since-cxx26-note@#cwg1460-DRExample-A {{destructor of union 'A' is implicitly deleted because its default constructor is not trivial}}
union B {
union {};
@@ -277,32 +279,42 @@ namespace cwg1460 { // cwg1460: 3.5
};
static_assert(A().a == 1 && A().b == 2 && A().c == 3, "");
- union B {
+ union B { // #cwg1460-Overriding-B
int a, b = 2, c;
constexpr B() : a(1) {}
constexpr B(char) : b(4) {}
constexpr B(int) : c(3) {}
constexpr B(const char*) {}
};
+ // since-cxx26-note@#cwg1460-Overriding-B 9 {{destructor of union 'B' is implicitly deleted because its default constructor is not trivial}}
static_assert(B().a == 1, "");
+ // since-cxx26-error at -1 {{attempt to use a deleted function}}
static_assert(B().b == 2, "");
- // since-cxx11-error at -1 {{static assertion expression is not an integral constant expression}}
- // since-cxx11-note at -2 {{read of member 'b' of union with active member 'a' is not allowed in a constant expression}}
+ // precxx26-error at -1 {{static assertion expression is not an integral constant expression}}
+ // precxx26-note at -2 {{read of member 'b' of union with active member 'a' is not allowed in a constant expression}}
+ // since-cxx26-error at -3 {{attempt to use a deleted function}}
static_assert(B('x').a == 0, "");
- // since-cxx11-error at -1 {{static assertion expression is not an integral constant expression}}
- // since-cxx11-note at -2 {{read of member 'a' of union with active member 'b' is not allowed in a constant expression}}
+ // precxx26-error at -1 {{static assertion expression is not an integral constant expression}}
+ // precxx26-note at -2 {{read of member 'a' of union with active member 'b' is not allowed in a constant expression}}
+ // since-cxx26-error at -3 {{attempt to use a deleted function}}
static_assert(B('x').b == 4, "");
+ // since-cxx26-error at -1 {{attempt to use a deleted function}}
static_assert(B(123).b == 2, "");
- // since-cxx11-error at -1 {{static assertion expression is not an integral constant expression}}
- // since-cxx11-note at -2 {{read of member 'b' of union with active member 'c' is not allowed in a constant expression}}
+ // precxx26-error at -1 {{static assertion expression is not an integral constant expression}}
+ // precxx26-note at -2 {{read of member 'b' of union with active member 'c' is not allowed in a constant expression}}
+ // since-cxx26-error at -3 {{attempt to use a deleted function}}
static_assert(B(123).c == 3, "");
+ // since-cxx26-error at -1 {{attempt to use a deleted function}}
static_assert(B("").a == 1, "");
- // since-cxx11-error at -1 {{static assertion expression is not an integral constant expression}}
- // since-cxx11-note at -2 {{read of member 'a' of union with active member 'b' is not allowed in a constant expression}}
+ // precxx26-error at -1 {{static assertion expression is not an integral constant expression}}
+ // precxx26-note at -2 {{read of member 'a' of union with active member 'b' is not allowed in a constant expression}}
+ // since-cxx26-error at -3 {{attempt to use a deleted function}}
static_assert(B("").b == 2, "");
+ // since-cxx26-error at -1 {{attempt to use a deleted function}}
static_assert(B("").c == 3, "");
- // since-cxx11-error at -1 {{static assertion expression is not an integral constant expression}}
- // since-cxx11-note at -2 {{read of member 'c' of union with active member 'b' is not allowed in a constant expression}}
+ // precxx26-error at -1 {{static assertion expression is not an integral constant expression}}
+ // precxx26-note at -2 {{read of member 'c' of union with active member 'b' is not allowed in a constant expression}}
+ // since-cxx26-error at -3 {{attempt to use a deleted function}}
struct C {
union { int a, b = 2, c; };
diff --git a/clang/test/CXX/special/class.ctor/p5-0x.cpp b/clang/test/CXX/special/class.ctor/p5-0x.cpp
index e0c53058f892b..f00ec4ddc6350 100644
--- a/clang/test/CXX/special/class.ctor/p5-0x.cpp
+++ b/clang/test/CXX/special/class.ctor/p5-0x.cpp
@@ -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; };
@@ -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; };
@@ -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;
diff --git a/clang/test/CXX/special/class.ctor/p6-0x.cpp b/clang/test/CXX/special/class.ctor/p6-0x.cpp
index 156a2b20c6b52..474e7aad91207 100644
--- a/clang/test/CXX/special/class.ctor/p6-0x.cpp
+++ b/clang/test/CXX/special/class.ctor/p6-0x.cpp
@@ -1,11 +1,12 @@
-// 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.
-struct NonConstexpr1 { // expected-note {{here}}
+struct NonConstexpr1 { // cxx11-23-note {{here}} cxx26-note {{previous declaration is here}}
int a;
};
-struct NonConstexpr2 { // expected-note {{here}}
+struct NonConstexpr2 { // cxx11-23-note {{here}} cxx26-note {{previous declaration is here}}
NonConstexpr1 nl;
};
struct NonConstexpr2a : NonConstexpr1 { };
@@ -15,8 +16,8 @@ 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}}
+ friend constexpr NonConstexpr2::NonConstexpr2(); // cxx11-23-error {{follows non-constexpr declaration}} cxx26-error {{missing exception specification}}
};
struct Constexpr1 {};
@@ -31,14 +32,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;
@@ -53,11 +54,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;
};
@@ -79,7 +80,7 @@ namespace UnionCtors {
int d = 5;
};
};
- struct E { // expected-note {{here}}
+ struct E { // cxx11-23-note {{here}}
union {
int a;
int b;
@@ -87,11 +88,11 @@ namespace UnionCtors {
};
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}}
};
}
@@ -122,6 +123,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}}
}
diff --git a/clang/test/CXX/special/class.dtor/p5-0x.cpp b/clang/test/CXX/special/class.dtor/p5-0x.cpp
index ae14dcdaf102a..286e980710009 100644
--- a/clang/test/CXX/special/class.dtor/p5-0x.cpp
+++ b/clang/test/CXX/special/class.dtor/p5-0x.cpp
@@ -1,10 +1,11 @@
-// RUN: %clang_cc1 -verify -std=c++11 %s -Wno-defaulted-function-deleted -triple x86_64-linux-gnu
+// RUN: %clang_cc1 -verify=expected,precxx26 -std=c++11 %s -Wno-defaulted-function-deleted -triple x86_64-linux-gnu
+// RUN: %clang_cc1 -verify=expected,cxx26 -std=c++26 %s -Wno-defaulted-function-deleted -triple x86_64-linux-gnu
struct NonTrivDtor {
~NonTrivDtor();
};
struct DeletedDtor {
- ~DeletedDtor() = delete; // expected-note 5 {{deleted here}}
+ ~DeletedDtor() = delete; // expected-note 4 {{deleted here}} precxx26-note {{deleted here}}
};
class InaccessibleDtor {
~InaccessibleDtor() = default;
@@ -14,30 +15,30 @@ class InaccessibleDtor {
// -- X is a union-like class that has a variant member with a non-trivial
// destructor.
-union A1 {
+union A1 { // cxx26-note {{not trivial}}
A1();
- NonTrivDtor n; // expected-note {{destructor of 'A1' is implicitly deleted because variant field 'n' has a non-trivial destructor}}
+ NonTrivDtor n; // precxx26-note {{destructor of 'A1' is implicitly deleted because variant field 'n' has a non-trivial destructor}}
};
A1 a1; // expected-error {{deleted function}}
struct A2 {
A2();
union {
- NonTrivDtor n; // expected-note {{because variant field 'n' has a non-trivial destructor}}
+ NonTrivDtor n; // precxx26-note {{because variant field 'n' has a non-trivial destructor}}
};
};
-A2 a2; // expected-error {{deleted function}}
-union A3 {
+A2 a2; // precxx26-error {{deleted function}}
+union A3 { // cxx26-note {{not trivial}}
A3();
- NonTrivDtor n[3]; // expected-note {{because variant field 'n' has a non-trivial destructor}}
+ NonTrivDtor n[3]; // precxx26-note {{because variant field 'n' has a non-trivial destructor}}
};
A3 a3; // expected-error {{deleted function}}
struct A4 {
A4();
union {
- NonTrivDtor n[3]; // expected-note {{because variant field 'n' has a non-trivial destructor}}
+ NonTrivDtor n[3]; // precxx26-note {{because variant field 'n' has a non-trivial destructor}}
};
};
-A4 a4; // expected-error {{deleted function}}
+A4 a4; // precxx26-error {{deleted function}}
// -- any of the non-static data members has class type M (or array thereof) and
// M has a deleted or inaccessible destructor.
@@ -61,17 +62,17 @@ struct B4 {
InaccessibleDtor a[4]; // expected-note {{because field 'a' has an inaccessible destructor}}
};
B4 b4; // expected-error {{deleted function}}
-union B5 {
+union B5 { // cxx26-note {{not trivial}}
B5();
- union { // expected-note-re {{because field 'B5::(anonymous union at {{.+}})' has a deleted destructor}}
- DeletedDtor a; // expected-note {{because field 'a' has a deleted destructor}}
+ union { // precxx26-note-re {{because field 'B5::(anonymous union at {{.+}})' has a deleted destructor}}
+ DeletedDtor a; // precxx26-note {{because field 'a' has a deleted destructor}}
};
};
B5 b5; // expected-error {{deleted function}}
-union B6 {
+union B6 { // cxx26-note {{not trivial}}
B6();
- union { // expected-note-re {{because field 'B6::(anonymous union at {{.+}})' has a deleted destructor}}
- InaccessibleDtor a; // expected-note {{because field 'a' has an inaccessible destructor}}
+ union { // precxx26-note-re {{because field 'B6::(anonymous union at {{.+}})' has a deleted destructor}}
+ InaccessibleDtor a; // precxx26-note {{because field 'a' has an inaccessible destructor}}
};
};
B6 b6; // expected-error {{deleted function}}
diff --git a/clang/test/SemaCXX/cxx26-trivial-union.cpp b/clang/test/SemaCXX/cxx26-trivial-union.cpp
new file mode 100644
index 0000000000000..d0a49776781ba
--- /dev/null
+++ b/clang/test/SemaCXX/cxx26-trivial-union.cpp
@@ -0,0 +1,251 @@
+// RUN: %clang_cc1 -std=c++26 -fsyntax-only -verify=cxx26 %s
+// RUN: %clang_cc1 -std=c++23 -fsyntax-only -verify=precxx26 %s
+
+// P3074R7: trivial unions
+
+struct NonTrivial {
+ NonTrivial();
+ NonTrivial(const NonTrivial&);
+ NonTrivial& operator=(const NonTrivial&);
+ ~NonTrivial();
+};
+
+struct NonTrivialDtor {
+ ~NonTrivialDtor();
+};
+
+// ===== Test 1: Basic union with non-trivial member =====
+// P3074: default ctor and dtor should be trivial, not deleted.
+union U1 {
+ NonTrivial nt; // precxx26-note 2{{non-trivial}}
+};
+
+#if __cplusplus > 202302L
+static_assert(__is_trivially_constructible(U1));
+static_assert(__is_trivially_destructible(U1));
+U1 test_u1;
+#else
+U1 test_u1_pre; // precxx26-error {{deleted}}
+void destroy_u1(U1 *p) { p->~U1(); } // precxx26-error {{deleted}}
+#endif
+
+// ===== Test 2: Union with non-trivial member and int =====
+union U2 {
+ NonTrivial nt;
+ int k;
+};
+
+#if __cplusplus > 202302L
+static_assert(__is_trivially_constructible(U2));
+static_assert(__is_trivially_destructible(U2));
+#endif
+
+// ===== Test 3: Union with DMI on member with non-trivial dtor =====
+// P3074: dtor is deleted because DMI + non-trivial dtor on same member.
+union U3_deleted_dtor {
+ NonTrivialDtor ntd = {}; // cxx26-note {{non-trivial}} precxx26-note {{non-trivial}}
+};
+
+void test_u3_destroy(U3_deleted_dtor *p) {
+ p->~U3_deleted_dtor(); // cxx26-error {{deleted}} precxx26-error {{deleted}}
+}
+
+// ===== Test 4: Union with DMI on non-class member =====
+// DMI on int, but NonTrivial has no DMI => dtor should NOT be deleted.
+union U4 {
+ NonTrivial nt; // precxx26-note {{non-trivial}}
+ int k = 42;
+};
+
+#if __cplusplus > 202302L
+// Despite non-trivial default ctor (due to DMI), destructor is NOT deleted
+// because the member with DMI (k) is int (trivially destructible).
+static_assert(__is_trivially_destructible(U4));
+#else
+void destroy_u4(U4 *p) { p->~U4(); } // precxx26-error {{deleted}}
+#endif
+
+// ===== Test 5: Union with user-provided default constructor =====
+// P3074 (7.x.1): user-provided ctor is non-trivial => dtor is deleted.
+union U5 { // cxx26-note {{not trivial}}
+ U5() : nt() {}
+ NonTrivialDtor nt; // precxx26-note {{non-trivial}}
+};
+
+void test_u5_destroy(U5 *p) { p->~U5(); } // cxx26-error {{deleted}} precxx26-error {{deleted}}
+
+// ===== Test 6: Feature test macro =====
+#if __cplusplus > 202302L
+static_assert(__cpp_trivial_union >= 202502L);
+#else
+#ifdef __cpp_trivial_union
+#error "should not have __cpp_trivial_union in C++23"
+#endif
+#endif
+
+// ===== Test 7: Trivial union (no change from status quo) =====
+union U7 {
+ int a;
+ float b;
+};
+
+static_assert(__is_trivially_constructible(U7));
+static_assert(__is_trivially_destructible(U7));
+
+// ===== Test 8: Array member in union =====
+union U8 {
+ NonTrivial arr[4];
+};
+
+#if __cplusplus > 202302L
+static_assert(__is_trivially_constructible(U8));
+static_assert(__is_trivially_destructible(U8));
+#endif
+
+// ===== Test 9: Paper example - string with DMI =====
+struct FakeString {
+ FakeString(const char*);
+ FakeString(const FakeString&);
+ FakeString& operator=(const FakeString&);
+ ~FakeString();
+};
+
+union PaperU2 {
+ FakeString s = "hello"; // cxx26-note {{non-trivial}} precxx26-note {{non-trivial}}
+};
+
+void test_paper_u2(PaperU2 *p) {
+ p->~PaperU2(); // cxx26-error {{deleted}} precxx26-error {{deleted}}
+}
+
+// ===== Test 10: Paper example U4 - DMI on pointer, non-trivial string =====
+union PaperU4 {
+ FakeString s; // precxx26-note {{non-trivial}}
+ PaperU4 *next = nullptr;
+};
+
+#if __cplusplus > 202302L
+static_assert(__is_trivially_destructible(PaperU4));
+#else
+void destroy_paper_u4(PaperU4 *p) { p->~PaperU4(); } // precxx26-error {{deleted}}
+#endif
+
+// ===== Test 11: No default ctor (suppressed by user-declared ctor) =====
+// When the implicit default ctor is suppressed (not deleted), rule 7.x.1
+// does not apply. The union can't be default-initialized, so no risk of
+// indeterminate active member. Destructor is trivial per P3074.
+#if __cplusplus > 202302L
+union U11 {
+ U11(int);
+ NonTrivialDtor nt;
+};
+static_assert(__is_trivially_destructible(U11));
+U11 u11(1);
+
+// ===== Test 12: Deleted default ctor (7.x.1: selects deleted) =====
+union U12 { // cxx26-note {{deleted function}}
+ U12() = delete;
+ U12(int);
+ NonTrivialDtor nt;
+};
+U12 u12(1); // cxx26-error {{deleted}}
+
+// ===== Test 13: Defaulted ctor => trivial, dtor NOT deleted =====
+union U13 {
+ U13() = default;
+ NonTrivialDtor nt;
+ U13 *next = nullptr;
+};
+U13 u13;
+
+// ===== Test 14: Array member with DMI + non-trivial dtor (7.x.2) =====
+struct NonTrivialInt {
+ int i;
+ constexpr NonTrivialInt(int i) : i(i) {}
+ constexpr ~NonTrivialInt() {}
+};
+
+union U14 {
+ NonTrivialInt arr[2] = {1, 2}; // cxx26-note {{non-trivial}}
+};
+U14 u14; // cxx26-error {{deleted}}
+
+// ===== Test 15: Anonymous struct in union, no DMI => NOT deleted =====
+union U15 {
+ struct {
+ NonTrivialDtor x;
+ };
+};
+U15 u15;
+
+// ===== Test 16: Anonymous struct in union, with DMI => deleted =====
+union U16 {
+ struct {
+ NonTrivialInt x = 1; // cxx26-note {{non-trivial}}
+ };
+};
+U16 u16; // cxx26-error {{deleted}}
+
+// ===== Test 17: struct containing anonymous union =====
+struct S17 {
+ union {
+ NonTrivialDtor x;
+ };
+};
+S17 s17;
+
+// ===== Test 18: Deeply nested union-struct-union-struct-union =====
+union U18 {
+ struct {
+ union {
+ struct {
+ union {
+ NonTrivialDtor x;
+ };
+ };
+ };
+ };
+};
+U18 u18;
+
+// ===== Test 19: struct-union-struct-union-struct nesting =====
+struct S19 {
+ union {
+ struct {
+ union {
+ struct {
+ NonTrivialDtor x;
+ };
+ };
+ };
+ };
+};
+S19 s19;
+
+// ===== Test 20: Anonymous union inside union =====
+union U20 {
+ union {
+ NonTrivialDtor x;
+ };
+};
+U20 u20;
+
+// ===== Test 21: Deleted destructor member (p7.2, not union-specific) =====
+// p7.2 still applies: deleted/inaccessible member dtor => union dtor deleted.
+struct DeletedDtor {
+ ~DeletedDtor() = delete; // cxx26-note {{deleted here}} precxx26-note {{deleted here}}
+};
+
+union U21 {
+ DeletedDtor a; // cxx26-note {{deleted destructor}} precxx26-note {{deleted destructor}}
+};
+void test_u21(U21 *p) { p->~U21(); } // cxx26-error {{deleted}} precxx26-error {{deleted}}
+
+// ===== Test 22: Constexpr evaluation of trivial union =====
+constexpr int constexpr_test() {
+ U2 u;
+ u.k = 42;
+ return u.k;
+}
+static_assert(constexpr_test() == 42);
+#endif
More information about the cfe-commits
mailing list