Skip to content

Commit ae66e1c

Browse files
committed
[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.
1 parent 215c0d2 commit ae66e1c

File tree

14 files changed

+405
-42
lines changed

14 files changed

+405
-42
lines changed

clang/docs/LanguageExtensions.rst

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -434,6 +434,36 @@ __datasizeof
434434
``__datasizeof`` behaves like ``sizeof``, except that it returns the size of the
435435
type ignoring tail padding.
436436

437+
.. _builtin_structured_binding_size-doc:
438+
439+
__builtin_structured_binding_size (C++)
440+
---------------------------------------
441+
``__builtin_structured_binding_size`` returns the *structured binding size*
442+
([dcl.struct.bind]) of the type ``T`` (or unevaluate expression ``arg``)
443+
passed as argument.
444+
445+
This is equivalent to the size of the pack ``p`` in ``auto&& [...p] = arg;``.
446+
If the argument is not destructurable (ie not an array, vector, complex,
447+
*tuple-like* type or destructurable class type), ``__builtin_structured_binding_size(T)``
448+
is not a valid expression (``__builtin_structured_binding_size`` is SFINEA-friendly).
449+
450+
A type is considered a valid tuple if ``std::tuple_size_v<T>`` is a valid expression,
451+
even if there is no valid ``std::tuple_element`` specialization or suitable
452+
``get`` function for that type.
453+
454+
.. code-block:: c++
455+
456+
template<std::size_t Idx, typename T>
457+
requires (Idx < __builtin_structured_binding_size(T))
458+
decltype(auto) constexpr get_binding(T&& obj) {
459+
auto && [...p] = std::forward<T>(obj);
460+
return p...[Idx];
461+
}
462+
struct S { int a = 0, b = 42; };
463+
static_assert(__builtin_structured_binding_size(S) == 2);
464+
static_assert(get_binding<1>(S{}) == 42);
465+
466+
437467
_BitInt, _ExtInt
438468
----------------
439469

clang/docs/ReleaseNotes.rst

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,9 @@ What's New in Clang |release|?
7474
C++ Language Changes
7575
--------------------
7676

77+
- Added a :ref:`__builtin_structured_binding_size <builtin_structured_binding_size-doc>` (T)
78+
builtin that returns the number of structured bindings that would be produced by destructuring ``T``.
79+
7780
C++2c Feature Support
7881
^^^^^^^^^^^^^^^^^^^^^
7982

clang/include/clang/Basic/DiagnosticSemaKinds.td

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -591,6 +591,8 @@ def err_decomp_decl_std_tuple_size_not_constant : Error<
591591
"is not a valid integral constant expression">;
592592
def note_in_binding_decl_init : Note<
593593
"in implicit initialization of binding declaration %0">;
594+
def err_arg_is_not_destructurable : Error<
595+
"type %0 is not destructurable">;
594596

595597
def err_std_type_trait_not_class_template : Error<
596598
"unsupported standard library implementation: "

clang/include/clang/Basic/TokenKinds.def

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -553,8 +553,8 @@ TYPE_TRAIT_2(__reference_converts_from_temporary, ReferenceConvertsFromTemporary
553553
// IsDeducible is only used internally by clang for CTAD implementation and
554554
// is not exposed to users.
555555
TYPE_TRAIT_2(/*EmptySpellingName*/, IsDeducible, KEYCXX)
556-
557556
TYPE_TRAIT_1(__is_bitwise_cloneable, IsBitwiseCloneable, KEYALL)
557+
UNARY_EXPR_OR_TYPE_TRAIT(__builtin_structured_binding_size, StructuredBindingSize, KEYCXX)
558558

559559
// Embarcadero Expression Traits
560560
EXPRESSION_TRAIT(__is_lvalue_expr, IsLValueExpr, KEYCXX)

clang/include/clang/Sema/Sema.h

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6095,7 +6095,8 @@ class Sema final : public SemaBase {
60956095
RecordDecl *ClassDecl,
60966096
const IdentifierInfo *Name);
60976097

6098-
unsigned GetDecompositionElementCount(QualType DecompType);
6098+
std::optional<unsigned int> GetDecompositionElementCount(QualType DecompType,
6099+
SourceLocation Loc);
60996100
void CheckCompleteDecompositionDeclaration(DecompositionDecl *DD);
61006101

61016102
/// Stack containing information needed when in C++2a an 'auto' is encountered

clang/lib/AST/ByteCode/Compiler.cpp

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2154,6 +2154,8 @@ bool Compiler<Emitter>::VisitUnaryExprOrTypeTraitExpr(
21542154
E->getArgumentType()),
21552155
E);
21562156
}
2157+
assert(Kind != UETT_StructuredBindingSize &&
2158+
"should have been evaluated in Sema");
21572159

