[clang] [Clang][RFC] Intrododuce a builtin to determine the structure binding size (PR #131515)
via cfe-commits
cfe-commits at lists.llvm.org
Mon Mar 17 01:48:50 PDT 2025
https://github.com/cor3ntin updated https://github.com/llvm/llvm-project/pull/131515
>From ae66e1cc48c721badc234ff5bc5a89aeb6cd2ea3 Mon Sep 17 00:00:00 2001
From: Corentin Jabot <corentinjabot at gmail.com>
Date: Sun, 16 Mar 2025 14:04:15 +0100
Subject: [PATCH 1/4] [Clang][RFC] Intrododuce a builtin to determine the
number of bindings that would be produced by
```cpp
auto [...p] = expr;
```
This is necessary to implement P2300 (https://eel.is/c++draft/exec#snd.concepts-5),
but can also be used to implement a general get<N>
function that supports aggregates
__builtin_structured_binding_size works like sizeof in that it supports both
type and expression arguments.
If the argument cannot be destructured, a sfinae-friendly error is produced.
A type is considered a valid tuple if `std::tuple_size_v<T>`
is a valid expression, even if there is no valid `std::tuple_element`
specialization or suitable `get` function for that type.
This is modeled as a UnaryExprOrTypeTraitExpr, but it is wrapped
in a ConstantExpr because the structured binding size can only be
established during sema.
---
clang/docs/LanguageExtensions.rst | 30 ++++
clang/docs/ReleaseNotes.rst | 3 +
.../clang/Basic/DiagnosticSemaKinds.td | 2 +
clang/include/clang/Basic/TokenKinds.def | 2 +-
clang/include/clang/Sema/Sema.h | 3 +-
clang/lib/AST/ByteCode/Compiler.cpp | 2 +
clang/lib/AST/ExprConstant.cpp | 5 +
clang/lib/AST/ItaniumMangle.cpp | 9 +
clang/lib/Parse/ParseExpr.cpp | 13 +-
clang/lib/Sema/SemaDeclCXX.cpp | 123 +++++++++----
clang/lib/Sema/SemaExpr.cpp | 69 ++++++-
clang/test/CodeGenCXX/builtins.cpp | 6 +
.../mangle-structured-binding-size.cpp | 12 ++
.../builtin-structured-binding-size.cpp | 168 ++++++++++++++++++
14 files changed, 405 insertions(+), 42 deletions(-)
create mode 100644 clang/test/CodeGenCXX/mangle-structured-binding-size.cpp
create mode 100644 clang/test/SemaCXX/builtin-structured-binding-size.cpp
diff --git a/clang/docs/LanguageExtensions.rst b/clang/docs/LanguageExtensions.rst
index cc12ff5bad353..9a5cd8f1e5f5d 100644
--- a/clang/docs/LanguageExtensions.rst
+++ b/clang/docs/LanguageExtensions.rst
@@ -434,6 +434,36 @@ __datasizeof
``__datasizeof`` behaves like ``sizeof``, except that it returns the size of the
type ignoring tail padding.
+.. _builtin_structured_binding_size-doc:
+
+__builtin_structured_binding_size (C++)
+---------------------------------------
+``__builtin_structured_binding_size`` returns the *structured binding size*
+([dcl.struct.bind]) of the type ``T`` (or unevaluate expression ``arg``)
+passed as argument.
+
+This is equivalent to the size of the pack ``p`` in ``auto&& [...p] = arg;``.
+If the argument is not destructurable (ie not an array, vector, complex,
+*tuple-like* type or destructurable class type), ``__builtin_structured_binding_size(T)``
+is not a valid expression (``__builtin_structured_binding_size`` is SFINEA-friendly).
+
+A type is considered a valid tuple if ``std::tuple_size_v<T>`` is a valid expression,
+even if there is no valid ``std::tuple_element`` specialization or suitable
+``get`` function for that type.
+
+.. code-block:: c++
+
+ template<std::size_t Idx, typename T>
+ requires (Idx < __builtin_structured_binding_size(T))
+ decltype(auto) constexpr get_binding(T&& obj) {
+ auto && [...p] = std::forward<T>(obj);
+ return p...[Idx];
+ }
+ struct S { int a = 0, b = 42; };
+ static_assert(__builtin_structured_binding_size(S) == 2);
+ static_assert(get_binding<1>(S{}) == 42);
+
+
_BitInt, _ExtInt
----------------
diff --git a/clang/docs/ReleaseNotes.rst b/clang/docs/ReleaseNotes.rst
index 2a1c5ee2d788e..f49e389773e4e 100644
--- a/clang/docs/ReleaseNotes.rst
+++ b/clang/docs/ReleaseNotes.rst
@@ -74,6 +74,9 @@ What's New in Clang |release|?
C++ Language Changes
--------------------
+- Added a :ref:`__builtin_structured_binding_size <builtin_structured_binding_size-doc>` (T)
+ builtin that returns the number of structured bindings that would be produced by destructuring ``T``.
+
C++2c Feature Support
^^^^^^^^^^^^^^^^^^^^^
diff --git a/clang/include/clang/Basic/DiagnosticSemaKinds.td b/clang/include/clang/Basic/DiagnosticSemaKinds.td
index 86c9c955c1c78..fad826c1c6336 100644
--- a/clang/include/clang/Basic/DiagnosticSemaKinds.td
+++ b/clang/include/clang/Basic/DiagnosticSemaKinds.td
@@ -591,6 +591,8 @@ def err_decomp_decl_std_tuple_size_not_constant : Error<
"is not a valid integral constant expression">;
def note_in_binding_decl_init : Note<
"in implicit initialization of binding declaration %0">;
+def err_arg_is_not_destructurable : Error<
+ "type %0 is not destructurable">;
def err_std_type_trait_not_class_template : Error<
"unsupported standard library implementation: "
diff --git a/clang/include/clang/Basic/TokenKinds.def b/clang/include/clang/Basic/TokenKinds.def
index 397a5d95709fb..bad9387673ef9 100644
--- a/clang/include/clang/Basic/TokenKinds.def
+++ b/clang/include/clang/Basic/TokenKinds.def
@@ -553,8 +553,8 @@ TYPE_TRAIT_2(__reference_converts_from_temporary, ReferenceConvertsFromTemporary
// IsDeducible is only used internally by clang for CTAD implementation and
// is not exposed to users.
TYPE_TRAIT_2(/*EmptySpellingName*/, IsDeducible, KEYCXX)
-
TYPE_TRAIT_1(__is_bitwise_cloneable, IsBitwiseCloneable, KEYALL)
+UNARY_EXPR_OR_TYPE_TRAIT(__builtin_structured_binding_size, StructuredBindingSize, KEYCXX)
// Embarcadero Expression Traits
EXPRESSION_TRAIT(__is_lvalue_expr, IsLValueExpr, KEYCXX)
diff --git a/clang/include/clang/Sema/Sema.h b/clang/include/clang/Sema/Sema.h
index 657350fa843b9..cd7078b119712 100644
--- a/clang/include/clang/Sema/Sema.h
+++ b/clang/include/clang/Sema/Sema.h
@@ -6095,7 +6095,8 @@ class Sema final : public SemaBase {
RecordDecl *ClassDecl,
const IdentifierInfo *Name);
- unsigned GetDecompositionElementCount(QualType DecompType);
+ std::optional<unsigned int> GetDecompositionElementCount(QualType DecompType,
+ SourceLocation Loc);
void CheckCompleteDecompositionDeclaration(DecompositionDecl *DD);
/// Stack containing information needed when in C++2a an 'auto' is encountered
diff --git a/clang/lib/AST/ByteCode/Compiler.cpp b/clang/lib/AST/ByteCode/Compiler.cpp
index b9f88230007b5..0259605086b21 100644
--- a/clang/lib/AST/ByteCode/Compiler.cpp
+++ b/clang/lib/AST/ByteCode/Compiler.cpp
@@ -2154,6 +2154,8 @@ bool Compiler<Emitter>::VisitUnaryExprOrTypeTraitExpr(
E->getArgumentType()),
E);
}
+ assert(Kind != UETT_StructuredBindingSize &&
+ "should have been evaluated in Sema");
return false;
}
diff --git a/clang/lib/AST/ExprConstant.cpp b/clang/lib/AST/ExprConstant.cpp
index f8e8aaddbfdbd..1763bbc18043d 100644
--- a/clang/lib/AST/ExprConstant.cpp
+++ b/clang/lib/AST/ExprConstant.cpp
@@ -14878,6 +14878,11 @@ bool IntExprEvaluator::VisitUnaryExprOrTypeTraitExpr(
}
return Success(Sizeof, E);
}
+ case UETT_StructuredBindingSize:
+ // This can only be computed from Sema and has been cached.
+ // We can still get there from code that strips the outer ConstantExpr.
+ return false;
+
case UETT_OpenMPRequiredSimdAlign:
assert(E->isArgumentType());
return Success(
diff --git a/clang/lib/AST/ItaniumMangle.cpp b/clang/lib/AST/ItaniumMangle.cpp
index b6ba36784f38a..12993d5cb35f1 100644
--- a/clang/lib/AST/ItaniumMangle.cpp
+++ b/clang/lib/AST/ItaniumMangle.cpp
@@ -5389,6 +5389,15 @@ void CXXNameMangler::mangleExpression(const Expr *E, unsigned Arity,
Diags.Report(DiagID);
return;
}
+ case UETT_StructuredBindingSize:
+ Out << "u11__builtin_structured_binding_size";
+ if (SAE->isArgumentType())
+ mangleType(SAE->getArgumentType());
+ else
+ mangleTemplateArgExpr(SAE->getArgumentExpr());
+ Out << 'E';
+ break;
+ return;
}
break;
}
diff --git a/clang/lib/Parse/ParseExpr.cpp b/clang/lib/Parse/ParseExpr.cpp
index 0c28972d6ed8f..2e3f0ce3194f5 100644
--- a/clang/lib/Parse/ParseExpr.cpp
+++ b/clang/lib/Parse/ParseExpr.cpp
@@ -1544,6 +1544,7 @@ ExprResult Parser::ParseCastExpression(CastParseKind ParseKind,
// unary-expression: '__builtin_omp_required_simd_align' '(' type-name ')'
case tok::kw___builtin_omp_required_simd_align:
case tok::kw___builtin_vectorelements:
+ case tok::kw___builtin_structured_binding_size:
if (NotPrimaryExpression)
*NotPrimaryExpression = true;
AllowSuffix = false;
@@ -2463,7 +2464,8 @@ Parser::ParseExprAfterUnaryExprOrTypeTrait(const Token &OpTok,
tok::kw___datasizeof, tok::kw___alignof, tok::kw_alignof,
tok::kw__Alignof, tok::kw_vec_step,
tok::kw___builtin_omp_required_simd_align,
- tok::kw___builtin_vectorelements) &&
+ tok::kw___builtin_vectorelements,
+ tok::kw___builtin_structured_binding_size) &&
"Not a typeof/sizeof/alignof/vec_step expression!");
ExprResult Operand;
@@ -2473,7 +2475,8 @@ Parser::ParseExprAfterUnaryExprOrTypeTrait(const Token &OpTok,
// If construct allows a form without parenthesis, user may forget to put
// pathenthesis around type name.
if (OpTok.isOneOf(tok::kw_sizeof, tok::kw___datasizeof, tok::kw___alignof,
- tok::kw_alignof, tok::kw__Alignof)) {
+ tok::kw_alignof, tok::kw__Alignof,
+ tok::kw___builtin_structured_binding_size)) {
if (isTypeIdUnambiguously()) {
DeclSpec DS(AttrFactory);
ParseSpecifierQualifierList(DS);
@@ -2599,7 +2602,8 @@ ExprResult Parser::ParseUnaryExprOrTypeTraitExpression() {
assert(Tok.isOneOf(tok::kw_sizeof, tok::kw___datasizeof, tok::kw___alignof,
tok::kw_alignof, tok::kw__Alignof, tok::kw_vec_step,
tok::kw___builtin_omp_required_simd_align,
- tok::kw___builtin_vectorelements) &&
+ tok::kw___builtin_vectorelements,
+ tok::kw___builtin_structured_binding_size) &&
"Not a sizeof/alignof/vec_step expression!");
Token OpTok = Tok;
ConsumeToken();
@@ -2687,6 +2691,9 @@ ExprResult Parser::ParseUnaryExprOrTypeTraitExpression() {
case tok::kw___datasizeof:
ExprKind = UETT_DataSizeOf;
break;
+ case tok::kw___builtin_structured_binding_size:
+ ExprKind = UETT_StructuredBindingSize;
+ break;
case tok::kw___builtin_vectorelements:
ExprKind = UETT_VectorElements;
break;
diff --git a/clang/lib/Sema/SemaDeclCXX.cpp b/clang/lib/Sema/SemaDeclCXX.cpp
index a02bd8335fa20..164e81e1cfa61 100644
--- a/clang/lib/Sema/SemaDeclCXX.cpp
+++ b/clang/lib/Sema/SemaDeclCXX.cpp
@@ -1475,6 +1475,48 @@ static DeclAccessPair findDecomposableBaseClass(Sema &S, SourceLocation Loc,
return DeclAccessPair::make(const_cast<CXXRecordDecl*>(ClassWithFields), AS);
}
+static bool CheckMemberDecompositionFields(Sema &S, SourceLocation Loc,
+ const CXXRecordDecl *OrigRD,
+ QualType DecompType,
+ DeclAccessPair BasePair) {
+ const CXXRecordDecl *RD = cast_or_null<CXXRecordDecl>(BasePair.getDecl());
+ if (!RD)
+ return true;
+
+ for (auto *FD : RD->fields()) {
+ if (FD->isUnnamedBitField())
+ continue;
+
+ // All the non-static data members are required to be nameable, so they
+ // must all have names.
+ if (!FD->getDeclName()) {
+ if (RD->isLambda()) {
+ S.Diag(Loc, diag::err_decomp_decl_lambda);
+ S.Diag(RD->getLocation(), diag::note_lambda_decl);
+ return true;
+ }
+
+ if (FD->isAnonymousStructOrUnion()) {
+ S.Diag(Loc, diag::err_decomp_decl_anon_union_member)
+ << DecompType << FD->getType()->isUnionType();
+ S.Diag(FD->getLocation(), diag::note_declared_at);
+ return true;
+ }
+
+ // FIXME: Are there any other ways we could have an anonymous member?
+ }
+ // The field must be accessible in the context of the structured binding.
+ // We already checked that the base class is accessible.
+ // FIXME: Add 'const' to AccessedEntity's classes so we can remove the
+ // const_cast here.
+ S.CheckStructuredBindingMemberAccess(
+ Loc, const_cast<CXXRecordDecl *>(OrigRD),
+ DeclAccessPair::make(FD, CXXRecordDecl::MergeAccess(
+ BasePair.getAccess(), FD->getAccess())));
+ }
+ return false;
+}
+
static bool checkMemberDecomposition(Sema &S, ArrayRef<BindingDecl*> Bindings,
ValueDecl *Src, QualType DecompType,
const CXXRecordDecl *OrigRD) {
@@ -1503,43 +1545,20 @@ static bool checkMemberDecomposition(Sema &S, ArrayRef<BindingDecl*> Bindings,
auto FlatBindings = DD->flat_bindings();
assert(llvm::range_size(FlatBindings) == NumFields);
auto FlatBindingsItr = FlatBindings.begin();
+
+ if (CheckMemberDecompositionFields(S, Src->getLocation(), OrigRD, DecompType,
+ BasePair))
+ return true;
+
for (auto *FD : RD->fields()) {
if (FD->isUnnamedBitField())
continue;
- // All the non-static data members are required to be nameable, so they
- // must all have names.
- if (!FD->getDeclName()) {
- if (RD->isLambda()) {
- S.Diag(Src->getLocation(), diag::err_decomp_decl_lambda);
- S.Diag(RD->getLocation(), diag::note_lambda_decl);
- return true;
- }
-
- if (FD->isAnonymousStructOrUnion()) {
- S.Diag(Src->getLocation(), diag::err_decomp_decl_anon_union_member)
- << DecompType << FD->getType()->isUnionType();
- S.Diag(FD->getLocation(), diag::note_declared_at);
- return true;
- }
-
- // FIXME: Are there any other ways we could have an anonymous member?
- }
-
// We have a real field to bind.
assert(FlatBindingsItr != FlatBindings.end());
BindingDecl *B = *(FlatBindingsItr++);
SourceLocation Loc = B->getLocation();
- // The field must be accessible in the context of the structured binding.
- // We already checked that the base class is accessible.
- // FIXME: Add 'const' to AccessedEntity's classes so we can remove the
- // const_cast here.
- S.CheckStructuredBindingMemberAccess(
- Loc, const_cast<CXXRecordDecl *>(OrigRD),
- DeclAccessPair::make(FD, CXXRecordDecl::MergeAccess(
- BasePair.getAccess(), FD->getAccess())));
-
// Initialize the binding to Src.FD.
ExprResult E = S.BuildDeclRefExpr(Src, DecompType, VK_LValue, Loc);
if (E.isInvalid())
@@ -1642,6 +1661,50 @@ void Sema::CheckCompleteDecompositionDeclaration(DecompositionDecl *DD) {
DD->setInvalidDecl();
}
+std::optional<unsigned> Sema::GetDecompositionElementCount(QualType T,
+ SourceLocation Loc) {
+ const ASTContext &Ctx = getASTContext();
+ assert(!T->isDependentType());
+ if (auto *CAT = Ctx.getAsConstantArrayType(T))
+ return CAT->getSize().getZExtValue();
+ if (auto *VT = T->getAs<VectorType>())
+ return VT->getNumElements();
+ if (T->getAs<ComplexType>())
+ return 2;
+
+ llvm::APSInt TupleSize(Ctx.getTypeSize(Ctx.getSizeType()));
+ switch (isTupleLike(*this, Loc, T, TupleSize)) {
+ case IsTupleLike::Error:
+ return {};
+ case IsTupleLike::TupleLike:
+ return TupleSize.getExtValue();
+ case IsTupleLike::NotTupleLike:
+ break;
+ }
+ CXXRecordDecl *OrigRD = T->getAsCXXRecordDecl();
+ if (!OrigRD || OrigRD->isUnion()) {
+ return std::nullopt;
+ }
+
+ if (RequireCompleteType(Loc, T, diag::err_incomplete_type))
+ return std::nullopt;
+
+ CXXCastPath BasePath;
+ DeclAccessPair BasePair =
+ findDecomposableBaseClass(*this, Loc, OrigRD, BasePath);
+ const CXXRecordDecl *RD = cast_or_null<CXXRecordDecl>(BasePair.getDecl());
+ if (!RD)
+ return std::nullopt;
+
+ unsigned NumFields = llvm::count_if(
+ RD->fields(), [](FieldDecl *FD) { return !FD->isUnnamedBitField(); });
+
+ if (CheckMemberDecompositionFields(*this, Loc, OrigRD, T, BasePair))
+ return true;
+
+ return NumFields;
+}
+
void Sema::MergeVarDeclExceptionSpecs(VarDecl *New, VarDecl *Old) {
// Shortcut if exceptions are disabled.
if (!getLangOpts().CXXExceptions)
@@ -17262,8 +17325,8 @@ void Sema::DiagnoseStaticAssertDetails(const Expr *E) {
Expr::EvalResult Result;
SmallString<12> ValueString;
bool Print;
- } DiagSide[2] = {{LHS, Expr::EvalResult(), {}, false},
- {RHS, Expr::EvalResult(), {}, false}};
+ } DiagSide[2] = {{Op->getLHS(), Expr::EvalResult(), {}, false},
+ {Op->getRHS(), Expr::EvalResult(), {}, false}};
for (unsigned I = 0; I < 2; I++) {
const Expr *Side = DiagSide[I].Cond;
diff --git a/clang/lib/Sema/SemaExpr.cpp b/clang/lib/Sema/SemaExpr.cpp
index e19136b394800..8766331a0df59 100644
--- a/clang/lib/Sema/SemaExpr.cpp
+++ b/clang/lib/Sema/SemaExpr.cpp
@@ -4160,6 +4160,54 @@ static bool CheckVecStepTraitOperandType(Sema &S, QualType T,
return false;
}
+static ExprResult BuildStructuredBindingSizeTraitImpl(Sema &S, QualType T,
+ Expr *E,
+ TypeSourceInfo *TInfo,
+ SourceLocation Loc,
+ SourceRange ArgRange) {
+ assert(!!E != !!TInfo);
+ assert(!T->isDependentType());
+ std::optional<unsigned> Size =
+ S.GetDecompositionElementCount(T, ArgRange.getBegin());
+ if (!Size) {
+ return S.Diag(Loc, diag::err_arg_is_not_destructurable) << T << ArgRange;
+ return ExprError();
+ }
+ Expr *Inner;
+ if (E)
+ Inner = new (S.getASTContext()) UnaryExprOrTypeTraitExpr(
+ UnaryExprOrTypeTrait::UETT_StructuredBindingSize, E,
+ S.getASTContext().getSizeType(), Loc, E->getEndLoc());
+
+ else
+ Inner = new (S.getASTContext()) UnaryExprOrTypeTraitExpr(
+ UnaryExprOrTypeTrait::UETT_StructuredBindingSize, TInfo,
+ S.getASTContext().getSizeType(), Loc, ArgRange.getEnd());
+
+ // Computing the number of bindings requires Sema and is non-trivial,
+ // so we cache the result now.
+ llvm::APSInt V =
+ S.getASTContext().MakeIntValue(*Size, S.getASTContext().getSizeType());
+ return ConstantExpr::Create(S.getASTContext(), Inner, APValue{V});
+}
+
+static ExprResult BuildStructuredBindingSizeTrait(Sema &S,
+ TypeSourceInfo *TInfo,
+ SourceLocation Loc,
+ SourceRange ArgRange) {
+ return BuildStructuredBindingSizeTraitImpl(S, TInfo->getType(),
+ /*Expr=*/nullptr, TInfo, Loc,
+ ArgRange);
+}
+
+static ExprResult BuildStructuredBindingSizeTrait(Sema &S, SourceLocation OpLoc,
+ Expr *E) {
+
+ return BuildStructuredBindingSizeTraitImpl(S, E->getType(), E,
+ /*TInfo=*/nullptr, OpLoc,
+ E->getSourceRange());
+}
+
static bool CheckVectorElementsTraitOperandType(Sema &S, QualType T,
SourceLocation Loc,
SourceRange ArgRange) {
@@ -4650,10 +4698,14 @@ ExprResult Sema::CreateUnaryExprOrTypeTraitExpr(TypeSourceInfo *TInfo,
QualType T = TInfo->getType();
- if (!T->isDependentType() &&
- CheckUnaryExprOrTypeTraitOperand(T, OpLoc, R, ExprKind,
- getTraitSpelling(ExprKind)))
- return ExprError();
+ if (!T->isDependentType()) {
+ if (ExprKind == UETT_StructuredBindingSize)
+ return BuildStructuredBindingSizeTrait(*this, TInfo, OpLoc, R);
+
+ if (CheckUnaryExprOrTypeTraitOperand(T, OpLoc, R, ExprKind,
+ getTraitSpelling(ExprKind)))
+ return ExprError();
+ }
// Adds overload of TransformToPotentiallyEvaluated for TypeSourceInfo to
// properly deal with VLAs in nested calls of sizeof and typeof.
@@ -4680,14 +4732,17 @@ Sema::CreateUnaryExprOrTypeTraitExpr(Expr *E, SourceLocation OpLoc,
bool isInvalid = false;
if (E->isTypeDependent()) {
// Delay type-checking for type-dependent expressions.
+ } else if (ExprKind == UETT_StructuredBindingSize) {
+ // Custom logic
+ return BuildStructuredBindingSizeTrait(*this, OpLoc, E);
} else if (ExprKind == UETT_AlignOf || ExprKind == UETT_PreferredAlignOf) {
isInvalid = CheckAlignOfExpr(*this, E, ExprKind);
} else if (ExprKind == UETT_VecStep) {
isInvalid = CheckVecStepExpr(E);
} else if (ExprKind == UETT_OpenMPRequiredSimdAlign) {
- Diag(E->getExprLoc(), diag::err_openmp_default_simd_align_expr);
- isInvalid = true;
- } else if (E->refersToBitField()) { // C99 6.5.3.4p1.
+ Diag(E->getExprLoc(), diag::err_openmp_default_simd_align_expr);
+ isInvalid = true;
+ } else if (E->refersToBitField()) { // C99 6.5.3.4p1.
Diag(E->getExprLoc(), diag::err_sizeof_alignof_typeof_bitfield) << 0;
isInvalid = true;
} else if (ExprKind == UETT_VectorElements) {
diff --git a/clang/test/CodeGenCXX/builtins.cpp b/clang/test/CodeGenCXX/builtins.cpp
index 37f9491d12d04..9169f3a3276d3 100644
--- a/clang/test/CodeGenCXX/builtins.cpp
+++ b/clang/test/CodeGenCXX/builtins.cpp
@@ -77,3 +77,9 @@ int constexpr_overflow_result() {
// CHECK: [[RET_VAL:%.+]] = load i32, ptr [[Z]]
// CHECK: ret i32 [[RET_VAL]]
}
+
+int structured_binding_size() {
+ struct S2 {int a, b;};
+ return __builtin_structured_binding_size(S2);
+ // CHECK: ret i32 2
+}
diff --git a/clang/test/CodeGenCXX/mangle-structured-binding-size.cpp b/clang/test/CodeGenCXX/mangle-structured-binding-size.cpp
new file mode 100644
index 0000000000000..5b53ed8d7166d
--- /dev/null
+++ b/clang/test/CodeGenCXX/mangle-structured-binding-size.cpp
@@ -0,0 +1,12 @@
+// RUN: %clang_cc1 -std=c++11 -emit-llvm %s -o - -triple=x86_64-linux-gnu | FileCheck %s
+
+struct S {};
+
+template <class T> void f1(decltype(__builtin_structured_binding_size(T))) {}
+template void f1<S>(__SIZE_TYPE__);
+// CHECK: void @_Z2f1I1SEvDTu11__builtin_structured_binding_sizeT_EE
+
+template <class T> void f2(decltype(__builtin_structured_binding_size(T{}))) {}
+template void f2<S>(__SIZE_TYPE__);
+// CHECK: void @_Z2f2I1SEvDTu11__builtin_structured_binding_sizeXtlT_EEEE
+
diff --git a/clang/test/SemaCXX/builtin-structured-binding-size.cpp b/clang/test/SemaCXX/builtin-structured-binding-size.cpp
new file mode 100644
index 0000000000000..460f2881ff5dc
--- /dev/null
+++ b/clang/test/SemaCXX/builtin-structured-binding-size.cpp
@@ -0,0 +1,168 @@
+// RUN: %clang_cc1 %s -std=c++2c -fsyntax-only -verify
+
+struct S0 {};
+struct S1 {int a;};
+struct S2 {int a; int b;};
+struct S3 {double a; int b; int c;};
+
+
+
+struct SD : S1 {};
+struct SE1 : S1 { int b;};
+
+class P1 {int a;}; // #note-private
+
+
+template <typename T>
+concept is_destructurable = requires {
+ { __builtin_structured_binding_size(T) };
+};
+
+static_assert(__builtin_structured_binding_size(S0) == 0);
+static_assert(__is_same_as(decltype(__builtin_structured_binding_size(S0)), decltype(sizeof(void*))));
+
+static_assert(__builtin_structured_binding_size(S1) == 0);
+// expected-error at -1 {{static assertion failed due to requirement '__builtin_structured_binding_size(S1) == 0'}} \
+// expected-note at -1 {{expression evaluates to '1 == 0'}}
+static_assert(__builtin_structured_binding_size(S1) == 1);
+static_assert(__builtin_structured_binding_size(SD) == 1);
+static_assert(__builtin_structured_binding_size(SE1) == 1);
+// expected-error at -1 {{cannot decompose class type 'SE1': both it and its base class 'S1' have non-static data members}} \
+// expected-error at -1 {{type 'SE1' is not destructurable}}
+
+
+static_assert(__builtin_structured_binding_size(int[0]) == 0);
+static_assert(__builtin_structured_binding_size(int[1]) == 1);
+static_assert(__builtin_structured_binding_size(int[42]) == 42);
+
+using vec2 = int __attribute__((__vector_size__(2 * sizeof(int))));
+using vec3 = int __attribute__((__vector_size__(3 * sizeof(int))));
+static_assert(__builtin_structured_binding_size(vec2) == 2);
+static_assert(__builtin_structured_binding_size(vec3) == 3);
+static_assert(__builtin_structured_binding_size(__builtin_complex(0., 0.)) == 2);
+static_assert(__builtin_structured_binding_size(decltype(__builtin_complex(0., 0.))) == 2);
+
+
+int VLASize; // expected-note {{declared here}}
+static_assert(__builtin_structured_binding_size(int[VLASize]) == 42);
+// expected-error at -1 {{type 'int[VLASize]' is not destructurable}} \
+// expected-warning at -1 {{variable length arrays in C++ are a Clang extension}} \
+// expected-note at -1 {{read of non-const variable 'VLASize' is not allowed in a constant expression}}
+
+
+struct Incomplete; // expected-note {{forward declaration of 'Incomplete'}}
+static_assert(__builtin_structured_binding_size(Incomplete) == 1);
+// expected-error at -1 {{incomplete type 'Incomplete' where a complete type is required}} \
+// expected-error at -1 {{type 'Incomplete' is not destructurable}}
+static_assert(__builtin_structured_binding_size(Incomplete[]) == 1);
+// expected-error at -1 {{type 'Incomplete[]' is not destructurable}}
+static_assert(__builtin_structured_binding_size(Incomplete[0]) == 0);
+static_assert(__builtin_structured_binding_size(Incomplete[1]) == 1);
+static_assert(__builtin_structured_binding_size(Incomplete[42]) == 42);
+
+
+static_assert(__builtin_structured_binding_size(P1) == 0);
+// expected-error at -1 {{static assertion failed due to requirement '__builtin_structured_binding_size(P1) == 0'}} \
+// expected-note at -1 {{expression evaluates to '1 == 0'}} \
+// expected-error at -1 {{cannot decompose private member 'a' of 'P1}} \
+// expected-note@#note-private {{implicitly declared private here}}
+
+
+static_assert(is_destructurable<S0>);
+static_assert(is_destructurable<S1>);
+static_assert(!is_destructurable<SE1>);
+static_assert(!is_destructurable<int>);
+static_assert(!is_destructurable<int[]>);
+static_assert(is_destructurable<int[1]>);
+static_assert(!is_destructurable<P1>);
+
+template <typename T>
+constexpr int f() {return 0;};
+template <typename T>
+requires is_destructurable<T>
+constexpr int f() {return 1;};
+
+static_assert(f<int>() == 0);
+static_assert(f<S0>() == 1);
+
+struct T0;
+struct T1;
+struct T42;
+struct TSizeError;
+
+namespace std {
+
+template <typename>
+struct tuple_size;
+
+template <>
+struct tuple_size<T0> {
+ static constexpr int value = 0;
+};
+
+template <>
+struct tuple_size<T1> {
+ static constexpr int value = 1;
+};
+
+template <>
+struct tuple_size<T42> {
+ static constexpr int value = 42;
+};
+
+template <>
+struct tuple_size<TSizeError> {
+ static constexpr void* value = nullptr;
+};
+
+static_assert(__builtin_structured_binding_size(T0) == 0);
+static_assert(__builtin_structured_binding_size(T1) == 1);
+static_assert(__builtin_structured_binding_size(T42) == 42);
+static_assert(__builtin_structured_binding_size(TSizeError) == 42);
+// expected-error at -1 {{cannot decompose this type; 'std::tuple_size<TSizeError>::value' is not a valid integral constant expression}} \
+// expected-error at -1 {{type 'TSizeError' is not destructurable}}
+static_assert(!is_destructurable<TSizeError>);
+}
+
+
+void test_expr(S1 & s1, S2 && s2, T0 & t0, int i, const S1 & s1c, int arr[2]) {
+ static_assert(__builtin_structured_binding_size(s1) == 1);
+ static_assert(__builtin_structured_binding_size(s1c) == 1);
+ static_assert(__builtin_structured_binding_size(s2) == 2);
+ static_assert(__builtin_structured_binding_size(t0) == 0);
+ static_assert(__builtin_structured_binding_size(i));
+ // expected-error at -1 {{type 'int' is not destructurable}}
+ static_assert(__builtin_structured_binding_size(arr) == 1);
+ // expected-error at -1 {{type 'int *' is not destructurable}}
+}
+
+
+// Check we can implement std::exec::tag_of_t
+template <typename T>
+struct type_identity {
+ using type = T;
+};
+template<typename T> T &&declval();
+
+template <typename T>
+requires (__builtin_structured_binding_size(T) >=2)
+consteval auto tag_of_impl(T& t) {
+ auto && [tag, ..._] = t;
+ return type_identity<decltype(auto(tag))>{};
+}
+
+template <typename T>
+requires (__builtin_structured_binding_size(T) >=2) // #tag-of-constr
+using tag_of_t = decltype(tag_of_impl(declval<T&>()))::type;
+
+static_assert(__is_same_as(tag_of_t<S2>, int));
+static_assert(__is_same_as(tag_of_t<S3>, double));
+
+
+static_assert(__is_same_as(tag_of_t<S1>, int));
+// expected-error at -1 {{constraints not satisfied for alias template 'tag_of_t' [with T = S1]}} \
+// expected-note@#tag-of-constr {{because '__builtin_structured_binding_size(S1) >= 2' (1 >= 2) evaluated to false}}
+
+static_assert(__is_same_as(tag_of_t<int>, int)); // error
+// expected-error at -1 {{constraints not satisfied for alias template 'tag_of_t' [with T = int]}}
+// expected-note@#tag-of-constr {{because substituted constraint expression is ill-formed: type 'int' is not destructurable}}
>From f706e54760b5527e2b5749d29af0913a346e7695 Mon Sep 17 00:00:00 2001
From: Corentin Jabot <corentinjabot at gmail.com>
Date: Sun, 16 Mar 2025 15:16:32 +0100
Subject: [PATCH 2/4] fix tests
---
clang/include/clang/AST/Stmt.h | 2 +-
clang/lib/Sema/SemaDeclCXX.cpp | 12 ++++++++++--
2 files changed, 11 insertions(+), 3 deletions(-)
diff --git a/clang/include/clang/AST/Stmt.h b/clang/include/clang/AST/Stmt.h
index 604ac51d478cf..476aa2c8c393a 100644
--- a/clang/include/clang/AST/Stmt.h
+++ b/clang/include/clang/AST/Stmt.h
@@ -531,7 +531,7 @@ class alignas(void *) Stmt {
unsigned : NumExprBits;
LLVM_PREFERRED_TYPE(UnaryExprOrTypeTrait)
- unsigned Kind : 3;
+ unsigned Kind : 4;
LLVM_PREFERRED_TYPE(bool)
unsigned IsType : 1; // true if operand is a type, false if an expression.
};
diff --git a/clang/lib/Sema/SemaDeclCXX.cpp b/clang/lib/Sema/SemaDeclCXX.cpp
index 164e81e1cfa61..53ca94011f158 100644
--- a/clang/lib/Sema/SemaDeclCXX.cpp
+++ b/clang/lib/Sema/SemaDeclCXX.cpp
@@ -17320,13 +17320,21 @@ void Sema::DiagnoseStaticAssertDetails(const Expr *E) {
if (!UsefulToPrintExpr(LHS) && !UsefulToPrintExpr(RHS))
return;
+ auto UseCachedValue = [](const Expr *Outer, const Expr *Inner) {
+ if (const ConstantExpr *E = dyn_cast<ConstantExpr>(Outer);
+ E && !E->getAPValueResult().isAbsent())
+ return Outer;
+ return Inner;
+ };
+
struct {
const clang::Expr *Cond;
Expr::EvalResult Result;
SmallString<12> ValueString;
bool Print;
- } DiagSide[2] = {{Op->getLHS(), Expr::EvalResult(), {}, false},
- {Op->getRHS(), Expr::EvalResult(), {}, false}};
+ } DiagSide[2] = {
+ {UseCachedValue(Op->getLHS(), LHS), Expr::EvalResult(), {}, false},
+ {UseCachedValue(Op->getRHS(), RHS), Expr::EvalResult(), {}, false}};
for (unsigned I = 0; I < 2; I++) {
const Expr *Side = DiagSide[I].Cond;
>From 766d4083b1e104ab5e6c9f717081ff298437e6e6 Mon Sep 17 00:00:00 2001
From: Corentin Jabot <corentinjabot at gmail.com>
Date: Mon, 17 Mar 2025 09:08:23 +0100
Subject: [PATCH 3/4] fix mangling
---
clang/docs/LanguageExtensions.rst | 2 +-
clang/lib/AST/ItaniumMangle.cpp | 75 +++++++------------
.../mangle-structured-binding-size.cpp | 4 +-
3 files changed, 28 insertions(+), 53 deletions(-)
diff --git a/clang/docs/LanguageExtensions.rst b/clang/docs/LanguageExtensions.rst
index 9a5cd8f1e5f5d..33087ff9307b7 100644
--- a/clang/docs/LanguageExtensions.rst
+++ b/clang/docs/LanguageExtensions.rst
@@ -439,7 +439,7 @@ type ignoring tail padding.
__builtin_structured_binding_size (C++)
---------------------------------------
``__builtin_structured_binding_size`` returns the *structured binding size*
-([dcl.struct.bind]) of the type ``T`` (or unevaluate expression ``arg``)
+([dcl.struct.bind]) of the type ``T`` (or unevaluated expression ``arg``)
passed as argument.
This is equivalent to the size of the pack ``p`` in ``auto&& [...p] = arg;``.
diff --git a/clang/lib/AST/ItaniumMangle.cpp b/clang/lib/AST/ItaniumMangle.cpp
index 12993d5cb35f1..2e887849ebe8a 100644
--- a/clang/lib/AST/ItaniumMangle.cpp
+++ b/clang/lib/AST/ItaniumMangle.cpp
@@ -5327,7 +5327,19 @@ void CXXNameMangler::mangleExpression(const Expr *E, unsigned Arity,
}
};
- switch(SAE->getKind()) {
+ auto MangleExtensionBuiltin = [&](const UnaryExprOrTypeTraitExpr *E,
+ StringRef Name = {}) {
+ if (Name.empty())
+ Name = getTraitSpelling(E->getKind());
+ Out << 'u' << Name.size() << Name;
+ if (SAE->isArgumentType())
+ mangleType(SAE->getArgumentType());
+ else
+ mangleTemplateArgExpr(SAE->getArgumentExpr());
+ Out << 'E';
+ };
+
+ switch (SAE->getKind()) {
case UETT_SizeOf:
Out << 's';
MangleAlignofSizeofArg();
@@ -5337,12 +5349,7 @@ void CXXNameMangler::mangleExpression(const Expr *E, unsigned Arity,
// have acted differently since Clang 8, but were previously mangled the
// same.)
if (!isCompatibleWith(LangOptions::ClangABI::Ver11)) {
- Out << "u11__alignof__";
- if (SAE->isArgumentType())
- mangleType(SAE->getArgumentType());
- else
- mangleTemplateArgExpr(SAE->getArgumentExpr());
- Out << 'E';
+ MangleExtensionBuiltin(SAE, "__alignof");
break;
}
[[fallthrough]];
@@ -5350,54 +5357,22 @@ void CXXNameMangler::mangleExpression(const Expr *E, unsigned Arity,
Out << 'a';
MangleAlignofSizeofArg();
break;
+
+ case UETT_StructuredBindingSize:
+ MangleExtensionBuiltin(SAE);
+ break;
+
+ case UETT_VectorElements:
+ case UETT_OpenMPRequiredSimdAlign:
+ case UETT_VecStep:
+ case UETT_PtrAuthTypeDiscriminator:
case UETT_DataSizeOf: {
- DiagnosticsEngine &Diags = Context.getDiags();
- unsigned DiagID =
- Diags.getCustomDiagID(DiagnosticsEngine::Error,
- "cannot yet mangle __datasizeof expression");
- Diags.Report(DiagID);
- return;
- }
- case UETT_PtrAuthTypeDiscriminator: {
DiagnosticsEngine &Diags = Context.getDiags();
unsigned DiagID = Diags.getCustomDiagID(
- DiagnosticsEngine::Error,
- "cannot yet mangle __builtin_ptrauth_type_discriminator expression");
- Diags.Report(E->getExprLoc(), DiagID);
+ DiagnosticsEngine::Error, "cannot yet mangle %0 expression");
+ Diags.Report(E->getExprLoc(), DiagID) << getTraitSpelling(SAE->getKind());
return;
}
- case UETT_VecStep: {
- DiagnosticsEngine &Diags = Context.getDiags();
- unsigned DiagID = Diags.getCustomDiagID(DiagnosticsEngine::Error,
- "cannot yet mangle vec_step expression");
- Diags.Report(DiagID);
- return;
- }
- case UETT_OpenMPRequiredSimdAlign: {
- DiagnosticsEngine &Diags = Context.getDiags();
- unsigned DiagID = Diags.getCustomDiagID(
- DiagnosticsEngine::Error,
- "cannot yet mangle __builtin_omp_required_simd_align expression");
- Diags.Report(DiagID);
- return;
- }
- case UETT_VectorElements: {
- DiagnosticsEngine &Diags = Context.getDiags();
- unsigned DiagID = Diags.getCustomDiagID(
- DiagnosticsEngine::Error,
- "cannot yet mangle __builtin_vectorelements expression");
- Diags.Report(DiagID);
- return;
- }
- case UETT_StructuredBindingSize:
- Out << "u11__builtin_structured_binding_size";
- if (SAE->isArgumentType())
- mangleType(SAE->getArgumentType());
- else
- mangleTemplateArgExpr(SAE->getArgumentExpr());
- Out << 'E';
- break;
- return;
}
break;
}
diff --git a/clang/test/CodeGenCXX/mangle-structured-binding-size.cpp b/clang/test/CodeGenCXX/mangle-structured-binding-size.cpp
index 5b53ed8d7166d..80e4063b9d556 100644
--- a/clang/test/CodeGenCXX/mangle-structured-binding-size.cpp
+++ b/clang/test/CodeGenCXX/mangle-structured-binding-size.cpp
@@ -4,9 +4,9 @@ struct S {};
template <class T> void f1(decltype(__builtin_structured_binding_size(T))) {}
template void f1<S>(__SIZE_TYPE__);
-// CHECK: void @_Z2f1I1SEvDTu11__builtin_structured_binding_sizeT_EE
+// CHECK: void @_Z2f1I1SEvDTu33__builtin_structured_binding_sizeT_EE
template <class T> void f2(decltype(__builtin_structured_binding_size(T{}))) {}
template void f2<S>(__SIZE_TYPE__);
-// CHECK: void @_Z2f2I1SEvDTu11__builtin_structured_binding_sizeXtlT_EEEE
+// CHECK: void @_Z2f2I1SEvDTu33__builtin_structured_binding_sizeXtlT_EEEE
>From d9d04595707a877b598ec28dd990aa36129a935e Mon Sep 17 00:00:00 2001
From: Corentin Jabot <corentinjabot at gmail.com>
Date: Mon, 17 Mar 2025 09:48:36 +0100
Subject: [PATCH 4/4] Clarify docs
---
clang/docs/LanguageExtensions.rst | 9 +++++----
1 file changed, 5 insertions(+), 4 deletions(-)
diff --git a/clang/docs/LanguageExtensions.rst b/clang/docs/LanguageExtensions.rst
index 33087ff9307b7..3a52e16ad0c6d 100644
--- a/clang/docs/LanguageExtensions.rst
+++ b/clang/docs/LanguageExtensions.rst
@@ -443,11 +443,12 @@ __builtin_structured_binding_size (C++)
passed as argument.
This is equivalent to the size of the pack ``p`` in ``auto&& [...p] = arg;``.
-If the argument is not destructurable (ie not an array, vector, complex,
-*tuple-like* type or destructurable class type), ``__builtin_structured_binding_size(T)``
-is not a valid expression (``__builtin_structured_binding_size`` is SFINEA-friendly).
+If the argument is not destructurable (ie not a builtin array, builtin SIMD vector,
+builtin complex, *tuple-like* type or destructurable class type),
+``__builtin_structured_binding_size(T)`` is not a valid expression
+(``__builtin_structured_binding_size`` is SFINEA-friendly).
-A type is considered a valid tuple if ``std::tuple_size_v<T>`` is a valid expression,
+A type is considered a valid *tuple-like* if ``std::tuple_size_v<T>`` is a valid expression,
even if there is no valid ``std::tuple_element`` specialization or suitable
``get`` function for that type.
More information about the cfe-commits
mailing list