[clang] [Clang]P3726R2: Adjustments to Union Lifetime Rules (PR #185830)
Peng Xie via cfe-commits
cfe-commits at lists.llvm.org
Thu Apr 16 00:51:37 PDT 2026
https://github.com/love1angel updated https://github.com/llvm/llvm-project/pull/185830
>From 6b435c9b2296217570596c015ca17dd10520cd80 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 1/9] [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/AST/CXXRecordDeclDefinitionBits.def | 5 +
clang/include/clang/AST/DeclCXX.h | 7 +
.../clang/Basic/DiagnosticSemaKinds.td | 6 +
clang/lib/AST/DeclCXX.cpp | 46 +++-
clang/lib/Frontend/InitPreprocessor.cpp | 4 +
clang/lib/Sema/SemaDeclCXX.cpp | 112 +++++++-
clang/test/CXX/drs/cwg14xx.cpp | 68 +++--
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 | 253 ++++++++++++++++++
11 files changed, 512 insertions(+), 69 deletions(-)
create mode 100644 clang/test/SemaCXX/cxx26-trivial-union.cpp
diff --git a/clang/include/clang/AST/CXXRecordDeclDefinitionBits.def b/clang/include/clang/AST/CXXRecordDeclDefinitionBits.def
index 7e6e2147a448d..0fd4a86de6f5a 100644
--- a/clang/include/clang/AST/CXXRecordDeclDefinitionBits.def
+++ b/clang/include/clang/AST/CXXRecordDeclDefinitionBits.def
@@ -119,6 +119,11 @@ FIELD(HasInitMethod, 1, NO_MERGE)
/// within anonymous unions or structs.
FIELD(HasInClassInitializer, 1, NO_MERGE)
+/// True if any field with an in-class initializer has a type (or base
+/// element type) with a non-trivial destructor. Used for C++26 union
+/// destructor deletion check ([class.dtor]p7 bullet 2).
+FIELD(HasDMIWithNonTrivialDtor, 1, NO_MERGE)
+
/// True if any field is of reference type, and does not have an
/// in-class initializer.
///
diff --git a/clang/include/clang/AST/DeclCXX.h b/clang/include/clang/AST/DeclCXX.h
index 2af396f025c93..912691683fcb7 100644
--- a/clang/include/clang/AST/DeclCXX.h
+++ b/clang/include/clang/AST/DeclCXX.h
@@ -1147,6 +1147,13 @@ class CXXRecordDecl : public RecordDecl {
/// structs).
bool hasInClassInitializer() const { return data().HasInClassInitializer; }
+ /// Whether this class has any in-class initializer for a non-static data
+ /// member whose type (or base element type) has a non-trivial destructor.
+ /// Used for C++26 union destructor deletion ([class.dtor]p7 bullet 2).
+ bool hasDMIWithNonTrivialDtor() const {
+ return data().HasDMIWithNonTrivialDtor;
+ }
+
/// Whether this class or any of its subobjects has any members of
/// reference type which would make value-initialization ill-formed.
///
diff --git a/clang/include/clang/Basic/DiagnosticSemaKinds.td b/clang/include/clang/Basic/DiagnosticSemaKinds.td
index 6d2fae551566f..a6f9a4d92f511 100644
--- a/clang/include/clang/Basic/DiagnosticSemaKinds.td
+++ b/clang/include/clang/Basic/DiagnosticSemaKinds.td
@@ -6429,6 +6429,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{"
+ "%select{it has no default constructor|its default constructor is a deleted function}2|"
+ "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 3b9d888bb2c0a..32ae2131beae1 100644
--- a/clang/lib/AST/DeclCXX.cpp
+++ b/clang/lib/AST/DeclCXX.cpp
@@ -81,6 +81,7 @@ CXXRecordDecl::DefinitionData::DefinitionData(CXXRecordDecl *D)
HasPrivateFields(false), HasProtectedFields(false),
HasPublicFields(false), HasMutableFields(false), HasVariantMembers(false),
HasOnlyCMembers(true), HasInitMethod(false), HasInClassInitializer(false),
+ HasDMIWithNonTrivialDtor(false),
HasUninitializedReferenceMember(false), HasUninitializedFields(false),
HasInheritedConstructor(false), HasInheritedDefaultConstructor(false),
HasInheritedAssignment(false),
@@ -1177,10 +1178,29 @@ void CXXRecordDecl::addedMember(Decl *D) {
Field->getType()->getAsCXXRecordDecl()->hasInClassInitializer())) {
data().HasInClassInitializer = true;
+ // C++26 [class.dtor]p7 bullet 2: track whether any field with a DMI
+ // has a type with a non-trivial destructor, for union dtor deletion.
+ if (Field->hasInClassInitializer()) {
+ QualType FT =
+ getASTContext().getBaseElementType(Field->getType());
+ if (const auto *FR = FT->getAsCXXRecordDecl();
+ FR && FR->hasNonTrivialDestructor())
+ data().HasDMIWithNonTrivialDtor = true;
+ } else if (Field->isAnonymousStructOrUnion()) {
+ if (Field->getType()->getAsCXXRecordDecl()->data()
+ .HasDMIWithNonTrivialDtor)
+ data().HasDMIWithNonTrivialDtor = true;
+ }
+
// 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;
+ // C++26 [class.default.ctor]p3:
+ // A default constructor is trivial if [...] either X is a union or
+ // no non-static data member of its class has a
+ // brace-or-equal-initializer.
+ if (!(isUnion() && getASTContext().getLangOpts().CPlusPlus26))
+ data().HasTrivialSpecialMembers &= ~SMF_DefaultConstructor;
// C++11 [dcl.init.aggr]p1:
// An aggregate is a [...] class with [...] no
@@ -1239,7 +1259,11 @@ void CXXRecordDecl::addedMember(Decl *D) {
if (FieldRec->hasNonTrivialMoveAssignment())
data().DefaultedMoveAssignmentIsDeleted = true;
if (FieldRec->hasNonTrivialDestructor()) {
- data().DefaultedDestructorIsDeleted = true;
+ // 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 (see C++26 [class.dtor]p7).
+ 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 +1291,13 @@ 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())
+ // C++26 [class.default.ctor]p3:
+ // A default constructor is trivial if [...] either X is a union
+ // or for all the non-static data members of its class that are
+ // of class type [...] 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 +1335,15 @@ void CXXRecordDecl::addedMember(Decl *D) {
if (!FieldRec->hasTrivialMoveAssignment())
data().HasTrivialSpecialMembers &= ~SMF_MoveAssignment;
- if (!FieldRec->hasTrivialDestructor())
+ // C++26 [class.dtor]p8:
+ // A destructor is trivial if [...] either X is a union or for
+ // all the non-static data members of its class that are of class
+ // type [...] 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 c1d3960e65ef6..50659188c4244 100644
--- a/clang/lib/Sema/SemaDeclCXX.cpp
+++ b/clang/lib/Sema/SemaDeclCXX.cpp
@@ -9631,6 +9631,11 @@ bool SpecialMemberDeletionInfo::shouldDeleteForSubobjectCall(
if (SMOR.getKind() == Sema::SpecialMemberOverloadResult::NoMemberOrDeleted) {
if (CSM == CXXSpecialMemberKind::DefaultConstructor && Field &&
Field->getParent()->isUnion()) {
+ // In C++26, a union's defaulted default constructor is never
+ // deleted due to a variant member with a non-trivial default
+ // constructor (see C++26 [class.default.ctor]p3).
+ 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
@@ -9653,6 +9658,11 @@ bool SpecialMemberDeletionInfo::shouldDeleteForSubobjectCall(
// destructor is never actually called, but is semantically checked as
// if it were.
if (CSM == CXXSpecialMemberKind::DefaultConstructor) {
+ // In C++26, a union's defaulted default constructor is never
+ // deleted due to a variant member with a non-trivial default
+ // constructor (see C++26 [class.default.ctor]p3).
+ 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
@@ -9661,6 +9671,12 @@ bool SpecialMemberDeletionInfo::shouldDeleteForSubobjectCall(
const auto *RD = cast<CXXRecordDecl>(Field->getParent());
if (!RD->hasInClassInitializer())
DiagKind = NonTrivialDecl;
+ } else if (CSM == CXXSpecialMemberKind::Destructor &&
+ S.getLangOpts().CPlusPlus26) {
+ // 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 (see C++26 [class.dtor]p7).
+ return false;
} else {
DiagKind = NonTrivialDecl;
}
@@ -9822,6 +9838,13 @@ bool SpecialMemberDeletionInfo::shouldDeleteForField(FieldDecl *FD) {
if (inUnion() && shouldDeleteForVariantPtrAuthMember(FD))
return true;
+ // In C++26, a union's defaulted default constructor is trivially defined
+ // and never deleted due to variant member properties
+ // (see C++26 [class.default.ctor]p3).
+ 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.
@@ -9900,7 +9923,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)
@@ -9930,6 +9954,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()))
@@ -9945,6 +9971,72 @@ bool SpecialMemberDeletionInfo::shouldDeleteForAllConstMembers() {
return false;
}
+/// C++26 [class.dtor]p7: Check union-specific destructor deletion rules.
+/// Returns true if the destructor should be deleted.
+static bool ShouldDeleteUnionDestructorInCpp26(Sema &S, CXXRecordDecl *RD,
+ CXXMethodDecl *MD,
+ bool Diagnose) {
+ // Bullet 1: overload resolution for default initialization.
+ Sema::SpecialMemberOverloadResult SMOR = S.LookupSpecialMember(
+ RD, CXXSpecialMemberKind::DefaultConstructor,
+ /*ConstArg=*/false, /*VolatileArg=*/false, /*RValueThis=*/false,
+ /*ConstThis=*/false, /*VolatileThis=*/false);
+ bool CtorOK = false;
+ if (SMOR.getKind() == Sema::SpecialMemberOverloadResult::Success) {
+ auto *Ctor = cast<CXXConstructorDecl>(SMOR.getMethod());
+ // Use !isUserProvided() rather than isTrivial() because the triviality
+ // flag may not be set yet at the point DeclareImplicitDestructor runs.
+ CtorOK = !Ctor->isDeleted() && !Ctor->isUserProvided();
+ } else if (SMOR.getKind() ==
+ Sema::SpecialMemberOverloadResult::NoMemberOrDeleted &&
+ !SMOR.getMethod()) {
+ CtorOK = true;
+ }
+ if (!CtorOK) {
+ if (Diagnose) {
+ auto *Ctor = SMOR.getMethod();
+ S.Diag(RD->getLocation(), diag::note_deleted_dtor_default_ctor)
+ << RD << SMOR.getKind() << (Ctor && Ctor->isDeleted());
+ }
+ return true;
+ }
+
+ // Bullet 2: variant member with DMI and non-trivial dtor.
+ if (!RD->hasDMIWithNonTrivialDtor())
+ return false;
+
+ if (!Diagnose)
+ return true;
+
+ // Walk fields to find the specific field for the diagnostic.
+ auto FindDMIWithNonTrivialDtor = [&](const CXXRecordDecl *Record,
+ auto &Self) -> bool {
+ for (const FieldDecl *FD : Record->fields()) {
+ if (FD->hasInClassInitializer()) {
+ QualType FT = S.Context.getBaseElementType(FD->getType());
+ if (const auto *FR = FT->getAsCXXRecordDecl();
+ FR && FR->hasNonTrivialDestructor()) {
+ S.Diag(FD->getLocation(),
+ diag::note_deleted_special_member_class_subobject)
+ << S.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 (Self(SubRD, Self))
+ return true;
+ }
+ }
+ }
+ return false;
+ };
+ FindDMIWithNonTrivialDtor(RD, FindDMIWithNonTrivialDtor);
+ return true;
+}
+
/// Determine whether a defaulted special member function should be defined as
/// deleted, as specified in C++11 [class.ctor]p5, C++11 [class.copy]p11,
/// C++11 [class.copy]p23, and C++11 [class.dtor]p5.
@@ -10050,6 +10142,13 @@ bool Sema::ShouldDeleteSpecialMember(CXXMethodDecl *MD,
SpecialMemberDeletionInfo SMI(*this, MD, CSM, ICI, Diagnose);
+ // C++26 [class.dtor]p7: union-specific destructor deletion rules.
+ if (getLangOpts().CPlusPlus26 && RD->isUnion() &&
+ CSM == CXXSpecialMemberKind::Destructor) {
+ if (ShouldDeleteUnionDestructorInCpp26(*this, RD, MD, Diagnose))
+ return true;
+ }
+
// 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
@@ -10483,7 +10582,16 @@ 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))
+ //
+ // C++26 [class.default.ctor]p3, [class.dtor]p8:
+ // A default constructor [destructor] is trivial if [...] either X is
+ // a union or for all the non-static data members [...] each such class
+ // has a trivial default constructor [destructor].
+ if (RD->isUnion() && getLangOpts().CPlusPlus26 &&
+ (CSM == CXXSpecialMemberKind::DefaultConstructor ||
+ CSM == CXXSpecialMemberKind::Destructor)) {
+ // Skip member triviality checks for unions in C++26.
+ } 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 afa4bc9d8179a..4db8e38d9a5e5 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 -fexceptions -fcxx-exceptions -pedantic-errors -verify-directives -verify=expected
-// RUN: %clang_cc1 -std=c++11 %s -fexceptions -fcxx-exceptions -pedantic-errors -verify-directives -verify=expected,cxx11-17,since-cxx11
-// RUN: %clang_cc1 -std=c++14 %s -fexceptions -fcxx-exceptions -pedantic-errors -verify-directives -verify=expected,cxx14-17,cxx11-17,since-cxx11,since-cxx14
-// RUN: %clang_cc1 -std=c++17 %s -fexceptions -fcxx-exceptions -pedantic-errors -verify-directives -verify=expected,cxx14-17,cxx11-17,since-cxx11,since-cxx14
-// RUN: %clang_cc1 -std=c++20 %s -fexceptions -fcxx-exceptions -pedantic-errors -verify-directives -verify=expected,since-cxx11,since-cxx14,since-cxx20
-// RUN: %clang_cc1 -std=c++23 %s -fexceptions -fcxx-exceptions -pedantic-errors -verify-directives -verify=expected,since-cxx11,since-cxx14,since-cxx20
-// RUN: %clang_cc1 -std=c++2c %s -fexceptions -fcxx-exceptions -pedantic-errors -verify-directives -verify=expected,since-cxx11,since-cxx14,since-cxx20
+// RUN: %clang_cc1 -std=c++11 %s -fexceptions -fcxx-exceptions -pedantic-errors -verify-directives -verify=expected,cxx11-17,since-cxx11,precxx26
+// RUN: %clang_cc1 -std=c++14 %s -fexceptions -fcxx-exceptions -pedantic-errors -verify-directives -verify=expected,cxx14-17,cxx11-17,since-cxx11,since-cxx14,precxx26
+// RUN: %clang_cc1 -std=c++17 %s -fexceptions -fcxx-exceptions -pedantic-errors -verify-directives -verify=expected,cxx14-17,cxx11-17,since-cxx11,since-cxx14,precxx26
+// RUN: %clang_cc1 -std=c++20 %s -fexceptions -fcxx-exceptions -pedantic-errors -verify-directives -verify=expected,since-cxx11,since-cxx14,since-cxx20,precxx26
+// RUN: %clang_cc1 -std=c++23 %s -fexceptions -fcxx-exceptions -pedantic-errors -verify-directives -verify=expected,since-cxx11,since-cxx14,since-cxx20,precxx26
+// RUN: %clang_cc1 -std=c++2c %s -fexceptions -fcxx-exceptions -pedantic-errors -verify-directives -verify=expected,since-cxx11,since-cxx14,since-cxx20,since-cxx26
// RUN: %clang_cc1 -std=c++98 %s -fexceptions -fcxx-exceptions -pedantic-errors -fexperimental-new-constant-interpreter -verify-directives -verify=expected
-// RUN: %clang_cc1 -std=c++11 %s -fexceptions -fcxx-exceptions -pedantic-errors -fexperimental-new-constant-interpreter -verify-directives -verify=expected,cxx11-17,since-cxx11
-// RUN: %clang_cc1 -std=c++14 %s -fexceptions -fcxx-exceptions -pedantic-errors -fexperimental-new-constant-interpreter -verify-directives -verify=expected,cxx14-17,cxx11-17,since-cxx11,since-cxx14
-// RUN: %clang_cc1 -std=c++17 %s -fexceptions -fcxx-exceptions -pedantic-errors -fexperimental-new-constant-interpreter -verify-directives -verify=expected,cxx14-17,cxx11-17,since-cxx11,since-cxx14
-// RUN: %clang_cc1 -std=c++20 %s -fexceptions -fcxx-exceptions -pedantic-errors -fexperimental-new-constant-interpreter -verify-directives -verify=expected,since-cxx11,since-cxx14,since-cxx20
-// RUN: %clang_cc1 -std=c++23 %s -fexceptions -fcxx-exceptions -pedantic-errors -fexperimental-new-constant-interpreter -verify-directives -verify=expected,since-cxx11,since-cxx14,since-cxx20
-// RUN: %clang_cc1 -std=c++2c %s -fexceptions -fcxx-exceptions -pedantic-errors -fexperimental-new-constant-interpreter -verify-directives -verify=expected,since-cxx11,since-cxx14,since-cxx20
+// RUN: %clang_cc1 -std=c++11 %s -fexceptions -fcxx-exceptions -pedantic-errors -fexperimental-new-constant-interpreter -verify-directives -verify=expected,cxx11-17,since-cxx11,precxx26
+// RUN: %clang_cc1 -std=c++14 %s -fexceptions -fcxx-exceptions -pedantic-errors -fexperimental-new-constant-interpreter -verify-directives -verify=expected,cxx14-17,cxx11-17,since-cxx11,since-cxx14,precxx26
+// RUN: %clang_cc1 -std=c++17 %s -fexceptions -fcxx-exceptions -pedantic-errors -fexperimental-new-constant-interpreter -verify-directives -verify=expected,cxx14-17,cxx11-17,since-cxx11,since-cxx14,precxx26
+// RUN: %clang_cc1 -std=c++20 %s -fexceptions -fcxx-exceptions -pedantic-errors -fexperimental-new-constant-interpreter -verify-directives -verify=expected,since-cxx11,since-cxx14,since-cxx20,precxx26
+// RUN: %clang_cc1 -std=c++23 %s -fexceptions -fcxx-exceptions -pedantic-errors -fexperimental-new-constant-interpreter -verify-directives -verify=expected,since-cxx11,since-cxx14,since-cxx20,precxx26
+// RUN: %clang_cc1 -std=c++2c %s -fexceptions -fcxx-exceptions -pedantic-errors -fexperimental-new-constant-interpreter -verify-directives -verify=expected,since-cxx11,since-cxx14,since-cxx20,since-cxx26
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,7 +279,7 @@ 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) {}
@@ -285,24 +287,42 @@ namespace cwg1460 { // cwg1460: 3.5
constexpr B(const char*) {}
};
static_assert(B().a == 1, "");
+ // since-cxx26-error at -1 {{attempt to use a deleted function}}
+ // since-cxx26-note@#cwg1460-Overriding-B {{destructor of union 'B' is implicitly deleted because its default constructor is not trivial}}
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}}
+ // since-cxx26-note@#cwg1460-Overriding-B {{destructor of union 'B' is implicitly deleted because its default constructor is not trivial}}
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}}
+ // since-cxx26-note@#cwg1460-Overriding-B {{destructor of union 'B' is implicitly deleted because its default constructor is not trivial}}
static_assert(B('x').b == 4, "");
+ // since-cxx26-error at -1 {{attempt to use a deleted function}}
+ // since-cxx26-note@#cwg1460-Overriding-B {{destructor of union 'B' is implicitly deleted because its default constructor is not trivial}}
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}}
+ // since-cxx26-note@#cwg1460-Overriding-B {{destructor of union 'B' is implicitly deleted because its default constructor is not trivial}}
static_assert(B(123).c == 3, "");
+ // since-cxx26-error at -1 {{attempt to use a deleted function}}
+ // since-cxx26-note@#cwg1460-Overriding-B {{destructor of union 'B' is implicitly deleted because its default constructor is not trivial}}
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}}
+ // since-cxx26-note@#cwg1460-Overriding-B {{destructor of union 'B' is implicitly deleted because its default constructor is not trivial}}
static_assert(B("").b == 2, "");
+ // since-cxx26-error at -1 {{attempt to use a deleted function}}
+ // since-cxx26-note@#cwg1460-Overriding-B {{destructor of union 'B' is implicitly deleted because its default constructor is not trivial}}
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}}
+ // since-cxx26-note@#cwg1460-Overriding-B {{destructor of union 'B' is implicitly deleted because its default constructor is not trivial}}
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..8265c4f4d7670
--- /dev/null
+++ b/clang/test/SemaCXX/cxx26-trivial-union.cpp
@@ -0,0 +1,253 @@
+// 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 =====
+// Dtor is deleted: user-provided default ctor is non-trivial
+// (see C++26 [class.dtor]p7).
+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), the
+// [class.dtor]p7 bullet 1 rule does not apply. The union can't be
+// default-initialized, so no risk of indeterminate active member.
+// Destructor is trivial (see C++26 [class.dtor]p7).
+#if __cplusplus > 202302L
+union U11 {
+ U11(int);
+ NonTrivialDtor nt;
+};
+static_assert(__is_trivially_destructible(U11));
+U11 u11(1);
+
+// ===== Test 12: Deleted default ctor (dtor deleted per [class.dtor]p7) =====
+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 ([class.dtor]p7 bullet 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
>From 3faefdb97098efd7614510c1bb2ba9d642a86a79 Mon Sep 17 00:00:00 2001
From: Peng Xie <helianthus547 at gmail.com>
Date: Fri, 10 Apr 2026 15:33:40 +0800
Subject: [PATCH 2/9] Address P3074 review feedback
---
clang/lib/AST/DeclCXX.cpp | 19 ++++++++++---------
clang/lib/Sema/SemaDeclCXX.cpp | 15 +++++++--------
2 files changed, 17 insertions(+), 17 deletions(-)
diff --git a/clang/lib/AST/DeclCXX.cpp b/clang/lib/AST/DeclCXX.cpp
index 32ae2131beae1..91d9dc49c362a 100644
--- a/clang/lib/AST/DeclCXX.cpp
+++ b/clang/lib/AST/DeclCXX.cpp
@@ -81,10 +81,9 @@ CXXRecordDecl::DefinitionData::DefinitionData(CXXRecordDecl *D)
HasPrivateFields(false), HasProtectedFields(false),
HasPublicFields(false), HasMutableFields(false), HasVariantMembers(false),
HasOnlyCMembers(true), HasInitMethod(false), HasInClassInitializer(false),
- HasDMIWithNonTrivialDtor(false),
- HasUninitializedReferenceMember(false), HasUninitializedFields(false),
- HasInheritedConstructor(false), HasInheritedDefaultConstructor(false),
- HasInheritedAssignment(false),
+ HasDMIWithNonTrivialDtor(false), HasUninitializedReferenceMember(false),
+ HasUninitializedFields(false), HasInheritedConstructor(false),
+ HasInheritedDefaultConstructor(false), HasInheritedAssignment(false),
NeedOverloadResolutionForCopyConstructor(false),
NeedOverloadResolutionForMoveConstructor(false),
NeedOverloadResolutionForCopyAssignment(false),
@@ -1178,16 +1177,18 @@ void CXXRecordDecl::addedMember(Decl *D) {
Field->getType()->getAsCXXRecordDecl()->hasInClassInitializer())) {
data().HasInClassInitializer = true;
- // C++26 [class.dtor]p7 bullet 2: track whether any field with a DMI
- // has a type with a non-trivial destructor, for union dtor deletion.
+ // C++26 [class.dtor]p7 bullet 2: track whether any field with a
+ // default member initializer (DMI) has a type with a non-trivial
+ // destructor, for union destructor deletion.
if (Field->hasInClassInitializer()) {
- QualType FT =
- getASTContext().getBaseElementType(Field->getType());
+ QualType FT = getASTContext().getBaseElementType(Field->getType());
if (const auto *FR = FT->getAsCXXRecordDecl();
FR && FR->hasNonTrivialDestructor())
data().HasDMIWithNonTrivialDtor = true;
} else if (Field->isAnonymousStructOrUnion()) {
- if (Field->getType()->getAsCXXRecordDecl()->data()
+ if (Field->getType()
+ ->getAsCXXRecordDecl()
+ ->data()
.HasDMIWithNonTrivialDtor)
data().HasDMIWithNonTrivialDtor = true;
}
diff --git a/clang/lib/Sema/SemaDeclCXX.cpp b/clang/lib/Sema/SemaDeclCXX.cpp
index 50659188c4244..5adddbcc3e28d 100644
--- a/clang/lib/Sema/SemaDeclCXX.cpp
+++ b/clang/lib/Sema/SemaDeclCXX.cpp
@@ -9673,9 +9673,8 @@ bool SpecialMemberDeletionInfo::shouldDeleteForSubobjectCall(
DiagKind = NonTrivialDecl;
} else if (CSM == CXXSpecialMemberKind::Destructor &&
S.getLangOpts().CPlusPlus26) {
- // 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 (see C++26 [class.dtor]p7).
+ // C++26 [class.dtor]p7: union destructor deletion is determined
+ // by the union-specific rules, not variant member triviality.
return false;
} else {
DiagKind = NonTrivialDecl;
@@ -9838,9 +9837,8 @@ bool SpecialMemberDeletionInfo::shouldDeleteForField(FieldDecl *FD) {
if (inUnion() && shouldDeleteForVariantPtrAuthMember(FD))
return true;
- // In C++26, a union's defaulted default constructor is trivially defined
- // and never deleted due to variant member properties
- // (see C++26 [class.default.ctor]p3).
+ // C++26 [class.default.ctor]p3: a union's defaulted default constructor
+ // is trivially defined regardless of variant members.
if (inUnion() && S.getLangOpts().CPlusPlus26 &&
CSM == CXXSpecialMemberKind::DefaultConstructor)
return false;
@@ -10001,7 +9999,8 @@ static bool ShouldDeleteUnionDestructorInCpp26(Sema &S, CXXRecordDecl *RD,
return true;
}
- // Bullet 2: variant member with DMI and non-trivial dtor.
+ // Bullet 2: variant member with default member initializer (DMI)
+ // and non-trivial destructor.
if (!RD->hasDMIWithNonTrivialDtor())
return false;
@@ -10590,7 +10589,7 @@ bool Sema::SpecialMemberIsTrivial(CXXMethodDecl *MD, CXXSpecialMemberKind CSM,
if (RD->isUnion() && getLangOpts().CPlusPlus26 &&
(CSM == CXXSpecialMemberKind::DefaultConstructor ||
CSM == CXXSpecialMemberKind::Destructor)) {
- // Skip member triviality checks for unions in C++26.
+ // Union member triviality doesn't affect the union's own triviality.
} else if (!checkTrivialClassMembers(*this, RD, CSM, ConstArg, TAH, Diagnose))
return false;
>From 09fed545c9136bf6fd7a7f9a6000b5aa32d6634f Mon Sep 17 00:00:00 2001
From: Peng Xie <helianthus547 at gmail.com>
Date: Thu, 16 Apr 2026 15:42:09 +0800
Subject: [PATCH 3/9] Update release notes and cxx_status for P3074R7
---
clang/docs/ReleaseNotes.rst | 3 +++
clang/www/cxx_status.html | 2 +-
2 files changed, 4 insertions(+), 1 deletion(-)
diff --git a/clang/docs/ReleaseNotes.rst b/clang/docs/ReleaseNotes.rst
index 2da7175b51ea3..58d3487707902 100644
--- a/clang/docs/ReleaseNotes.rst
+++ b/clang/docs/ReleaseNotes.rst
@@ -127,6 +127,9 @@ C++ Language Changes
C++2c Feature Support
^^^^^^^^^^^^^^^^^^^^^
+- Implemented `P3074R7: Trivial unions <https://wg21.link/P3074R7>`_
+ (``__cpp_trivial_union``).
+
C++23 Feature Support
^^^^^^^^^^^^^^^^^^^^^
diff --git a/clang/www/cxx_status.html b/clang/www/cxx_status.html
index 2c834b07f9a8f..72c62f8424585 100755
--- a/clang/www/cxx_status.html
+++ b/clang/www/cxx_status.html
@@ -259,7 +259,7 @@ <h2 id="cxx26">C++2c implementation status</h2>
<tr>
<td>Trivial unions</td>
<td><a href="https://wg21.link/P3074">P3074R7</a></td>
- <td class="none" align="center">No</td>
+ <td class="unreleased" align="center">Clang 23</td>
</tr>
<tr>
<td>Partial program correctness</td>
>From b896a88331983a95b582db97ef36d9a4f90f4d5d Mon Sep 17 00:00:00 2001
From: Peng Xie <helianthus547 at gmail.com>
Date: Wed, 11 Mar 2026 15:26:16 +0800
Subject: [PATCH 4/9] [Clang] Implement P3726R1: Adjustments to Union Lifetime
Rules
Follow-up to P3074R7 (trivial unions). P3726R1 adjusts the union
lifetime model with three changes:
- Add __builtin_start_lifetime(void*) as a consteval builtin that
begins the lifetime of an object at the given address. This replaces
the implicit-lifetime-start semantics originally in P3074R7 p4.
- Extend the constituent values rule so that reading from a union
array member is valid when each element has a constituent value,
matching the existing scalar/struct member behavior.
- Bump __cpp_trivial_union from 202502L to 202602L.
---
clang/include/clang/Basic/Builtins.td | 6 +
.../include/clang/Basic/DiagnosticASTKinds.td | 5 +
.../clang/Basic/DiagnosticSemaKinds.td | 4 +
clang/lib/AST/ExprConstant.cpp | 120 +++++++++++++++++-
clang/lib/CodeGen/CGBuiltin.cpp | 5 +
clang/lib/Frontend/InitPreprocessor.cpp | 2 +-
clang/lib/Sema/SemaChecking.cpp | 53 ++++++++
clang/test/SemaCXX/cxx26-start-lifetime.cpp | 115 +++++++++++++++++
clang/test/SemaCXX/cxx26-trivial-union.cpp | 2 +-
9 files changed, 305 insertions(+), 7 deletions(-)
create mode 100644 clang/test/SemaCXX/cxx26-start-lifetime.cpp
diff --git a/clang/include/clang/Basic/Builtins.td b/clang/include/clang/Basic/Builtins.td
index b8bbc544595e2..259a2c2c47b87 100644
--- a/clang/include/clang/Basic/Builtins.td
+++ b/clang/include/clang/Basic/Builtins.td
@@ -1000,6 +1000,12 @@ def IsWithinLifetime : LangBuiltin<"CXX_LANG"> {
let Prototype = "bool(void*)";
}
+def StartLifetime : LangBuiltin<"CXX_LANG"> {
+ let Spellings = ["__builtin_start_lifetime"];
+ let Attributes = [NoThrow, CustomTypeChecking, Consteval];
+ let Prototype = "void(void*)";
+}
+
def GetVtablePointer : LangBuiltin<"CXX_LANG"> {
let Spellings = ["__builtin_get_vtable_pointer"];
let Attributes = [CustomTypeChecking, NoThrow, Const];
diff --git a/clang/include/clang/Basic/DiagnosticASTKinds.td b/clang/include/clang/Basic/DiagnosticASTKinds.td
index bde418695f647..37261fc3d6407 100644
--- a/clang/include/clang/Basic/DiagnosticASTKinds.td
+++ b/clang/include/clang/Basic/DiagnosticASTKinds.td
@@ -437,6 +437,11 @@ def err_invalid_is_within_lifetime : Note<
"a pointer to an object whose lifetime has not yet begun}1"
>;
+def err_invalid_start_lifetime : Note<
+ "'%0' cannot be called with "
+ "%select{a null pointer|a one-past-the-end pointer}1"
+>;
+
// inline asm related.
let CategoryName = "Inline Assembly Issue" in {
def err_asm_invalid_escape : Error<
diff --git a/clang/include/clang/Basic/DiagnosticSemaKinds.td b/clang/include/clang/Basic/DiagnosticSemaKinds.td
index a6f9a4d92f511..f63f8b618dc3a 100644
--- a/clang/include/clang/Basic/DiagnosticSemaKinds.td
+++ b/clang/include/clang/Basic/DiagnosticSemaKinds.td
@@ -13278,6 +13278,10 @@ def err_builtin_is_within_lifetime_invalid_arg : Error<
"%select{non-|function }0pointer argument to '__builtin_is_within_lifetime' "
"is not allowed">;
+def err_builtin_start_lifetime_invalid_arg : Error<
+ "'__builtin_start_lifetime' argument must be a pointer to a complete "
+ "implicit-lifetime aggregate type, but got %0">;
+
// A multi-component builtin type diagnostic. The first component broadly
// selects a scalar or container type (scalar, vector or matrix). The second
// component selects integer types and the third component selects
diff --git a/clang/lib/AST/ExprConstant.cpp b/clang/lib/AST/ExprConstant.cpp
index 4f45fa728c605..54e987c1e9127 100644
--- a/clang/lib/AST/ExprConstant.cpp
+++ b/clang/lib/AST/ExprConstant.cpp
@@ -2454,17 +2454,31 @@ static bool CheckEvaluationResult(CheckEvaluationResultKind CERK,
// expression.
if (Value.isArray()) {
QualType EltTy = Type->castAsArrayTypeUnsafe()->getElementType();
+
+ // C++26 [expr.const]p2:
+ // An inactive union subobject includes [...] an element E of an array
+ // member of a union where E is not within its lifetime.
+ // Skip such elements during constituent values checking.
+ bool IsUnionArrayMember =
+ Info.getLangOpts().CPlusPlus26 && SubobjectDecl &&
+ SubobjectDecl->getDeclContext()->isRecord() &&
+ cast<RecordDecl>(SubobjectDecl->getDeclContext())->isUnion();
+
for (unsigned I = 0, N = Value.getArrayInitializedElts(); I != N; ++I) {
- if (!CheckEvaluationResult(CERK, Info, DiagLoc, EltTy,
- Value.getArrayInitializedElt(I), Kind,
+ const APValue &Elt = Value.getArrayInitializedElt(I);
+ if (IsUnionArrayMember && !Elt.hasValue())
+ continue;
+ if (!CheckEvaluationResult(CERK, Info, DiagLoc, EltTy, Elt, Kind,
SubobjectDecl, CheckedTemps))
return false;
}
if (!Value.hasArrayFiller())
return true;
- return CheckEvaluationResult(CERK, Info, DiagLoc, EltTy,
- Value.getArrayFiller(), Kind, SubobjectDecl,
- CheckedTemps);
+ const APValue &Filler = Value.getArrayFiller();
+ if (IsUnionArrayMember && !Filler.hasValue())
+ return true;
+ return CheckEvaluationResult(CERK, Info, DiagLoc, EltTy, Filler, Kind,
+ SubobjectDecl, CheckedTemps);
}
if (Value.isUnion() && Value.getUnionField()) {
return CheckEvaluationResult(
@@ -6797,6 +6811,45 @@ struct StartLifetimeOfUnionMemberHandler {
const AccessKinds StartLifetimeOfUnionMemberHandler::AccessKind;
+namespace {
+/// Handler for __builtin_start_lifetime (see C++26 [obj.lifetime]).
+/// Starts the lifetime of the target object without initializing subobjects.
+struct BuiltinStartLifetimeHandler {
+ EvalInfo &Info;
+ static const AccessKinds AccessKind = AK_Construct;
+ typedef bool result_type;
+ bool failed() { return false; }
+ bool found(APValue &Subobj, QualType SubobjType) {
+ // C++26 [obj.lifetime]p3:
+ // If the object referenced by r is already within its lifetime,
+ // there are no effects.
+ if (Subobj.hasValue())
+ return true;
+
+ // Begin the lifetime of the object without initializing subobjects.
+ if (auto *RD = SubobjType->getAsCXXRecordDecl()) {
+ if (RD->isUnion()) {
+ Subobj = APValue((const FieldDecl *)nullptr);
+ } else {
+ Subobj = APValue(APValue::UninitStruct(), RD->getNumBases(),
+ std::distance(RD->field_begin(), RD->field_end()));
+ }
+ } else if (auto *AT = dyn_cast_or_null<ConstantArrayType>(
+ SubobjType->getAsArrayTypeUnsafe())) {
+ Subobj = APValue(APValue::UninitArray(), 0, AT->getZExtSize());
+ // Leave array filler absent — no element lifetimes started.
+ } else {
+ Subobj = APValue::IndeterminateValue();
+ }
+ return true;
+ }
+ bool found(APSInt &, QualType) { return true; }
+ bool found(APFloat &, QualType) { return true; }
+};
+} // end anonymous namespace
+
+const AccessKinds BuiltinStartLifetimeHandler::AccessKind;
+
/// Handle a builtin simple-assignment or a call to a trivial assignment
/// operator whose left-hand side might involve a union member access. If it
/// does, implicitly start the lifetime of any accessed union elements per
@@ -20533,6 +20586,8 @@ static bool EvaluateAtomic(const Expr *E, const LValue *This, APValue &Result,
// comma operator
//===----------------------------------------------------------------------===//
+static bool EvaluateBuiltinStartLifetime(EvalInfo &Info, const CallExpr *E);
+
namespace {
class VoidExprEvaluator
: public ExprEvaluatorBase<VoidExprEvaluator> {
@@ -20566,6 +20621,9 @@ class VoidExprEvaluator
case Builtin::BI__builtin_operator_delete:
return HandleOperatorDeleteCall(Info, E);
+ case Builtin::BI__builtin_start_lifetime:
+ return EvaluateBuiltinStartLifetime(Info, E);
+
default:
return false;
}
@@ -22273,3 +22331,55 @@ std::optional<bool> EvaluateBuiltinIsWithinLifetime(IntExprEvaluator &IEE,
return findSubobject(Info, E, CO, Val.getLValueDesignator(), handler);
}
} // namespace
+
+/// Evaluate __builtin_start_lifetime(ptr) (see C++26 [obj.lifetime]).
+/// Starts the lifetime of the object pointed to by ptr without initialization.
+/// If the object is a union member, it becomes the active member.
+static bool EvaluateBuiltinStartLifetime(EvalInfo &Info, const CallExpr *E) {
+ if (!Info.InConstantContext)
+ return false;
+
+ assert(E->getBuiltinCallee() == Builtin::BI__builtin_start_lifetime);
+ const Expr *Arg = E->getArg(0);
+ if (Arg->isValueDependent())
+ return false;
+
+ LValue Val;
+ if (!EvaluatePointer(Arg, Val, Info))
+ return false;
+
+ auto Error = [&](int Diag) {
+ bool CalledFromStd = false;
+ const auto *Callee = Info.CurrentCall->getCallee();
+ if (Callee && Callee->isInStdNamespace()) {
+ const IdentifierInfo *Identifier = Callee->getIdentifier();
+ CalledFromStd = Identifier && Identifier->isStr("start_lifetime");
+ }
+ Info.FFDiag(CalledFromStd ? Info.CurrentCall->getCallRange().getBegin()
+ : E->getExprLoc(),
+ diag::err_invalid_start_lifetime)
+ << (CalledFromStd ? "std::start_lifetime" : "__builtin_start_lifetime")
+ << Diag;
+ return false;
+ };
+
+ if (Val.isNullPointer() || Val.getLValueBase().isNull())
+ return Error(0);
+
+ if (Val.getLValueDesignator().isOnePastTheEnd())
+ return Error(1);
+
+ QualType T = Val.getLValueBase().getType();
+
+ // Find the complete object.
+ CompleteObject CO =
+ findCompleteObject(Info, E, AccessKinds::AK_Construct, Val, T);
+ if (!CO)
+ return false;
+
+ // Navigate to the target subobject. Use AK_Construct so that
+ // findSubobject will activate inactive union members along the path.
+ // The handler starts the lifetime without initializing subobjects.
+ BuiltinStartLifetimeHandler Handler{Info};
+ return findSubobject(Info, E, CO, Val.getLValueDesignator(), Handler);
+}
diff --git a/clang/lib/CodeGen/CGBuiltin.cpp b/clang/lib/CodeGen/CGBuiltin.cpp
index 4d74d681cd320..488df7bf102ea 100644
--- a/clang/lib/CodeGen/CGBuiltin.cpp
+++ b/clang/lib/CodeGen/CGBuiltin.cpp
@@ -5649,6 +5649,11 @@ RValue CodeGenFunction::EmitBuiltinExpr(const GlobalDecl GD, unsigned BuiltinID,
E->getCallee()->getType()->castAs<FunctionProtoType>(), E, true);
return RValue::get(nullptr);
+ case Builtin::BI__builtin_start_lifetime:
+ // No-op at runtime. Lifetime of implicit-lifetime aggregates
+ // begins automatically with storage acquisition.
+ return RValue::get(nullptr);
+
case Builtin::BI__builtin_is_aligned:
return EmitBuiltinIsAligned(E);
case Builtin::BI__builtin_align_up:
diff --git a/clang/lib/Frontend/InitPreprocessor.cpp b/clang/lib/Frontend/InitPreprocessor.cpp
index a00b34625510b..e61bbac819685 100644
--- a/clang/lib/Frontend/InitPreprocessor.cpp
+++ b/clang/lib/Frontend/InitPreprocessor.cpp
@@ -748,7 +748,7 @@ static void InitializeCPlusPlusFeatureTestMacros(const LangOptions &LangOpts,
// C++26 features.
if (LangOpts.CPlusPlus26)
- Builder.defineMacro("__cpp_trivial_union", "202502L");
+ Builder.defineMacro("__cpp_trivial_union", "202602L");
if (LangOpts.Char8)
Builder.defineMacro("__cpp_char8_t", "202207L");
diff --git a/clang/lib/Sema/SemaChecking.cpp b/clang/lib/Sema/SemaChecking.cpp
index d53c3b6ab2674..c2f41887cae8a 100644
--- a/clang/lib/Sema/SemaChecking.cpp
+++ b/clang/lib/Sema/SemaChecking.cpp
@@ -2030,6 +2030,57 @@ static ExprResult BuiltinIsWithinLifetime(Sema &S, CallExpr *TheCall) {
return TheCall;
}
+static ExprResult BuiltinStartLifetime(Sema &S, CallExpr *TheCall) {
+ if (S.checkArgCount(TheCall, 1))
+ return ExprError();
+
+ ExprResult Arg = S.DefaultFunctionArrayLvalueConversion(TheCall->getArg(0));
+ if (Arg.isInvalid())
+ return ExprError();
+ QualType ParamTy = Arg.get()->getType();
+ TheCall->setArg(0, Arg.get());
+ TheCall->setType(S.Context.VoidTy);
+
+ const auto *PT = ParamTy->getAs<PointerType>();
+ if (!PT) {
+ S.Diag(TheCall->getArg(0)->getExprLoc(),
+ diag::err_builtin_start_lifetime_invalid_arg)
+ << ParamTy;
+ return ExprError();
+ }
+
+ QualType PointeeTy = PT->getPointeeType();
+
+ // Mandates: T is a complete type
+ if (S.RequireCompleteType(TheCall->getArg(0)->getExprLoc(), PointeeTy,
+ diag::err_incomplete_type))
+ return ExprError();
+
+ // Mandates: T is an implicit-lifetime aggregate type
+ // Check aggregate first
+ if (!PointeeTy->isAggregateType()) {
+ S.Diag(TheCall->getArg(0)->getExprLoc(),
+ diag::err_builtin_start_lifetime_invalid_arg)
+ << PointeeTy;
+ return ExprError();
+ }
+
+ // Check implicit-lifetime: for aggregates, destructor must not be
+ // user-provided
+ if (const auto *RD = PointeeTy->getAsCXXRecordDecl()) {
+ if (const auto *Dtor = RD->getDestructor()) {
+ if (Dtor->isUserProvided()) {
+ S.Diag(TheCall->getArg(0)->getExprLoc(),
+ diag::err_builtin_start_lifetime_invalid_arg)
+ << PointeeTy;
+ return ExprError();
+ }
+ }
+ }
+
+ return TheCall;
+}
+
static ExprResult BuiltinTriviallyRelocate(Sema &S, CallExpr *TheCall) {
if (S.checkArgCount(TheCall, 3))
return ExprError();
@@ -3078,6 +3129,8 @@ Sema::CheckBuiltinFunctionCall(FunctionDecl *FDecl, unsigned BuiltinID,
return BuiltinLaunder(*this, TheCall);
case Builtin::BI__builtin_is_within_lifetime:
return BuiltinIsWithinLifetime(*this, TheCall);
+ case Builtin::BI__builtin_start_lifetime:
+ return BuiltinStartLifetime(*this, TheCall);
case Builtin::BI__builtin_trivially_relocate:
return BuiltinTriviallyRelocate(*this, TheCall);
diff --git a/clang/test/SemaCXX/cxx26-start-lifetime.cpp b/clang/test/SemaCXX/cxx26-start-lifetime.cpp
new file mode 100644
index 0000000000000..19f0e0d0d5196
--- /dev/null
+++ b/clang/test/SemaCXX/cxx26-start-lifetime.cpp
@@ -0,0 +1,115 @@
+// RUN: %clang_cc1 -std=c++26 -fsyntax-only -verify %s
+
+// P3726R1: __builtin_start_lifetime and constituent values tests
+
+namespace std {
+ using size_t = decltype(sizeof(0));
+}
+void* operator new(std::size_t, void* p) noexcept { return p; }
+
+// ===== Type checking tests =====
+
+struct Agg { int x; int y; };
+struct NonAgg {
+ NonAgg(int);
+ int x;
+};
+struct AggWithUserDtor {
+ int x;
+ ~AggWithUserDtor();
+};
+
+// type checking for __builtin_start_lifetime is done via consteval contexts.
+consteval void check_agg() {
+ Agg a;
+ __builtin_start_lifetime(&a); // OK
+}
+consteval void check_array() {
+ int arr[4];
+ __builtin_start_lifetime(&arr); // OK
+}
+
+consteval void check_scalar() {
+ int x = 0;
+ __builtin_start_lifetime(&x); // expected-error {{pointer to a complete implicit-lifetime aggregate type}}
+}
+
+consteval void check_non_agg() {
+ // NonAgg is not constructible without an argument, can't be a local here.
+ // Just test the pointer type check with an invalid construct.
+}
+
+consteval void check_user_dtor() {
+ AggWithUserDtor awd;
+ __builtin_start_lifetime(&awd); // expected-error {{pointer to a complete implicit-lifetime aggregate type}}
+}
+
+// ===== Constexpr evaluation tests =====
+
+// Test: start_lifetime on array member of union, then placement new elements
+consteval int test_start_lifetime_array() {
+ struct S {
+ union { int storage[4]; };
+ int size = 0;
+ };
+ S s;
+ __builtin_start_lifetime(&s.storage);
+ // Now storage is the active member, but no elements are within lifetime.
+ ::new (&s.storage[0]) int(10);
+ ::new (&s.storage[1]) int(20);
+ s.size = 2;
+ return s.storage[0] + s.storage[1]; // 30
+}
+static_assert(test_start_lifetime_array() == 30);
+
+// Test: start_lifetime is no-op if already within lifetime
+consteval int test_start_lifetime_noop() {
+ struct S {
+ union { int storage[2]; };
+ };
+ S s;
+ __builtin_start_lifetime(&s.storage);
+ ::new (&s.storage[0]) int(42);
+ // Call again - should be a no-op since storage is already active
+ __builtin_start_lifetime(&s.storage);
+ return s.storage[0]; // Still 42
+}
+static_assert(test_start_lifetime_noop() == 42);
+
+// Test: start_lifetime on struct member of union
+consteval int test_start_lifetime_struct() {
+ struct Inner { int a; int b; };
+ union U { Inner inner; int x; };
+ U u;
+ __builtin_start_lifetime(&u.inner);
+ // inner is now active but its members aren't initialized yet
+ ::new (&u.inner.a) int(1);
+ ::new (&u.inner.b) int(2);
+ return u.inner.a + u.inner.b;
+}
+static_assert(test_start_lifetime_struct() == 3);
+
+// ===== Constituent values: array with holes in union =====
+// Array elements not within their lifetime in a union are inactive union
+// subobjects and should be skipped (see C++26 [expr.const]p2).
+
+struct CVResult {
+ union { int arr[4]; };
+ int size;
+};
+
+consteval CVResult test_constituent_values() {
+ CVResult s;
+ s.size = 2;
+ __builtin_start_lifetime(&s.arr);
+ ::new (&s.arr[0]) int(100);
+ ::new (&s.arr[1]) int(200);
+ // arr[2] and arr[3] are not within their lifetime — that's OK per P3726R1.
+ return s;
+}
+// This should be a valid constexpr variable even though arr[2] and arr[3]
+// are not initialized — they are inactive union subobjects per P3726R1.
+constexpr auto cv_result = test_constituent_values();
+static_assert(cv_result.arr[0] == 100);
+static_assert(cv_result.arr[1] == 200);
+static_assert(cv_result.size == 2);
diff --git a/clang/test/SemaCXX/cxx26-trivial-union.cpp b/clang/test/SemaCXX/cxx26-trivial-union.cpp
index 8265c4f4d7670..9beff149cfb94 100644
--- a/clang/test/SemaCXX/cxx26-trivial-union.cpp
+++ b/clang/test/SemaCXX/cxx26-trivial-union.cpp
@@ -77,7 +77,7 @@ void test_u5_destroy(U5 *p) { p->~U5(); } // cxx26-error {{deleted}} precxx26-er
// ===== Test 6: Feature test macro =====
#if __cplusplus > 202302L
-static_assert(__cpp_trivial_union >= 202502L);
+static_assert(__cpp_trivial_union >= 202602L);
#else
#ifdef __cpp_trivial_union
#error "should not have __cpp_trivial_union in C++23"
>From 077efdc8a8a6002a70071c1002663339ef5d4bca Mon Sep 17 00:00:00 2001
From: Peng Xie <helianthus547 at gmail.com>
Date: Thu, 9 Apr 2026 16:44:52 +0800
Subject: [PATCH 5/9] Update P3726 implementation to R2: terminology and tests
---
clang/lib/AST/ExprConstant.cpp | 5 +++--
clang/test/SemaCXX/cxx26-start-lifetime.cpp | 13 +++++++++----
2 files changed, 12 insertions(+), 6 deletions(-)
diff --git a/clang/lib/AST/ExprConstant.cpp b/clang/lib/AST/ExprConstant.cpp
index 54e987c1e9127..40fb49abc1ec9 100644
--- a/clang/lib/AST/ExprConstant.cpp
+++ b/clang/lib/AST/ExprConstant.cpp
@@ -2456,8 +2456,9 @@ static bool CheckEvaluationResult(CheckEvaluationResultKind CERK,
QualType EltTy = Type->castAsArrayTypeUnsafe()->getElementType();
// C++26 [expr.const]p2:
- // An inactive union subobject includes [...] an element E of an array
- // member of a union where E is not within its lifetime.
+ // A union elemental subobject is a direct member of a union or an
+ // element of an array that is a union elemental subobject.
+ // An inactive union elemental subobject is one not within its lifetime.
// Skip such elements during constituent values checking.
bool IsUnionArrayMember =
Info.getLangOpts().CPlusPlus26 && SubobjectDecl &&
diff --git a/clang/test/SemaCXX/cxx26-start-lifetime.cpp b/clang/test/SemaCXX/cxx26-start-lifetime.cpp
index 19f0e0d0d5196..78d69c82c8627 100644
--- a/clang/test/SemaCXX/cxx26-start-lifetime.cpp
+++ b/clang/test/SemaCXX/cxx26-start-lifetime.cpp
@@ -1,6 +1,6 @@
// RUN: %clang_cc1 -std=c++26 -fsyntax-only -verify %s
-// P3726R1: __builtin_start_lifetime and constituent values tests
+// P3726R2: __builtin_start_lifetime and constituent values tests
namespace std {
using size_t = decltype(sizeof(0));
@@ -90,8 +90,9 @@ consteval int test_start_lifetime_struct() {
static_assert(test_start_lifetime_struct() == 3);
// ===== Constituent values: array with holes in union =====
-// Array elements not within their lifetime in a union are inactive union
-// subobjects and should be skipped (see C++26 [expr.const]p2).
+// C++26 [expr.const]p2: A union elemental subobject is a direct member of a
+// union or an element of an array that is a union elemental subobject.
+// An inactive union elemental subobject is one not within its lifetime.
struct CVResult {
union { int arr[4]; };
@@ -108,8 +109,12 @@ consteval CVResult test_constituent_values() {
return s;
}
// This should be a valid constexpr variable even though arr[2] and arr[3]
-// are not initialized — they are inactive union subobjects per P3726R1.
+// are not initialized — they are inactive union elemental subobjects.
constexpr auto cv_result = test_constituent_values();
static_assert(cv_result.arr[0] == 100);
static_assert(cv_result.arr[1] == 200);
static_assert(cv_result.size == 2);
+
+// TODO: Multi-dimensional array support for start_lifetime requires
+// the constexpr evaluator to handle intermediate array element lifetime.
+// This is tracked separately.
>From fbf7af06bc72e33515fd281b0dee61ec058ff7bd Mon Sep 17 00:00:00 2001
From: Peng Xie <helianthus547 at gmail.com>
Date: Fri, 10 Apr 2026 13:41:26 +0800
Subject: [PATCH 6/9] Update P3726 tests for nested-array lifetime start
guidance
---
clang/test/SemaCXX/cxx26-start-lifetime.cpp | 35 ++++++++++++++++++---
1 file changed, 31 insertions(+), 4 deletions(-)
diff --git a/clang/test/SemaCXX/cxx26-start-lifetime.cpp b/clang/test/SemaCXX/cxx26-start-lifetime.cpp
index 78d69c82c8627..0eac683036617 100644
--- a/clang/test/SemaCXX/cxx26-start-lifetime.cpp
+++ b/clang/test/SemaCXX/cxx26-start-lifetime.cpp
@@ -105,7 +105,7 @@ consteval CVResult test_constituent_values() {
__builtin_start_lifetime(&s.arr);
::new (&s.arr[0]) int(100);
::new (&s.arr[1]) int(200);
- // arr[2] and arr[3] are not within their lifetime — that's OK per P3726R1.
+ // arr[2] and arr[3] are not within their lifetime — that's OK per P3726R2.
return s;
}
// This should be a valid constexpr variable even though arr[2] and arr[3]
@@ -115,6 +115,33 @@ static_assert(cv_result.arr[0] == 100);
static_assert(cv_result.arr[1] == 200);
static_assert(cv_result.size == 2);
-// TODO: Multi-dimensional array support for start_lifetime requires
-// the constexpr evaluator to handle intermediate array element lifetime.
-// This is tracked separately.
+// CWG guidance: for nested arrays, start lifetime of the top-level array and
+// then each nested array before constructing inner scalar subobjects.
+consteval int test_multidim_without_nested_start() {
+ struct S {
+ union {
+ int storage[2][3];
+ };
+ };
+ S s;
+ __builtin_start_lifetime(&s.storage);
+ ::new (&s.storage[0][0]) int(1); // expected-note {{construction of subobject of object outside its lifetime is not allowed in a constant expression}}
+ return s.storage[0][0];
+}
+static_assert(test_multidim_without_nested_start() == 1); // expected-error {{static assertion expression is not an integral constant expression}} expected-note {{in call to 'test_multidim_without_nested_start()'}}
+
+consteval int test_multidim_with_nested_start() {
+ struct S {
+ union {
+ int storage[2][3];
+ };
+ };
+ S s;
+ __builtin_start_lifetime(&s.storage);
+ __builtin_start_lifetime(&s.storage[0]);
+ __builtin_start_lifetime(&s.storage[1]);
+ ::new (&s.storage[0][0]) int(1);
+ ::new (&s.storage[0][1]) int(2);
+ return s.storage[0][0] + s.storage[0][1];
+}
+static_assert(test_multidim_with_nested_start() == 3);
>From 0fb888ed9c45c3b54e6245febb0faf739f05c405 Mon Sep 17 00:00:00 2001
From: Peng Xie <helianthus547 at gmail.com>
Date: Fri, 10 Apr 2026 14:40:42 +0800
Subject: [PATCH 7/9] start_lifetime: recursively start lifetime of nested
sub-arrays
CWG guidance (Croyden): start_lifetime on a multi-dimensional array
should recursively start the lifetime of each nested sub-array, but
not scalar elements. Set the array filler so expandArray propagates
the started state to each element on demand.
Update tests: single start_lifetime call now works for 2D and 3D arrays.
---
clang/lib/AST/ExprConstant.cpp | 11 ++++++--
clang/test/SemaCXX/cxx26-start-lifetime.cpp | 31 +++++++++++----------
2 files changed, 26 insertions(+), 16 deletions(-)
diff --git a/clang/lib/AST/ExprConstant.cpp b/clang/lib/AST/ExprConstant.cpp
index 40fb49abc1ec9..d8a23b6522be4 100644
--- a/clang/lib/AST/ExprConstant.cpp
+++ b/clang/lib/AST/ExprConstant.cpp
@@ -6837,8 +6837,15 @@ struct BuiltinStartLifetimeHandler {
}
} else if (auto *AT = dyn_cast_or_null<ConstantArrayType>(
SubobjType->getAsArrayTypeUnsafe())) {
- Subobj = APValue(APValue::UninitArray(), 0, AT->getZExtSize());
- // Leave array filler absent — no element lifetimes started.
+ unsigned NumElems = AT->getZExtSize();
+ Subobj = APValue(APValue::UninitArray(), 0, NumElems);
+ // CWG guidance (Croyden): starting the lifetime of a multi-dimensional
+ // array also starts the lifetime of each nested sub-array, but not
+ // scalar elements. Recurse via the filler so that expandArray
+ // propagates the started state to each element on demand.
+ QualType ElemType = AT->getElementType();
+ if (ElemType->isArrayType())
+ found(Subobj.getArrayFiller(), ElemType);
} else {
Subobj = APValue::IndeterminateValue();
}
diff --git a/clang/test/SemaCXX/cxx26-start-lifetime.cpp b/clang/test/SemaCXX/cxx26-start-lifetime.cpp
index 0eac683036617..0d4a67e8a2c93 100644
--- a/clang/test/SemaCXX/cxx26-start-lifetime.cpp
+++ b/clang/test/SemaCXX/cxx26-start-lifetime.cpp
@@ -115,9 +115,10 @@ static_assert(cv_result.arr[0] == 100);
static_assert(cv_result.arr[1] == 200);
static_assert(cv_result.size == 2);
-// CWG guidance: for nested arrays, start lifetime of the top-level array and
-// then each nested array before constructing inner scalar subobjects.
-consteval int test_multidim_without_nested_start() {
+// CWG guidance (Croyden): starting the lifetime of a multi-dimensional array
+// recursively starts the lifetime of each nested sub-array, but not scalar
+// elements. The wording will be fixed to make this clear.
+consteval int test_multidim_single_start() {
struct S {
union {
int storage[2][3];
@@ -125,23 +126,25 @@ consteval int test_multidim_without_nested_start() {
};
S s;
__builtin_start_lifetime(&s.storage);
- ::new (&s.storage[0][0]) int(1); // expected-note {{construction of subobject of object outside its lifetime is not allowed in a constant expression}}
- return s.storage[0][0];
+ // No need to start_lifetime each sub-array — done recursively.
+ ::new (&s.storage[0][0]) int(1);
+ ::new (&s.storage[0][1]) int(2);
+ ::new (&s.storage[1][0]) int(10);
+ return s.storage[0][0] + s.storage[0][1] + s.storage[1][0];
}
-static_assert(test_multidim_without_nested_start() == 1); // expected-error {{static assertion expression is not an integral constant expression}} expected-note {{in call to 'test_multidim_without_nested_start()'}}
+static_assert(test_multidim_single_start() == 13);
-consteval int test_multidim_with_nested_start() {
+// 3-dimensional array: start_lifetime recurses through all sub-array levels.
+consteval int test_3d_array() {
struct S {
union {
- int storage[2][3];
+ int storage[2][2][2];
};
};
S s;
__builtin_start_lifetime(&s.storage);
- __builtin_start_lifetime(&s.storage[0]);
- __builtin_start_lifetime(&s.storage[1]);
- ::new (&s.storage[0][0]) int(1);
- ::new (&s.storage[0][1]) int(2);
- return s.storage[0][0] + s.storage[0][1];
+ ::new (&s.storage[0][0][0]) int(1);
+ ::new (&s.storage[1][1][1]) int(7);
+ return s.storage[0][0][0] + s.storage[1][1][1];
}
-static_assert(test_multidim_with_nested_start() == 3);
+static_assert(test_3d_array() == 8);
>From 9b988f24f07811a7bdf4c8f41bf5dfc07fad31b8 Mon Sep 17 00:00:00 2001
From: Peng Xie <helianthus547 at gmail.com>
Date: Tue, 14 Apr 2026 15:55:56 +0800
Subject: [PATCH 8/9] Add bytecode interpreter support for
__builtin_start_lifetime
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
Co-authored-by: Timm Bäder <tbaeder at redhat.com>
---
clang/lib/AST/ByteCode/Interp.cpp | 2 +-
clang/lib/AST/ByteCode/InterpBuiltin.cpp | 38 +++++++++++++++++++++
clang/lib/AST/ByteCode/InterpHelpers.h | 2 ++
clang/test/SemaCXX/cxx26-start-lifetime.cpp | 20 +++++++++++
4 files changed, 61 insertions(+), 1 deletion(-)
diff --git a/clang/lib/AST/ByteCode/Interp.cpp b/clang/lib/AST/ByteCode/Interp.cpp
index 8cc3c9216f7f4..bc82e235e0288 100644
--- a/clang/lib/AST/ByteCode/Interp.cpp
+++ b/clang/lib/AST/ByteCode/Interp.cpp
@@ -1917,7 +1917,7 @@ bool CallPtr(InterpState &S, CodePtr OpPC, uint32_t ArgSize,
return Call(S, OpPC, F, VarArgSize);
}
-static void startLifetimeRecurse(const Pointer &Ptr) {
+void startLifetimeRecurse(const Pointer &Ptr) {
if (const Record *R = Ptr.getRecord()) {
Ptr.startLifetime();
for (const Record::Field &Fi : R->fields())
diff --git a/clang/lib/AST/ByteCode/InterpBuiltin.cpp b/clang/lib/AST/ByteCode/InterpBuiltin.cpp
index e7b3ef6ce1510..c417b9e28f855 100644
--- a/clang/lib/AST/ByteCode/InterpBuiltin.cpp
+++ b/clang/lib/AST/ByteCode/InterpBuiltin.cpp
@@ -4189,6 +4189,39 @@ static bool interp__builtin_ia32_gfni_mul(InterpState &S, CodePtr OpPC,
return true;
}
+static bool interp__builtin_start_lifetime(InterpState &S, CodePtr OpPC,
+ const CallExpr *Call) {
+ if (!S.inConstantContext())
+ return false;
+
+ Pointer Ptr = S.Stk.pop<Pointer>();
+
+ auto Error = [&](int Diag) {
+ bool CalledFromStd = false;
+ const auto *Callee = S.Current->getCallee();
+ if (Callee && Callee->isInStdNamespace()) {
+ const IdentifierInfo *Identifier = Callee->getIdentifier();
+ CalledFromStd = Identifier && Identifier->isStr("start_lifetime");
+ }
+ S.FFDiag(CalledFromStd ? S.Current->Caller->getSource(S.Current->getRetPC())
+ : S.Current->getSource(OpPC),
+ diag::err_invalid_start_lifetime)
+ << (CalledFromStd ? "std::start_lifetime" : "__builtin_start_lifetime")
+ << Diag;
+ return false;
+ };
+
+ if (Ptr.isZero())
+ return Error(0);
+ if (Ptr.isOnePastEnd())
+ return Error(1);
+
+ startLifetimeRecurse(Ptr);
+ Ptr.activate();
+ assert(Ptr.isActive());
+ return true;
+}
+
bool InterpretBuiltin(InterpState &S, CodePtr OpPC, const CallExpr *Call,
uint32_t BuiltinID) {
if (!S.getASTContext().BuiltinInfo.isConstantEvaluated(BuiltinID))
@@ -6050,6 +6083,9 @@ bool InterpretBuiltin(InterpState &S, CodePtr OpPC, const CallExpr *Call,
},
/*IsScalar=*/true);
+ case Builtin::BI__builtin_start_lifetime:
+ return interp__builtin_start_lifetime(S, OpPC, Call);
+
default:
S.FFDiag(S.Current->getLocation(OpPC),
diag::note_invalid_subexpr_in_const_expr)
@@ -6261,6 +6297,8 @@ static bool copyComposite(InterpState &S, CodePtr OpPC, const Pointer &Src,
DestElem.initialize();
});
}
+ if (Activate)
+ Dest.activate();
return true;
}
diff --git a/clang/lib/AST/ByteCode/InterpHelpers.h b/clang/lib/AST/ByteCode/InterpHelpers.h
index 905bf1b43bfab..11b751bd50041 100644
--- a/clang/lib/AST/ByteCode/InterpHelpers.h
+++ b/clang/lib/AST/ByteCode/InterpHelpers.h
@@ -69,6 +69,8 @@ bool DoMemcpy(InterpState &S, CodePtr OpPC, const Pointer &Src, Pointer &Dest);
UnsignedOrNone evaluateBuiltinObjectSize(const ASTContext &ASTCtx,
unsigned Kind, Pointer &Ptr);
+void startLifetimeRecurse(const Pointer &Ptr);
+
template <typename T>
static bool handleOverflow(InterpState &S, CodePtr OpPC, const T &SrcValue) {
const Expr *E = S.Current->getExpr(OpPC);
diff --git a/clang/test/SemaCXX/cxx26-start-lifetime.cpp b/clang/test/SemaCXX/cxx26-start-lifetime.cpp
index 0d4a67e8a2c93..92c37fe24c446 100644
--- a/clang/test/SemaCXX/cxx26-start-lifetime.cpp
+++ b/clang/test/SemaCXX/cxx26-start-lifetime.cpp
@@ -1,4 +1,6 @@
// RUN: %clang_cc1 -std=c++26 -fsyntax-only -verify %s
+// FIXME: Enable for bytecode interpreter once union array member activation
+// is supported: %clang_cc1 -std=c++26 -fsyntax-only -verify %s -fexperimental-new-constant-interpreter
// P3726R2: __builtin_start_lifetime and constituent values tests
@@ -44,6 +46,24 @@ consteval void check_user_dtor() {
__builtin_start_lifetime(&awd); // expected-error {{pointer to a complete implicit-lifetime aggregate type}}
}
+// ===== Error checking tests =====
+
+consteval bool check_null_pointer() {
+ Agg *p = nullptr;
+ __builtin_start_lifetime(p); // expected-note {{cannot be called with a null pointer}}
+ return true;
+}
+static_assert(check_null_pointer()); // expected-error {{constant expression}} \
+ // expected-note {{in call to}}
+
+consteval bool check_one_past_the_end() {
+ Agg a[2];
+ __builtin_start_lifetime(&a[2]); // expected-note {{cannot be called with a one-past-the-end pointer}}
+ return true;
+}
+static_assert(check_one_past_the_end()); // expected-error {{constant expression}} \
+ // expected-note {{in call to}}
+
// ===== Constexpr evaluation tests =====
// Test: start_lifetime on array member of union, then placement new elements
>From 1c1dda31f5e35a929a703e4b5120a26ba6238d56 Mon Sep 17 00:00:00 2001
From: Peng Xie <helianthus547 at gmail.com>
Date: Thu, 16 Apr 2026 15:42:58 +0800
Subject: [PATCH 9/9] Update release notes and cxx_status for P3726R2
---
clang/docs/ReleaseNotes.rst | 2 ++
clang/www/cxx_status.html | 2 +-
2 files changed, 3 insertions(+), 1 deletion(-)
diff --git a/clang/docs/ReleaseNotes.rst b/clang/docs/ReleaseNotes.rst
index 58d3487707902..053b6784e50ef 100644
--- a/clang/docs/ReleaseNotes.rst
+++ b/clang/docs/ReleaseNotes.rst
@@ -129,6 +129,8 @@ C++2c Feature Support
- Implemented `P3074R7: Trivial unions <https://wg21.link/P3074R7>`_
(``__cpp_trivial_union``).
+- Implemented `P3726R2: Adjustments to Union Lifetime Rules
+ <https://wg21.link/P3726R2>`_ (``__builtin_start_lifetime``).
C++23 Feature Support
^^^^^^^^^^^^^^^^^^^^^
diff --git a/clang/www/cxx_status.html b/clang/www/cxx_status.html
index 72c62f8424585..10a8a5678f6a4 100755
--- a/clang/www/cxx_status.html
+++ b/clang/www/cxx_status.html
@@ -357,7 +357,7 @@ <h2 id="cxx26">C++2c implementation status</h2>
<tr>
<td>Adjustments to Union Lifetime Rules</td>
<td><a href="https://wg21.link/P3726">P3726R2</a></td>
- <td class="none" align="center">No</td>
+ <td class="unreleased" align="center">Clang 23</td>
</tr>
<tr>
<td>Constant evaluations fixes</td>
More information about the cfe-commits
mailing list