21582160
return false;
21592161
}

clang/lib/AST/ExprConstant.cpp

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14878,6 +14878,11 @@ bool IntExprEvaluator::VisitUnaryExprOrTypeTraitExpr(
1487814878
}
1487914879
return Success(Sizeof, E);
1488014880
}
14881+
case UETT_StructuredBindingSize:
14882+
// This can only be computed from Sema and has been cached.
14883+
// We can still get there from code that strips the outer ConstantExpr.
14884+
return false;
14885+
1488114886
case UETT_OpenMPRequiredSimdAlign:
1488214887
assert(E->isArgumentType());
1488314888
return Success(

clang/lib/AST/ItaniumMangle.cpp

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5389,6 +5389,15 @@ void CXXNameMangler::mangleExpression(const Expr *E, unsigned Arity,
53895389
Diags.Report(DiagID);
53905390
return;
53915391
}
5392+
case UETT_StructuredBindingSize:
5393+
Out << "u11__builtin_structured_binding_size";
5394+
if (SAE->isArgumentType())
5395+
mangleType(SAE->getArgumentType());
5396+
else
5397+
mangleTemplateArgExpr(SAE->getArgumentExpr());
5398+
Out << 'E';
5399+
break;
5400+
return;
53925401
}
53935402
break;
53945403
}

clang/lib/Parse/ParseExpr.cpp

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1544,6 +1544,7 @@ ExprResult Parser::ParseCastExpression(CastParseKind ParseKind,
15441544
// unary-expression: '__builtin_omp_required_simd_align' '(' type-name ')'
15451545
case tok::kw___builtin_omp_required_simd_align:
15461546
case tok::kw___builtin_vectorelements:
1547+
case tok::kw___builtin_structured_binding_size:
15471548
if (NotPrimaryExpression)
15481549
*NotPrimaryExpression = true;
15491550
AllowSuffix = false;
@@ -2463,7 +2464,8 @@ Parser::ParseExprAfterUnaryExprOrTypeTrait(const Token &OpTok,
24632464
tok::kw___datasizeof, tok::kw___alignof, tok::kw_alignof,
24642465
tok::kw__Alignof, tok::kw_vec_step,
24652466
tok::kw___builtin_omp_required_simd_align,
2466-
tok::kw___builtin_vectorelements) &&
2467+
tok::kw___builtin_vectorelements,
2468+
tok::kw___builtin_structured_binding_size) &&
24672469
"Not a typeof/sizeof/alignof/vec_step expression!");
24682470

