Skip to content

Commit bc8b19c

Browse files
authored
[Clang] Introduce a trait to determine the structure binding size (#131515)
Introduce a trait 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` is a unary type trait that evaluates to the number of bindings in a decomposition If the argument cannot be decomposed, 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. Fixes #46049
1 parent 0619892 commit bc8b19c

File tree

20 files changed

+532
-65
lines changed

20 files changed

+532
-65
lines changed

clang/docs/LanguageExtensions.rst

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1912,6 +1912,40 @@ A simplistic usage example as might be seen in standard C++ headers follows:
19121912
// Emulate type trait for compatibility with other compilers.
19131913
#endif
19141914

1915+
1916+
.. _builtin_structured_binding_size-doc:
1917+
1918+
__builtin_structured_binding_size (C++)
1919+
---------------------------------------
1920+
1921+
The ``__builtin_structured_binding_size(T)`` type trait returns
1922+
the *structured binding size* ([dcl.struct.bind]) of type ``T``
1923+
1924+
This is equivalent to the size of the pack ``p`` in ``auto&& [...p] = declval<T&>();``.
1925+
If the argument cannot be decomposed, ``__builtin_structured_binding_size(T)``
1926+
is not a valid expression (``__builtin_structured_binding_size`` is SFINAE-friendly).
1927+
1928+
builtin arrays, builtin SIMD vectors,
1929+
builtin complex types, *tuple-like* types, and decomposable class types
1930+
are decomposable types.
1931+
1932+
A type is considered a valid *tuple-like* if ``std::tuple_size_v<T>`` is a valid expression,
1933+
even if there is no valid ``std::tuple_element`` specialization or suitable
1934+
``get`` function for that type.
1935+
1936+
.. code-block:: c++
1937+
1938+
template<std::size_t Idx, typename T>
1939+
requires (Idx < __builtin_structured_binding_size(T))
1940+
decltype(auto) constexpr get_binding(T&& obj) {
1941+
auto && [...p] = std::forward<T>(obj);
1942+
return p...[Idx];
1943+
}
1944+
struct S { int a = 0, b = 42; };
1945+
static_assert(__builtin_structured_binding_size(S) == 2);
1946+
static_assert(get_binding<1>(S{}) == 42);
1947+
1948+
19151949
Blocks
19161950
======
19171951

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
- Similarly to GCC, Clang now supports constant expressions in
7881
the strings of a GNU ``asm`` statement.
7982

clang/include/clang/AST/ExprCXX.h

Lines changed: 25 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@
5151
#include <cstdint>
5252
#include <memory>
5353
#include <optional>
54+
#include <variant>
5455

5556
namespace clang {
5657

@@ -2765,27 +2766,27 @@ class CXXPseudoDestructorExpr : public Expr {
27652766
/// \endcode
27662767
class TypeTraitExpr final
27672768
: public Expr,
2768-
private llvm::TrailingObjects<TypeTraitExpr, TypeSourceInfo *> {
2769+
private llvm::TrailingObjects<TypeTraitExpr, APValue, TypeSourceInfo *> {
27692770
/// The location of the type trait keyword.
27702771
SourceLocation Loc;
27712772

27722773
/// The location of the closing parenthesis.
27732774
SourceLocation RParenLoc;
27742775

2775-
// Note: The TypeSourceInfos for the arguments are allocated after the
2776-
// TypeTraitExpr.
2777-
27782776
TypeTraitExpr(QualType T, SourceLocation Loc, TypeTrait Kind,
2779-
ArrayRef<TypeSourceInfo *> Args,
2780-
SourceLocation RParenLoc,
2781-
bool Value);
2777+
ArrayRef<TypeSourceInfo *> Args, SourceLocation RParenLoc,
2778+
std::variant<bool, APValue> Value);
27822779

27832780
TypeTraitExpr(EmptyShell Empty) : Expr(TypeTraitExprClass, Empty) {}
27842781

27852782
size_t numTrailingObjects(OverloadToken<TypeSourceInfo *>) const {
27862783
return getNumArgs();
27872784
}
27882785

2786+
size_t numTrailingObjects(OverloadToken<APValue>) const {
2787+
return TypeTraitExprBits.IsBooleanTypeTrait ? 0 : 1;
2788+
}
2789+
27892790
public:
27902791
friend class ASTStmtReader;
27912792
friend class ASTStmtWriter;
@@ -2798,19 +2799,34 @@ class TypeTraitExpr final
27982799
SourceLocation RParenLoc,
27992800
bool Value);
28002801

2802+
static TypeTraitExpr *Create(const ASTContext &C, QualType T,
2803+
SourceLocation Loc, TypeTrait Kind,
2804+
ArrayRef<TypeSourceInfo *> Args,
2805+
SourceLocation RParenLoc, APValue Value);
2806+
28012807
static TypeTraitExpr *CreateDeserialized(const ASTContext &C,
2808+
bool IsStoredAsBool,
28022809
unsigned NumArgs);
28032810

28042811
/// Determine which type trait this expression uses.
28052812
TypeTrait getTrait() const {
28062813
return static_cast<TypeTrait>(TypeTraitExprBits.Kind);
28072814
}
28082815

2809-
bool getValue() const {
2810-
assert(!isValueDependent());
2816+
bool isStoredAsBoolean() const {
2817+
return TypeTraitExprBits.IsBooleanTypeTrait;
2818+
}
2819+
2820+
bool getBoolValue() const {
2821+
assert(!isValueDependent() && TypeTraitExprBits.IsBooleanTypeTrait);
28112822
return TypeTraitExprBits.Value;
28122823
}
28132824

2825+
const APValue &getAPValue() const {
2826+
assert(!isValueDependent() && !TypeTraitExprBits.IsBooleanTypeTrait);
2827+
return *getTrailingObjects<APValue>();
2828+
}
2829+
28142830
/// Determine the number of arguments to this type trait.
28152831
unsigned getNumArgs() const { return TypeTraitExprBits.NumArgs; }
28162832

clang/include/clang/AST/Stmt.h

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -954,11 +954,13 @@ class alignas(void *) Stmt {
954954
LLVM_PREFERRED_TYPE(TypeTrait)
955955
unsigned Kind : 8;
956956

957-
/// If this expression is not value-dependent, this indicates whether
958-
/// the trait evaluated true or false.
959957
LLVM_PREFERRED_TYPE(bool)
960-
unsigned Value : 1;
958+
unsigned IsBooleanTypeTrait : 1;
961959

960+
/// If this expression is a non value-dependent boolean trait,
961+
/// this indicates whether the trait evaluated true or false.
962+
LLVM_PREFERRED_TYPE(bool)
963+
unsigned Value : 1;
962964
/// The number of arguments to this type trait. According to [implimits]
963965
/// 8 bits would be enough, but we require (and test for) at least 16 bits
964966
/// to mirror FunctionType.

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 cannot be decomposed">;
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+
TYPE_TRAIT_1(__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
@@ -6116,7 +6116,8 @@ class Sema final : public SemaBase {
61166116
RecordDecl *ClassDecl,
61176117
const IdentifierInfo *Name);
61186118

6119-
unsigned GetDecompositionElementCount(QualType DecompType);
6119+
std::optional<unsigned int> GetDecompositionElementCount(QualType DecompType,
6120+
SourceLocation Loc);
61206121
void CheckCompleteDecompositionDeclaration(DecompositionDecl *DD);
61216122

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

clang/lib/AST/ASTImporter.cpp

Lines changed: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -8955,13 +8955,16 @@ ExpectedStmt ASTNodeImporter::VisitTypeTraitExpr(TypeTraitExpr *E) {
89558955
if (Error Err = ImportContainerChecked(E->getArgs(), ToArgs))
89568956
return std::move(Err);
89578957

8958-
// According to Sema::BuildTypeTrait(), if E is value-dependent,
8959-
// Value is always false.
8960-
bool ToValue = (E->isValueDependent() ? false : E->getValue());
8961-
8962-
return TypeTraitExpr::Create(
8963-
Importer.getToContext(), ToType, ToBeginLoc, E->getTrait(), ToArgs,
8964-
ToEndLoc, ToValue);
8958+
if (E->isStoredAsBoolean()) {
8959+
// According to Sema::BuildTypeTrait(), if E is value-dependent,
8960+
// Value is always false.
8961+
bool ToValue = (E->isValueDependent() ? false : E->getBoolValue());
8962+
return TypeTraitExpr::Create(Importer.getToContext(), ToType, ToBeginLoc,
8963+
E->getTrait(), ToArgs, ToEndLoc, ToValue);
8964+
}
8965+
return TypeTraitExpr::Create(Importer.getToContext(), ToType, ToBeginLoc,
8966+
E->getTrait(), ToArgs, ToEndLoc,
8967+
E->getAPValue());
89658968
}
89668969

89678970
ExpectedStmt ASTNodeImporter::VisitCXXTypeidExpr(CXXTypeidExpr *E) {

clang/lib/AST/ByteCode/Compiler.cpp

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2844,9 +2844,13 @@ template <class Emitter>
28442844
bool Compiler<Emitter>::VisitTypeTraitExpr(const TypeTraitExpr *E) {
28452845
if (DiscardResult)
28462846
return true;
2847-
if (E->getType()->isBooleanType())
2848-
return this->emitConstBool(E->getValue(), E);
2849-
return this->emitConst(E->getValue(), E);
2847+
if (E->isStoredAsBoolean()) {
2848+
if (E->getType()->isBooleanType())
2849+
return this->emitConstBool(E->getBoolValue(), E);
2850+
return this->emitConst(E->getBoolValue(), E);
2851+
}
2852+
PrimType T = classifyPrim(E->getType());
2853+
return this->visitAPValue(E->getAPValue(), T, E);
28502854
}
28512855

28522856
template <class Emitter>

clang/lib/AST/ExprCXX.cpp

Lines changed: 28 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1854,23 +1854,34 @@ bool MaterializeTemporaryExpr::isUsableInConstantExpressions(
18541854

18551855
TypeTraitExpr::TypeTraitExpr(QualType T, SourceLocation Loc, TypeTrait Kind,
18561856
ArrayRef<TypeSourceInfo *> Args,
1857-
SourceLocation RParenLoc, bool Value)
1857+
SourceLocation RParenLoc,
1858+
std::variant<bool, APValue> Value)
18581859
: Expr(TypeTraitExprClass, T, VK_PRValue, OK_Ordinary), Loc(Loc),
18591860
RParenLoc(RParenLoc) {
18601861
assert(Kind <= TT_Last && "invalid enum value!");
1862+
18611863
TypeTraitExprBits.Kind = Kind;
18621864
assert(static_cast<unsigned>(Kind) == TypeTraitExprBits.Kind &&
18631865
"TypeTraitExprBits.Kind overflow!");
1864-
TypeTraitExprBits.Value = Value;
1866+
1867+
TypeTraitExprBits.IsBooleanTypeTrait = std::holds_alternative<bool>(Value);
1868+
if (TypeTraitExprBits.IsBooleanTypeTrait)
1869+
TypeTraitExprBits.Value = std::get<bool>(Value);
1870+
else
1871+
*getTrailingObjects<APValue>() = std::get<APValue>(std::move(Value));
1872+
18651873
TypeTraitExprBits.NumArgs = Args.size();
18661874
assert(Args.size() == TypeTraitExprBits.NumArgs &&
18671875
"TypeTraitExprBits.NumArgs overflow!");
1868-
18691876
auto **ToArgs = getTrailingObjects<TypeSourceInfo *>();
18701877
for (unsigned I = 0, N = Args.size(); I != N; ++I)
18711878
ToArgs[I] = Args[I];
18721879

18731880
setDependence(computeDependence(this));
1881+
1882+
assert((TypeTraitExprBits.IsBooleanTypeTrait || isValueDependent() ||
1883+
getAPValue().isInt() || getAPValue().isAbsent()) &&
1884+
"Only int values are supported by clang");
18741885
}
18751886

18761887
TypeTraitExpr *TypeTraitExpr::Create(const ASTContext &C, QualType T,
@@ -1879,13 +1890,25 @@ TypeTraitExpr *TypeTraitExpr::Create(const ASTContext &C, QualType T,
18791890
ArrayRef<TypeSourceInfo *> Args,
18801891
SourceLocation RParenLoc,
18811892
bool Value) {
1882-
void *Mem = C.Allocate(totalSizeToAlloc<TypeSourceInfo *>(Args.size()));
1893+
void *Mem =
1894+
C.Allocate(totalSizeToAlloc<APValue, TypeSourceInfo *>(0, Args.size()));
1895+
return new (Mem) TypeTraitExpr(T, Loc, Kind, Args, RParenLoc, Value);
1896+
}
1897+
1898+
TypeTraitExpr *TypeTraitExpr::Create(const ASTContext &C, QualType T,
1899+
SourceLocation Loc, TypeTrait Kind,
1900+
ArrayRef<TypeSourceInfo *> Args,
1901+
SourceLocation RParenLoc, APValue Value) {
1902+
void *Mem =
1903+
C.Allocate(totalSizeToAlloc<APValue, TypeSourceInfo *>(1, Args.size()));
18831904
return new (Mem) TypeTraitExpr(T, Loc, Kind, Args, RParenLoc, Value);
18841905
}
18851906

18861907
TypeTraitExpr *TypeTraitExpr::CreateDeserialized(const ASTContext &C,
1908+
bool IsStoredAsBool,
18871909
unsigned NumArgs) {
1888-
void *Mem = C.Allocate(totalSizeToAlloc<TypeSourceInfo *>(NumArgs));
1910+
void *Mem = C.Allocate(totalSizeToAlloc<APValue, TypeSourceInfo *>(
1911+
IsStoredAsBool ? 0 : 1, NumArgs));
18891912
return new (Mem) TypeTraitExpr(EmptyShell());
18901913
}
18911914

clang/lib/AST/ExprConstant.cpp

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12102,7 +12102,12 @@ class IntExprEvaluator
1210212102
}
1210312103

1210412104
bool VisitTypeTraitExpr(const TypeTraitExpr *E) {
12105-
return Success(E->getValue(), E);
12105+
if (E->isStoredAsBoolean())
12106+
return Success(E->getBoolValue(), E);
12107+
if (E->getAPValue().isAbsent())
12108+
return false;
12109+
assert(E->getAPValue().isInt() && "APValue type not supported");
12110+
return Success(E->getAPValue().getInt(), E);
1210612111
}
1210712112

1210812113
bool VisitArrayTypeTraitExpr(const ArrayTypeTraitExpr *E) {

clang/lib/CodeGen/CGExprScalar.cpp

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -724,7 +724,12 @@ class ScalarExprEmitter
724724
}
725725

726726
Value *VisitTypeTraitExpr(const TypeTraitExpr *E) {
727-
return llvm::ConstantInt::get(ConvertType(E->getType()), E->getValue());
727+
if (E->isStoredAsBoolean())
728+
return llvm::ConstantInt::get(ConvertType(E->getType()),
729+
E->getBoolValue());
730+
assert(E->getAPValue().isInt() && "APValue type not supported");
731+
return llvm::ConstantInt::get(ConvertType(E->getType()),
732+
E->getAPValue().getInt());
728733
}
729734

730735
Value *VisitConceptSpecializationExpr(const ConceptSpecializationExpr *E) {

0 commit comments

Comments
 (0)