[clang] [Clang] Explain why is_standard_layout evaluated to false (PR #143722)
Samarth Narang via cfe-commits
cfe-commits at lists.llvm.org
Fri Jun 13 14:05:29 PDT 2025
https://github.com/snarang181 updated https://github.com/llvm/llvm-project/pull/143722
>From 6f787e1997f53968e23586b4eb03c45e6559ebbf Mon Sep 17 00:00:00 2001
From: Samarth Narang <snarang at umass.edu>
Date: Mon, 2 Jun 2025 19:30:39 -0400
Subject: [PATCH 1/2] Add std layout diagnostics Add diagnostic test cases
---
.../clang/Basic/DiagnosticSemaKinds.td | 9 +-
clang/lib/Sema/SemaTypeTraits.cpp | 177 +++++++++++++++++-
.../type-traits-unsatisfied-diags-std.cpp | 98 ++++++++++
.../SemaCXX/type-traits-unsatisfied-diags.cpp | 142 ++++++++++++++
4 files changed, 420 insertions(+), 6 deletions(-)
diff --git a/clang/include/clang/Basic/DiagnosticSemaKinds.td b/clang/include/clang/Basic/DiagnosticSemaKinds.td
index 8fe7ad6138aa0..f76f061196416 100644
--- a/clang/include/clang/Basic/DiagnosticSemaKinds.td
+++ b/clang/include/clang/Basic/DiagnosticSemaKinds.td
@@ -1767,7 +1767,9 @@ def note_unsatisfied_trait
: Note<"%0 is not %enum_select<TraitName>{"
"%TriviallyRelocatable{trivially relocatable}|"
"%Replaceable{replaceable}|"
- "%TriviallyCopyable{trivially copyable}"
+ "%TriviallyCopyable{trivially copyable}|"
+ "%Constructible{constructible with provided types}|"
+ "%StandardLayout{standard-layout}"
"}1">;
def note_unsatisfied_trait_reason
@@ -1787,6 +1789,11 @@ def note_unsatisfied_trait_reason
"%NonReplaceableField{has a non-replaceable member %1 of type %2}|"
"%NTCBase{has a non-trivially-copyable base %1}|"
"%NTCField{has a non-trivially-copyable member %1 of type %2}|"
+ "%NonStdLayoutBase{has a non-standard-layout base %1}|"
+ "%MixedAccess{has mixed access specifiers}|"
+ "%MultipleDataBase{has multiple base classes with data members}|"
+ "%VirtualFunction{has virtual functions}|"
+ "%NonStdLayoutMember{has a non-standard-layout member %1 of type %2}|"
"%DeletedDtr{has a %select{deleted|user-provided}1 destructor}|"
"%UserProvidedCtr{has a user provided %select{copy|move}1 "
"constructor}|"
diff --git a/clang/lib/Sema/SemaTypeTraits.cpp b/clang/lib/Sema/SemaTypeTraits.cpp
index 1738ab4466001..3b216df8ad3f3 100644
--- a/clang/lib/Sema/SemaTypeTraits.cpp
+++ b/clang/lib/Sema/SemaTypeTraits.cpp
@@ -1947,6 +1947,8 @@ static std::optional<TypeTrait> StdNameToTypeTrait(StringRef Name) {
TypeTrait::UTT_IsCppTriviallyRelocatable)
.Case("is_replaceable", TypeTrait::UTT_IsReplaceable)
.Case("is_trivially_copyable", TypeTrait::UTT_IsTriviallyCopyable)
+ .Case("is_constructible", TypeTrait::TT_IsConstructible)
+ .Case("is_standard_layout", TypeTrait::UTT_IsStandardLayout)
.Default(std::nullopt);
}
@@ -2257,21 +2259,180 @@ static void DiagnoseNonTriviallyCopyableReason(Sema &SemaRef,
}
}
-static void DiagnoseNonTriviallyCopyableReason(Sema &SemaRef,
- SourceLocation Loc, QualType T) {
+static void DiagnoseNonConstructibleReason(
+ Sema &SemaRef, SourceLocation Loc,
+ const llvm::SmallVector<clang::QualType, 1> &Ts) {
+ if (Ts.empty()) {
+ return;
+ }
+
+ bool ContainsVoid = false;
+ for (const QualType &ArgTy : Ts) {
+ ContainsVoid |= ArgTy->isVoidType();
+ }
+
+ if (ContainsVoid)
+ SemaRef.Diag(Loc, diag::note_unsatisfied_trait_reason)
+ << diag::TraitNotSatisfiedReason::CVVoidType;
+
+ QualType T = Ts[0];
+ if (T->isFunctionType())
+ SemaRef.Diag(Loc, diag::note_unsatisfied_trait_reason)
+ << diag::TraitNotSatisfiedReason::FunctionType;
+
+ if (T->isIncompleteArrayType())
+ SemaRef.Diag(Loc, diag::note_unsatisfied_trait_reason)
+ << diag::TraitNotSatisfiedReason::IncompleteArrayType;
+
+ const CXXRecordDecl *D = T->getAsCXXRecordDecl();
+ if (!D || D->isInvalidDecl() || !D->hasDefinition())
+ return;
+
+ llvm::BumpPtrAllocator OpaqueExprAllocator;
+ SmallVector<Expr *, 2> ArgExprs;
+ ArgExprs.reserve(Ts.size() - 1);
+ for (unsigned I = 1, N = Ts.size(); I != N; ++I) {
+ QualType ArgTy = Ts[I];
+ if (ArgTy->isObjectType() || ArgTy->isFunctionType())
+ ArgTy = SemaRef.Context.getRValueReferenceType(ArgTy);
+ ArgExprs.push_back(
+ new (OpaqueExprAllocator.Allocate<OpaqueValueExpr>())
+ OpaqueValueExpr(Loc, ArgTy.getNonLValueExprType(SemaRef.Context),
+ Expr::getValueKindForType(ArgTy)));
+ }
+
+ EnterExpressionEvaluationContext Unevaluated(
+ SemaRef, Sema::ExpressionEvaluationContext::Unevaluated);
+ Sema::ContextRAII TUContext(SemaRef,
+ SemaRef.Context.getTranslationUnitDecl());
+ InitializedEntity To(InitializedEntity::InitializeTemporary(T));
+ InitializationKind InitKind(InitializationKind::CreateDirect(Loc, Loc, Loc));
+ InitializationSequence Init(SemaRef, To, InitKind, ArgExprs);
+
+ Init.Diagnose(SemaRef, To, InitKind, ArgExprs);
+ SemaRef.Diag(D->getLocation(), diag::note_defined_here) << D;
+}
+
+static void DiagnoseNonStandardLayoutReason(Sema &SemaRef, SourceLocation Loc,
+ QualType T) {
SemaRef.Diag(Loc, diag::note_unsatisfied_trait)
- << T << diag::TraitName::TriviallyCopyable;
+ << T << diag::TraitName::StandardLayout;
- if (T->isReferenceType())
+ // Check type-level exclusion first
+ if (T->isVariablyModifiedType()) {
+ SemaRef.Diag(Loc, diag::note_unsatisfied_trait_reason)
+ << diag::TraitNotSatisfiedReason::VLA;
+ return;
+ }
+
+ if (T->isReferenceType()) {
SemaRef.Diag(Loc, diag::note_unsatisfied_trait_reason)
<< diag::TraitNotSatisfiedReason::Ref;
+ return;
+ }
+ T = T.getNonReferenceType();
+ const CXXRecordDecl *D = T->getAsCXXRecordDecl();
+ if (!D || D->isInvalidDecl())
+ return;
+
+ if (D->hasDefinition())
+ DiagnoseNonStandardLayoutReason(SemaRef, Loc, D);
+
+ SemaRef.Diag(D->getLocation(), diag::note_defined_here) << D;
+}
+
+static bool hasMixedAccessSpecifier(const CXXRecordDecl *D) {
+ AccessSpecifier FirstAccess = AS_none;
+ for (const FieldDecl *Field : D->fields()) {
+ if (Field->isUnnamedBitField())
+ continue;
+ AccessSpecifier FieldAccess = Field->getAccess();
+ if (FirstAccess == AS_none) {
+ FirstAccess = FieldAccess;
+ } else if (FieldAccess != FirstAccess) {
+ return true;
+ }
+ }
+ return false;
+}
+static bool hasMultipleDataBaseClassesWithFields(const CXXRecordDecl *D) {
+ int NumBasesWithFields = 0;
+ for (const CXXBaseSpecifier &Base : D->bases()) {
+ const CXXRecordDecl *BaseRD = Base.getType()->getAsCXXRecordDecl();
+ if (!BaseRD || BaseRD->isInvalidDecl())
+ continue;
+
+ for (const FieldDecl *Field : BaseRD->fields()) {
+ if (!Field->isUnnamedBitField()) {
+ ++NumBasesWithFields;
+ break; // Only count the base once.
+ }
+ }
+ }
+ return NumBasesWithFields > 1;
+}
+
+static void DiagnoseNonStandardLayoutReason(Sema &SemaRef, SourceLocation Loc,
+ const CXXRecordDecl *D) {
+ for (const CXXBaseSpecifier &B : D->bases()) {
+ assert(B.getType()->getAsCXXRecordDecl() && "invalid base?");
+ if (B.isVirtual()) {
+ SemaRef.Diag(Loc, diag::note_unsatisfied_trait_reason)
+ << diag::TraitNotSatisfiedReason::VBase << B.getType()
+ << B.getSourceRange();
+ }
+ if (!B.getType()->isStandardLayoutType()) {
+ SemaRef.Diag(Loc, diag::note_unsatisfied_trait_reason)
+ << diag::TraitNotSatisfiedReason::NonStdLayoutBase << B.getType()
+ << B.getSourceRange();
+ }
+ }
+ if (hasMixedAccessSpecifier(D)) {
+ SemaRef.Diag(Loc, diag::note_unsatisfied_trait_reason)
+ << diag::TraitNotSatisfiedReason::MixedAccess;
+ }
+ if (hasMultipleDataBaseClassesWithFields(D)) {
+ SemaRef.Diag(Loc, diag::note_unsatisfied_trait_reason)
+ << diag::TraitNotSatisfiedReason::MultipleDataBase;
+ }
+ if (D->isPolymorphic()) {
+ SemaRef.Diag(Loc, diag::note_unsatisfied_trait_reason)
+ << diag::TraitNotSatisfiedReason::VirtualFunction;
+ }
+ for (const FieldDecl *Field : D->fields()) {
+ if (!Field->getType()->isStandardLayoutType()) {
+ SemaRef.Diag(Loc, diag::note_unsatisfied_trait_reason)
+ << diag::TraitNotSatisfiedReason::NonStdLayoutMember << Field
+ << Field->getType() << Field->getSourceRange();
+ }
+ }
+}
+
+static void DiagnoseNonStandardLayoutReason(Sema &SemaRef, SourceLocation Loc,
+ QualType T) {
+ SemaRef.Diag(Loc, diag::note_unsatisfied_trait)
+ << T << diag::TraitName::StandardLayout;
+
+ // Check type-level exclusion first
+ if (T->isVariablyModifiedType()) {
+ SemaRef.Diag(Loc, diag::note_unsatisfied_trait_reason)
+ << diag::TraitNotSatisfiedReason::VLA;
+ return;
+ }
+
+ if (T->isReferenceType()) {
+ SemaRef.Diag(Loc, diag::note_unsatisfied_trait_reason)
+ << diag::TraitNotSatisfiedReason::Ref;
+ return;
+ }
+ T = T.getNonReferenceType();
const CXXRecordDecl *D = T->getAsCXXRecordDecl();
if (!D || D->isInvalidDecl())
return;
if (D->hasDefinition())
- DiagnoseNonTriviallyCopyableReason(SemaRef, Loc, D);
+ DiagnoseNonStandardLayoutReason(SemaRef, Loc, D);
SemaRef.Diag(D->getLocation(), diag::note_defined_here) << D;
}
@@ -2296,6 +2457,12 @@ void Sema::DiagnoseTypeTraitDetails(const Expr *E) {
case UTT_IsTriviallyCopyable:
DiagnoseNonTriviallyCopyableReason(*this, E->getBeginLoc(), Args[0]);
break;
+ case TT_IsConstructible:
+ DiagnoseNonConstructibleReason(*this, E->getBeginLoc(), Args);
+ break;
+ case UTT_IsStandardLayout:
+ DiagnoseNonStandardLayoutReason(*this, E->getBeginLoc(), Args[0]);
+ break;
default:
break;
}
diff --git a/clang/test/SemaCXX/type-traits-unsatisfied-diags-std.cpp b/clang/test/SemaCXX/type-traits-unsatisfied-diags-std.cpp
index 329b611110c1d..0f223f372418a 100644
--- a/clang/test/SemaCXX/type-traits-unsatisfied-diags-std.cpp
+++ b/clang/test/SemaCXX/type-traits-unsatisfied-diags-std.cpp
@@ -20,6 +20,21 @@ struct is_trivially_copyable {
template <typename T>
constexpr bool is_trivially_copyable_v = __is_trivially_copyable(T);
+
+template <typename... Args>
+struct is_constructible {
+ static constexpr bool value = __is_constructible(Args...);
+};
+
+template <typename... Args>
+constexpr bool is_constructible_v = __is_constructible(Args...);
+
+template <typename T>
+struct is_standard_layout {
+ static constexpr bool value = __is_standard_layout(T);
+};
+template <typename T>
+constexpr bool is_standard_layout_v = __is_standard_layout(T);
#endif
#ifdef STD2
@@ -44,6 +59,26 @@ using is_trivially_copyable = __details_is_trivially_copyable<T>;
template <typename T>
constexpr bool is_trivially_copyable_v = __is_trivially_copyable(T);
+
+template <typename... Args>
+struct __details_is_constructible{
+ static constexpr bool value = __is_constructible(Args...);
+};
+
+template <typename... Args>
+using is_constructible = __details_is_constructible<Args...>;
+
+template <typename... Args>
+constexpr bool is_constructible_v = __is_constructible(Args...);
+
+template <typename T>
+struct __details_is_standard_layout {
+ static constexpr bool value = __is_standard_layout(T);
+};
+template <typename T>
+using is_standard_layout = __details_is_standard_layout<T>;
+template <typename T>
+constexpr bool is_standard_layout_v = __is_standard_layout(T);
#endif
@@ -73,6 +108,24 @@ using is_trivially_copyable = __details_is_trivially_copyable<T>;
template <typename T>
constexpr bool is_trivially_copyable_v = is_trivially_copyable<T>::value;
+
+template <typename... Args>
+struct __details_is_constructible : bool_constant<__is_constructible(Args...)> {};
+
+template <typename... Args>
+using is_constructible = __details_is_constructible<Args...>;
+
+template <typename... Args>
+constexpr bool is_constructible_v = is_constructible<Args...>::value;
+
+
+template <typename T>
+struct __details_is_standard_layout : bool_constant<__is_standard_layout(T)> {};
+template <typename T>
+using is_standard_layout = __details_is_standard_layout<T>;
+template <typename T>
+constexpr bool is_standard_layout_v = is_standard_layout<T>::value;
+
#endif
}
@@ -100,6 +153,30 @@ static_assert(std::is_trivially_copyable_v<int&>);
// expected-note at -1 {{because it is a reference type}}
+// Direct tests
+static_assert(std::is_standard_layout<int>::value);
+static_assert(std::is_standard_layout_v<int>);
+
+static_assert(std::is_standard_layout<int&>::value);
+// expected-error-re at -1 {{static assertion failed due to requirement 'std::{{.*}}is_standard_layout<int &>::value'}} \
+// expected-note at -1 {{'int &' is not standard-layout}} \
+// expected-note at -1 {{because it is a reference type}}
+
+static_assert(std::is_standard_layout_v<int&>);
+// expected-error at -1 {{static assertion failed due to requirement 'std::is_standard_layout_v<int &>'}} \
+// expected-note at -1 {{'int &' is not standard-layout}} \
+// expected-note at -1 {{because it is a reference type}}
+
+
+static_assert(std::is_constructible<int, int>::value);
+
+static_assert(std::is_constructible<void>::value);
+// expected-error-re at -1 {{static assertion failed due to requirement 'std::{{.*}}is_constructible<void>::value'}} \
+// expected-note at -1 {{because it is a cv void type}}
+static_assert(std::is_constructible_v<void>);
+// expected-error at -1 {{static assertion failed due to requirement 'std::is_constructible_v<void>'}} \
+// expected-note at -1 {{because it is a cv void type}}
+
namespace test_namespace {
using namespace std;
static_assert(is_trivially_relocatable<int&>::value);
@@ -119,9 +196,28 @@ namespace test_namespace {
// expected-error at -1 {{static assertion failed due to requirement 'is_trivially_copyable_v<int &>'}} \
// expected-note at -1 {{'int &' is not trivially copyable}} \
// expected-note at -1 {{because it is a reference type}}
+
+ static_assert(is_constructible<void>::value);
+ // expected-error-re at -1 {{static assertion failed due to requirement '{{.*}}is_constructible<void>::value'}} \
+ // expected-note at -1 {{because it is a cv void type}}
+ static_assert(is_constructible_v<void>);
+ // expected-error at -1 {{static assertion failed due to requirement 'is_constructible_v<void>'}} \
+ // expected-note at -1 {{because it is a cv void type}}
+
+ static_assert(is_standard_layout<int&>::value);
+ // expected-error-re at -1 {{static assertion failed due to requirement '{{.*}}is_standard_layout<int &>::value'}} \
+ // expected-note at -1 {{'int &' is not standard-layout}} \
+ // expected-note at -1 {{because it is a reference type}}
+
+ static_assert(is_standard_layout_v<int&>);
+ // expected-error at -1 {{static assertion failed due to requirement 'is_standard_layout_v<int &>'}} \
+ // expected-note at -1 {{'int &' is not standard-layout}} \
+ // expected-note at -1 {{because it is a reference type}}
+
}
+
namespace concepts {
template <typename T>
requires std::is_trivially_relocatable<T>::value void f(); // #cand1
@@ -192,3 +288,5 @@ static_assert(std::is_replaceable_v<int&>);
// expected-error at -1 {{static assertion failed due to requirement 'std::is_replaceable_v<int &>'}} \
// expected-note at -1 {{'int &' is not replaceable}} \
// expected-note at -1 {{because it is a reference type}}
+
+
diff --git a/clang/test/SemaCXX/type-traits-unsatisfied-diags.cpp b/clang/test/SemaCXX/type-traits-unsatisfied-diags.cpp
index a8c78f6304ca9..d3f9b64fa2bfc 100644
--- a/clang/test/SemaCXX/type-traits-unsatisfied-diags.cpp
+++ b/clang/test/SemaCXX/type-traits-unsatisfied-diags.cpp
@@ -488,3 +488,145 @@ static_assert(__is_trivially_copyable(S12));
// expected-note at -1 {{'S12' is not trivially copyable}} \
// expected-note@#tc-S12 {{'S12' defined here}}
}
+
+namespace constructible {
+
+struct S1 { // #c-S1
+ S1(int); // #cc-S1
+};
+static_assert(__is_constructible(S1, char*));
+// expected-error at -1 {{static assertion failed due to requirement '__is_constructible(constructible::S1, char *)'}} \
+// expected-error at -1 {{no matching constructor for initialization of 'S1'}} \
+// expected-note@#c-S1 {{candidate constructor (the implicit copy constructor) not viable: no known conversion from 'char *' to 'const S1' for 1st argument}} \
+// expected-note@#c-S1 {{candidate constructor (the implicit move constructor) not viable: no known conversion from 'char *' to 'S1' for 1st argument}} \
+// expected-note@#cc-S1 {{candidate constructor not viable: no known conversion from 'char *' to 'int' for 1st argument; dereference the argument with *}} \
+// expected-note@#c-S1 {{'S1' defined here}}
+
+struct S2 { // #c-S2
+ S2(int, float, double); // #cc-S2
+};
+static_assert(__is_constructible(S2, float));
+// expected-error at -1 {{static assertion failed due to requirement '__is_constructible(constructible::S2, float)'}} \
+// expected-note@#c-S2 {{candidate constructor (the implicit copy constructor) not viable: no known conversion from 'float' to 'const S2' for 1st argument}} \
+// expected-note@#c-S2 {{candidate constructor (the implicit move constructor) not viable: no known conversion from 'float' to 'S2' for 1st argument}} \
+// expected-error at -1 {{no matching constructor for initialization of 'S2'}} \
+// expected-note@#cc-S2 {{candidate constructor not viable: requires 3 arguments, but 1 was provided}} \
+// expected-note@#c-S2 {{'S2' defined here}}
+
+static_assert(__is_constructible(S2, float, void));
+// expected-error at -1 {{static assertion failed due to requirement '__is_constructible(constructible::S2, float, void)'}} \
+// expected-note@#c-S2 {{candidate constructor (the implicit move constructor) not viable: requires 1 argument, but 2 were provided}} \
+// expected-note@#c-S2 {{candidate constructor (the implicit copy constructor) not viable: requires 1 argument, but 2 were provided}} \
+// expected-note at -1{{because it is a cv void type}} \
+// expected-error at -1 {{no matching constructor for initialization of 'S2'}} \
+// expected-note@#cc-S2 {{candidate constructor not viable: requires 3 arguments, but 2 were provided}} \
+// expected-note@#c-S2 {{'S2' defined here}}
+
+static_assert(__is_constructible(int[]));
+// expected-error at -1 {{static assertion failed due to requirement '__is_constructible(int[])'}} \
+// expected-note at -1 {{because it is an incomplete array type}}
+
+static_assert(__is_constructible(void));
+// expected-error at -1 {{static assertion failed due to requirement '__is_constructible(void)'}} \
+// expected-note at -1 {{because it is a cv void type}}
+
+static_assert(__is_constructible(void, void));
+// expected-error at -1 {{static assertion failed due to requirement '__is_constructible(void, void)'}} \
+// expected-note at -1 {{because it is a cv void type}}
+
+static_assert(__is_constructible(const void));
+// expected-error at -1 {{static assertion failed due to requirement '__is_constructible(const void)'}} \
+// expected-note at -1 {{because it is a cv void type}}
+
+static_assert(__is_constructible(volatile void));
+// expected-error at -1 {{static assertion failed due to requirement '__is_constructible(volatile void)'}} \
+// expected-note at -1 {{because it is a cv void type}}
+
+static_assert(__is_constructible(int ()));
+// expected-error at -1 {{static assertion failed due to requirement '__is_constructible(int ())'}} \
+// expected-note at -1 {{because it is a function type}}
+
+static_assert(__is_constructible(void (int, float)));
+// expected-error at -1 {{static assertion failed due to requirement '__is_constructible(void (int, float))'}} \
+// expected-note at -1 {{because it is a function type}}
+}
+
+namespace standard_layout_tests {
+ struct WithVirtual { // #sl-Virtual
+ virtual void foo();
+};
+static_assert(__is_standard_layout(WithVirtual));
+// expected-error at -1 {{static assertion failed due to requirement '__is_standard_layout(standard_layout_tests::WithVirtual)'}} \
+// expected-note at -1 {{'WithVirtual' is not standard-layout}} \
+// expected-note at -1 {{because it has virtual functions}} \
+// expected-note@#sl-Virtual {{'WithVirtual' defined here}}
+
+struct MixedAccess { // #sl-Mixed
+public:
+ int a;
+private:
+ int b;
+};
+static_assert(__is_standard_layout(MixedAccess));
+// expected-error at -1 {{static assertion failed due to requirement '__is_standard_layout(standard_layout_tests::MixedAccess)'}} \
+// expected-note at -1 {{'MixedAccess' is not standard-layout}} \
+// expected-note at -1 {{because it has mixed access specifiers}} \
+// expected-note@#sl-Mixed {{'MixedAccess' defined here}}
+
+struct VirtualBase { virtual ~VirtualBase(); }; // #sl-VirtualBase
+struct VB : virtual VirtualBase {}; // #sl-VB
+static_assert(__is_standard_layout(VB));
+// expected-error at -1 {{static assertion failed due to requirement '__is_standard_layout(standard_layout_tests::VB)'}} \
+// expected-note at -1 {{'VB' is not standard-layout}} \
+// expected-note at -1 {{because it has a virtual base 'VirtualBase'}} \
+// expected-note at -1 {{because it has a non-standard-layout base 'VirtualBase'}} \
+// expected-note at -1 {{because it has virtual functions}}
+// expected-note@#sl-VB {{'VB' defined here}}
+
+union U { // #sl-U
+public:
+ int x;
+private:
+ int y;
+};
+static_assert(__is_standard_layout(U));
+// expected-error at -1 {{static assertion failed due to requirement '__is_standard_layout(standard_layout_tests::U)'}} \
+// expected-note at -1 {{'U' is not standard-layout}} \
+// expected-note at -1 {{because it has mixed access specifiers}}
+// expected-note@#sl-U {{'U' defined here}}
+
+// Single base class is OK
+struct BaseClass{ int a; }; // #sl-BaseClass
+struct DerivedOK : BaseClass {}; // #sl-DerivedOK
+static_assert(__is_standard_layout(DerivedOK));
+
+// Primitive types should be standard layout
+static_assert(__is_standard_layout(int)); // #sl-Int
+static_assert(__is_standard_layout(float)); // #sl-Float
+
+// Multi-level inheritance: Non-standard layout
+struct Base1 { int a; }; // #sl-Base1
+struct Base2 { int b; }; // #sl-Base2
+struct DerivedClass : Base1, Base2 {}; // #sl-DerivedClass
+static_assert(__is_standard_layout(DerivedClass));
+// expected-error at -1 {{static assertion failed due to requirement '__is_standard_layout(standard_layout_tests::DerivedClass)'}} \
+// expected-note at -1 {{'DerivedClass' is not standard-layout}} \
+// expected-note at -1 {{because it has multiple base classes with data members}} \
+// expected-note@#sl-DerivedClass {{'DerivedClass' defined here}}
+
+// Inheritance hierarchy with multiple classes having data members
+struct BaseA { int a; }; // #sl-BaseA
+struct BaseB : BaseA {}; // inherits BaseA, has no new members
+struct BaseC: BaseB { int c; }; // #sl-BaseC
+static_assert(__is_standard_layout(BaseC));
+// expected-error at -1 {{static assertion failed due to requirement '__is_standard_layout(standard_layout_tests::BaseC)'}} \
+// expected-note at -1 {{'BaseC' is not standard-layout}} \
+// expected-note@#sl-BaseC {{'BaseC' defined here}} \
+
+// Multiple direct base classes with no data members --> standard layout
+struct BaseX {}; // #sl-BaseX
+struct BaseY {}; // #sl-BaseY
+struct MultiBase : BaseX, BaseY {}; // #sl-MultiBase
+static_assert(__is_standard_layout(MultiBase));
+
+}
>From 40ac4232a5a799e08c034afdecc3120741f3bdfa Mon Sep 17 00:00:00 2001
From: Samarth Narang <snarang at umass.edu>
Date: Thu, 12 Jun 2025 22:48:48 -0400
Subject: [PATCH 2/2] Add more descriptive messages for indirect base class
inheritance
---
.../clang/Basic/DiagnosticSemaKinds.td | 1 +
clang/lib/Sema/SemaTypeTraits.cpp | 31 +++++++++++++++++++
.../type-traits-unsatisfied-diags-std.cpp | 3 --
.../SemaCXX/type-traits-unsatisfied-diags.cpp | 1 +
4 files changed, 33 insertions(+), 3 deletions(-)
diff --git a/clang/include/clang/Basic/DiagnosticSemaKinds.td b/clang/include/clang/Basic/DiagnosticSemaKinds.td
index f76f061196416..f12cba7450af0 100644
--- a/clang/include/clang/Basic/DiagnosticSemaKinds.td
+++ b/clang/include/clang/Basic/DiagnosticSemaKinds.td
@@ -1794,6 +1794,7 @@ def note_unsatisfied_trait_reason
"%MultipleDataBase{has multiple base classes with data members}|"
"%VirtualFunction{has virtual functions}|"
"%NonStdLayoutMember{has a non-standard-layout member %1 of type %2}|"
+ "%IndirectBaseWithFields{has an indirect base %1 with data members}|"
"%DeletedDtr{has a %select{deleted|user-provided}1 destructor}|"
"%UserProvidedCtr{has a user provided %select{copy|move}1 "
"constructor}|"
diff --git a/clang/lib/Sema/SemaTypeTraits.cpp b/clang/lib/Sema/SemaTypeTraits.cpp
index 3b216df8ad3f3..2fdbf4e2b5079 100644
--- a/clang/lib/Sema/SemaTypeTraits.cpp
+++ b/clang/lib/Sema/SemaTypeTraits.cpp
@@ -2407,6 +2407,37 @@ static void DiagnoseNonStandardLayoutReason(Sema &SemaRef, SourceLocation Loc,
<< Field->getType() << Field->getSourceRange();
}
}
+
+ // if this class and an indirect base
+ // both have non-static data members, grab the first such base.
+ if (D->hasDirectFields()) {
+ SmallVector<const CXXRecordDecl *, 4> Records;
+
+ // Recursive lambda to collect all bases that declare fields
+ std::function<void(const CXXRecordDecl *)> collect =
+ [&](const CXXRecordDecl *R) {
+ for (const CXXBaseSpecifier &B : R->bases()) {
+ const auto *BR = B.getType()->getAsCXXRecordDecl();
+ if (!BR || !BR->hasDefinition())
+ continue;
+ if (BR->hasDirectFields())
+ Records.push_back(BR);
+ // Recurse into the base class.
+ collect(BR);
+ }
+ };
+
+ // Collect all bases that declare fields.
+ collect(D);
+
+ // If more than one record has fields, then the layout is non-standard.
+ if (!Records.empty()) {
+ const CXXRecordDecl *Indirect = Records.front();
+ SemaRef.Diag(Loc, diag::note_unsatisfied_trait_reason)
+ << diag::TraitNotSatisfiedReason::IndirectBaseWithFields << Indirect
+ << Indirect->getSourceRange();
+ }
+ }
}
static void DiagnoseNonStandardLayoutReason(Sema &SemaRef, SourceLocation Loc,
diff --git a/clang/test/SemaCXX/type-traits-unsatisfied-diags-std.cpp b/clang/test/SemaCXX/type-traits-unsatisfied-diags-std.cpp
index 0f223f372418a..626a0d4fa62b4 100644
--- a/clang/test/SemaCXX/type-traits-unsatisfied-diags-std.cpp
+++ b/clang/test/SemaCXX/type-traits-unsatisfied-diags-std.cpp
@@ -217,7 +217,6 @@ namespace test_namespace {
}
-
namespace concepts {
template <typename T>
requires std::is_trivially_relocatable<T>::value void f(); // #cand1
@@ -288,5 +287,3 @@ static_assert(std::is_replaceable_v<int&>);
// expected-error at -1 {{static assertion failed due to requirement 'std::is_replaceable_v<int &>'}} \
// expected-note at -1 {{'int &' is not replaceable}} \
// expected-note at -1 {{because it is a reference type}}
-
-
diff --git a/clang/test/SemaCXX/type-traits-unsatisfied-diags.cpp b/clang/test/SemaCXX/type-traits-unsatisfied-diags.cpp
index d3f9b64fa2bfc..9a934187a13ee 100644
--- a/clang/test/SemaCXX/type-traits-unsatisfied-diags.cpp
+++ b/clang/test/SemaCXX/type-traits-unsatisfied-diags.cpp
@@ -621,6 +621,7 @@ struct BaseC: BaseB { int c; }; // #sl-BaseC
static_assert(__is_standard_layout(BaseC));
// expected-error at -1 {{static assertion failed due to requirement '__is_standard_layout(standard_layout_tests::BaseC)'}} \
// expected-note at -1 {{'BaseC' is not standard-layout}} \
+// expected-note at -1 {{because it has an indirect base 'BaseA' with data members}} \
// expected-note@#sl-BaseC {{'BaseC' defined here}} \
// Multiple direct base classes with no data members --> standard layout
More information about the cfe-commits
mailing list