24692471
ExprResult Operand;
@@ -2473,7 +2475,8 @@ Parser::ParseExprAfterUnaryExprOrTypeTrait(const Token &OpTok,
24732475
// If construct allows a form without parenthesis, user may forget to put
24742476
// pathenthesis around type name.
24752477
if (OpTok.isOneOf(tok::kw_sizeof, tok::kw___datasizeof, tok::kw___alignof,
2476-
tok::kw_alignof, tok::kw__Alignof)) {
2478+
tok::kw_alignof, tok::kw__Alignof,
2479+
tok::kw___builtin_structured_binding_size)) {
24772480
if (isTypeIdUnambiguously()) {
24782481
DeclSpec DS(AttrFactory);
24792482
ParseSpecifierQualifierList(DS);
@@ -2599,7 +2602,8 @@ ExprResult Parser::ParseUnaryExprOrTypeTraitExpression() {
25992602
assert(Tok.isOneOf(tok::kw_sizeof, tok::kw___datasizeof, tok::kw___alignof,
26002603
tok::kw_alignof, tok::kw__Alignof, tok::kw_vec_step,
26012604
tok::kw___builtin_omp_required_simd_align,
2602-
tok::kw___builtin_vectorelements) &&
2605+
tok::kw___builtin_vectorelements,
2606+
tok::kw___builtin_structured_binding_size) &&
26032607
"Not a sizeof/alignof/vec_step expression!");
26042608
Token OpTok = Tok;
26052609
ConsumeToken();
@@ -2687,6 +2691,9 @@ ExprResult Parser::ParseUnaryExprOrTypeTraitExpression() {
26872691
case tok::kw___datasizeof:
26882692
ExprKind = UETT_DataSizeOf;
26892693
break;
2694+
case tok::kw___builtin_structured_binding_size:
2695+
ExprKind = UETT_StructuredBindingSize;
2696+
break;
26902697
case tok::kw___builtin_vectorelements:
26912698
ExprKind = UETT_VectorElements;
26922699
break;

clang/lib/Sema/SemaDeclCXX.cpp

Lines changed: 93 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -1475,6 +1475,48 @@ static DeclAccessPair findDecomposableBaseClass(Sema &S, SourceLocation Loc,
14751475
return DeclAccessPair::make(const_cast<CXXRecordDecl*>(ClassWithFields), AS);
14761476
}
14771477

1478+
static bool CheckMemberDecompositionFields(Sema &S, SourceLocation Loc,
1479+
const CXXRecordDecl *OrigRD,
1480+
QualType DecompType,
1481+
DeclAccessPair BasePair) {
1482+
const CXXRecordDecl *RD = cast_or_null<CXXRecordDecl>(BasePair.getDecl());
1483+
if (!RD)
1484+
return true;
1485+
1486+
for (auto *FD : RD->fields()) {
1487+
if (FD->isUnnamedBitField())
1488+
continue;
1489+
1490+
// All the non-static data members are required to be nameable, so they
1491+
// must all have names.
1492+
if (!FD->getDeclName()) {
1493+
if (RD->isLambda()) {
1494+
S.Diag(Loc, diag::err_decomp_decl_lambda);
1495+
S.Diag(RD->getLocation(), diag::note_lambda_decl);
1496+
return true;
1497+
}
1498+
1499+
if (FD->isAnonymousStructOrUnion()) {
1500+
S.Diag(Loc, diag::err_decomp_decl_anon_union_member)
1501+
<< DecompType << FD->getType()->isUnionType();
1502+
S.Diag(FD->getLocation(), diag::note_declared_at);
1503+
return true;
1504+
}
1505+
1506+
// FIXME: Are there any other ways we could have an anonymous member?
1507+
}
1508+
// The field must be accessible in the context of the structured binding.
1509+
// We already checked that the base class is accessible.
1510+
// FIXME: Add 'const' to AccessedEntity's classes so we can remove the
1511+
// const_cast here.
1512+
S.CheckStructuredBindingMemberAccess(
1513+
Loc, const_cast<CXXRecordDecl *>(OrigRD),
1514+
DeclAccessPair::make(FD, CXXRecordDecl::MergeAccess(
1515+
BasePair.getAccess(), FD->getAccess())));
1516+
}
1517+
return false;
1518+
}
1519+
14781520
static bool checkMemberDecomposition(Sema &S, ArrayRef<BindingDecl*> Bindings,
14791521
ValueDecl *Src, QualType DecompType,
14801522
const CXXRecordDecl *OrigRD) {
@@ -1503,43 +1545,20 @@ static bool checkMemberDecomposition(Sema &S, ArrayRef<BindingDecl*> Bindings,
15031545
auto FlatBindings = DD->flat_bindings();
15041546
assert(llvm::range_size(FlatBindings) == NumFields);
15051547
auto FlatBindingsItr = FlatBindings.begin();
1548+
1549+
if (CheckMemberDecompositionFields(S, Src->getLocation(), OrigRD, DecompType,
1550+
BasePair))
1551+
return true;
1552+
15061553
for (auto *FD : RD->fields()) {
15071554
if (FD->isUnnamedBitField())
15081555
continue;
15091556

1510-
// All the non-static data members are required to be nameable, so they
1511-
// must all have names.
1512-
if (!FD->getDeclName()) {
1513-
if (RD->isLambda()) {
1514-
S.Diag(Src->getLocation(), diag::err_decomp_decl_lambda);
1515-
S.Diag(RD->getLocation(), diag::note_lambda_decl);
1516-
return true;
1517-
}
1518-
1519-
if (FD->isAnonymousStructOrUnion()) {
1520-
S.Diag(Src->getLocation(), diag::err_decomp_decl_anon_union_member)
1521-
<< DecompType << FD->getType()->isUnionType();
1522-
S.Diag(FD->getLocation(), diag::note_declared_at);
1523-
return true;
1524-
}
1525-
1526-
// FIXME: Are there any other ways we could have an anonymous member?
1527-
}
1528-
15291557
// We have a real field to bind.
15301558
assert(FlatBindingsItr != FlatBindings.end());
15311559
BindingDecl *B = *(FlatBindingsItr++);
15321560
SourceLocation Loc = B->getLocation();
15331561

1534-
// The field must be accessible in the context of the structured binding.
1535-
// We already checked that the base class is accessible.
1536-
// FIXME: Add 'const' to AccessedEntity's classes so we can remove the
1537-
// const_cast here.
1538-
S.CheckStructuredBindingMemberAccess(
1539-
Loc, const_cast<CXXRecordDecl *>(OrigRD),
1540-
DeclAccessPair::make(FD, CXXRecordDecl::MergeAccess(
1541-
BasePair.getAccess(), FD->getAccess())));
1542-
15431562
// Initialize the binding to Src.FD.
15441563
ExprResult E = S.BuildDeclRefExpr(Src, DecompType, VK_LValue, Loc);
15451564
if (E.isInvalid())
@@ -1642,6 +1661,50 @@ void Sema::CheckCompleteDecompositionDeclaration(DecompositionDecl *DD) {
16421661
DD->setInvalidDecl();
16431662
}
16441663

1664+
std::optional<unsigned> Sema::GetDecompositionElementCount(QualType T,
1665+
SourceLocation Loc) {
1666+
const ASTContext &Ctx = getASTContext();
1667+
assert(!T->isDependentType());
1668+
if (auto *CAT = Ctx.getAsConstantArrayType(T))
1669+
return CAT->getSize().getZExtValue();
1670+
if (auto *VT = T->getAs<VectorType>())
1671+
return VT->getNumElements();
1672+
if (T->getAs<ComplexType>())
1673+
return 2;
1674+
1675+
llvm::APSInt TupleSize(Ctx.getTypeSize(Ctx.getSizeType()));
1676+
switch (isTupleLike(*this, Loc, T, TupleSize)) {
1677+
case IsTupleLike::Error:
1678+
return {};
1679+
case IsTupleLike::TupleLike:
1680+
return TupleSize.getExtValue();
1681+
case IsTupleLike::NotTupleLike:
1682+
break;
1683+
}
1684+
CXXRecordDecl *OrigRD = T->getAsCXXRecordDecl();
1685+
if (!OrigRD || OrigRD->isUnion()) {
1686+
return std::nullopt;
1687+
}
1688+
1689+
if (RequireCompleteType(Loc, T, diag::err_incomplete_type))
1690+
return std::nullopt;
1691+
1692+
CXXCastPath BasePath;
1693+
DeclAccessPair BasePair =
1694+
findDecomposableBaseClass(*this, Loc, OrigRD, BasePath);
1695+
const CXXRecordDecl *RD = cast_or_null<CXXRecordDecl>(BasePair.getDecl());
1696+
if (!RD)
1697+
return std::nullopt;
1698+
1699+
unsigned NumFields = llvm::count_if(
1700+
RD->fields(), [](FieldDecl *FD) { return !FD->isUnnamedBitField(); });
1701+
1702+
if (CheckMemberDecompositionFields(*this, Loc, OrigRD, T, BasePair))
1703+
return true;
1704+
1705+
return NumFields;
1706+
}
1707+
16451708
void Sema::MergeVarDeclExceptionSpecs(VarDecl *New, VarDecl *Old) {
16461709
// Shortcut if exceptions are disabled.
16471710
if (!getLangOpts().CXXExceptions)
@@ -17262,8 +17325,8 @@ void Sema::DiagnoseStaticAssertDetails(const Expr *E) {
1726217325
Expr::EvalResult Result;
1726317326
SmallString<12> ValueString;
1726417327
bool Print;
17265-
} DiagSide[2] = {{LHS, Expr::EvalResult(), {}, false},
17266-
{RHS, Expr::EvalResult(), {}, false}};
17328+
} DiagSide[2] = {{Op->getLHS(), Expr::EvalResult(), {}, false},
17329+
{Op->getRHS(), Expr::EvalResult(), {}, false}};
1726717330
for (unsigned I = 0; I < 2; I++) {
1726817331
const Expr *Side = DiagSide[I].Cond;
1726917332

0 commit comments

Comments
 (0)