Skip to content

[Clang] Introduce a trait to determine the structure binding size #131515

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 23 commits into from
Mar 18, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 34 additions & 0 deletions clang/docs/LanguageExtensions.rst
Original file line number Diff line number Diff line change
Expand Up @@ -1912,6 +1912,40 @@ A simplistic usage example as might be seen in standard C++ headers follows:
// Emulate type trait for compatibility with other compilers.
#endif


.. _builtin_structured_binding_size-doc:

__builtin_structured_binding_size (C++)
---------------------------------------

The ``__builtin_structured_binding_size(T)`` type trait returns
the *structured binding size* ([dcl.struct.bind]) of type ``T``

This is equivalent to the size of the pack ``p`` in ``auto&& [...p] = declval<T&>();``.
If the argument cannot be decomposed, ``__builtin_structured_binding_size(T)``
is not a valid expression (``__builtin_structured_binding_size`` is SFINAE-friendly).

builtin arrays, builtin SIMD vectors,
builtin complex types, *tuple-like* types, and decomposable class types
are decomposable types.

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.

.. 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);


Blocks
======

Expand Down
3 changes: 3 additions & 0 deletions clang/docs/ReleaseNotes.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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``.

- Similarly to GCC, Clang now supports constant expressions in
the strings of a GNU ``asm`` statement.

Expand Down
34 changes: 25 additions & 9 deletions clang/include/clang/AST/ExprCXX.h
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@
#include <cstdint>
#include <memory>
#include <optional>
#include <variant>

namespace clang {

Expand Down Expand Up @@ -2765,27 +2766,27 @@ class CXXPseudoDestructorExpr : public Expr {
/// \endcode
class TypeTraitExpr final
: public Expr,
private llvm::TrailingObjects<TypeTraitExpr, TypeSourceInfo *> {
private llvm::TrailingObjects<TypeTraitExpr, APValue, TypeSourceInfo *> {
/// The location of the type trait keyword.
SourceLocation Loc;

/// The location of the closing parenthesis.
SourceLocation RParenLoc;

// Note: The TypeSourceInfos for the arguments are allocated after the
// TypeTraitExpr.

TypeTraitExpr(QualType T, SourceLocation Loc, TypeTrait Kind,
ArrayRef<TypeSourceInfo *> Args,
SourceLocation RParenLoc,
bool Value);
ArrayRef<TypeSourceInfo *> Args, SourceLocation RParenLoc,
std::variant<bool, APValue> Value);

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

size_t numTrailingObjects(OverloadToken<TypeSourceInfo *>) const {
return getNumArgs();
}

size_t numTrailingObjects(OverloadToken<APValue>) const {
return TypeTraitExprBits.IsBooleanTypeTrait ? 0 : 1;
}

public:
friend class ASTStmtReader;
friend class ASTStmtWriter;
Expand All @@ -2798,19 +2799,34 @@ class TypeTraitExpr final
SourceLocation RParenLoc,
bool Value);

static TypeTraitExpr *Create(const ASTContext &C, QualType T,
SourceLocation Loc, TypeTrait Kind,
ArrayRef<TypeSourceInfo *> Args,
SourceLocation RParenLoc, APValue Value);

static TypeTraitExpr *CreateDeserialized(const ASTContext &C,
bool IsStoredAsBool,
unsigned NumArgs);

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

bool getValue() const {
assert(!isValueDependent());
bool isStoredAsBoolean() const {
return TypeTraitExprBits.IsBooleanTypeTrait;
}

bool getBoolValue() const {
assert(!isValueDependent() && TypeTraitExprBits.IsBooleanTypeTrait);
return TypeTraitExprBits.Value;
}

const APValue &getAPValue() const {
assert(!isValueDependent() && !TypeTraitExprBits.IsBooleanTypeTrait);
return *getTrailingObjects<APValue>();
}

/// Determine the number of arguments to this type trait.
unsigned getNumArgs() const { return TypeTraitExprBits.NumArgs; }

Expand Down
8 changes: 5 additions & 3 deletions clang/include/clang/AST/Stmt.h
Original file line number Diff line number Diff line change
Expand Up @@ -954,11 +954,13 @@ class alignas(void *) Stmt {
LLVM_PREFERRED_TYPE(TypeTrait)
unsigned Kind : 8;

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

/// If this expression is a non value-dependent boolean trait,
/// this indicates whether the trait evaluated true or false.
LLVM_PREFERRED_TYPE(bool)
unsigned Value : 1;
/// The number of arguments to this type trait. According to [implimits]
/// 8 bits would be enough, but we require (and test for) at least 16 bits
/// to mirror FunctionType.
Expand Down
2 changes: 2 additions & 0 deletions clang/include/clang/Basic/DiagnosticSemaKinds.td
Original file line number Diff line number Diff line change
Expand Up @@ -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 cannot be decomposed">;

def err_std_type_trait_not_class_template : Error<
"unsupported standard library implementation: "
Expand Down
2 changes: 1 addition & 1 deletion clang/include/clang/Basic/TokenKinds.def
Original file line number Diff line number Diff line change
Expand Up @@ -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)
TYPE_TRAIT_1(__builtin_structured_binding_size, StructuredBindingSize, KEYCXX)

// Embarcadero Expression Traits
EXPRESSION_TRAIT(__is_lvalue_expr, IsLValueExpr, KEYCXX)
Expand Down
3 changes: 2 additions & 1 deletion clang/include/clang/Sema/Sema.h
Original file line number Diff line number Diff line change
Expand Up @@ -6101,7 +6101,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
Expand Down
17 changes: 10 additions & 7 deletions clang/lib/AST/ASTImporter.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -8955,13 +8955,16 @@ ExpectedStmt ASTNodeImporter::VisitTypeTraitExpr(TypeTraitExpr *E) {
if (Error Err = ImportContainerChecked(E->getArgs(), ToArgs))
return std::move(Err);

// According to Sema::BuildTypeTrait(), if E is value-dependent,
// Value is always false.
bool ToValue = (E->isValueDependent() ? false : E->getValue());

return TypeTraitExpr::Create(
Importer.getToContext(), ToType, ToBeginLoc, E->getTrait(), ToArgs,
ToEndLoc, ToValue);
if (E->isStoredAsBoolean()) {
// According to Sema::BuildTypeTrait(), if E is value-dependent,
// Value is always false.
bool ToValue = (E->isValueDependent() ? false : E->getBoolValue());
return TypeTraitExpr::Create(Importer.getToContext(), ToType, ToBeginLoc,
E->getTrait(), ToArgs, ToEndLoc, ToValue);
}
return TypeTraitExpr::Create(Importer.getToContext(), ToType, ToBeginLoc,
E->getTrait(), ToArgs, ToEndLoc,
E->getAPValue());
}

ExpectedStmt ASTNodeImporter::VisitCXXTypeidExpr(CXXTypeidExpr *E) {
Expand Down
10 changes: 7 additions & 3 deletions clang/lib/AST/ByteCode/Compiler.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2844,9 +2844,13 @@ template <class Emitter>
bool Compiler<Emitter>::VisitTypeTraitExpr(const TypeTraitExpr *E) {
if (DiscardResult)
return true;
if (E->getType()->isBooleanType())
return this->emitConstBool(E->getValue(), E);
return this->emitConst(E->getValue(), E);
if (E->isStoredAsBoolean()) {
if (E->getType()->isBooleanType())
return this->emitConstBool(E->getBoolValue(), E);
return this->emitConst(E->getBoolValue(), E);
}
PrimType T = classifyPrim(E->getType());
return this->visitAPValue(E->getAPValue(), T, E);
}

template <class Emitter>
Expand Down
33 changes: 28 additions & 5 deletions clang/lib/AST/ExprCXX.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1854,23 +1854,34 @@ bool MaterializeTemporaryExpr::isUsableInConstantExpressions(

TypeTraitExpr::TypeTraitExpr(QualType T, SourceLocation Loc, TypeTrait Kind,
ArrayRef<TypeSourceInfo *> Args,
SourceLocation RParenLoc, bool Value)
SourceLocation RParenLoc,
std::variant<bool, APValue> Value)
: Expr(TypeTraitExprClass, T, VK_PRValue, OK_Ordinary), Loc(Loc),
RParenLoc(RParenLoc) {
assert(Kind <= TT_Last && "invalid enum value!");

TypeTraitExprBits.Kind = Kind;
assert(static_cast<unsigned>(Kind) == TypeTraitExprBits.Kind &&
"TypeTraitExprBits.Kind overflow!");
TypeTraitExprBits.Value = Value;

TypeTraitExprBits.IsBooleanTypeTrait = std::holds_alternative<bool>(Value);
if (TypeTraitExprBits.IsBooleanTypeTrait)
TypeTraitExprBits.Value = std::get<bool>(Value);
else
*getTrailingObjects<APValue>() = std::get<APValue>(std::move(Value));

TypeTraitExprBits.NumArgs = Args.size();
assert(Args.size() == TypeTraitExprBits.NumArgs &&
"TypeTraitExprBits.NumArgs overflow!");

auto **ToArgs = getTrailingObjects<TypeSourceInfo *>();
for (unsigned I = 0, N = Args.size(); I != N; ++I)
ToArgs[I] = Args[I];

setDependence(computeDependence(this));

assert((TypeTraitExprBits.IsBooleanTypeTrait || isValueDependent() ||
getAPValue().isInt() || getAPValue().isAbsent()) &&
"Only int values are supported by clang");
}

TypeTraitExpr *TypeTraitExpr::Create(const ASTContext &C, QualType T,
Expand All @@ -1879,13 +1890,25 @@ TypeTraitExpr *TypeTraitExpr::Create(const ASTContext &C, QualType T,
ArrayRef<TypeSourceInfo *> Args,
SourceLocation RParenLoc,
bool Value) {
void *Mem = C.Allocate(totalSizeToAlloc<TypeSourceInfo *>(Args.size()));
void *Mem =
C.Allocate(totalSizeToAlloc<APValue, TypeSourceInfo *>(0, Args.size()));
return new (Mem) TypeTraitExpr(T, Loc, Kind, Args, RParenLoc, Value);
}

TypeTraitExpr *TypeTraitExpr::Create(const ASTContext &C, QualType T,
SourceLocation Loc, TypeTrait Kind,
ArrayRef<TypeSourceInfo *> Args,
SourceLocation RParenLoc, APValue Value) {
void *Mem =
C.Allocate(totalSizeToAlloc<APValue, TypeSourceInfo *>(1, Args.size()));
return new (Mem) TypeTraitExpr(T, Loc, Kind, Args, RParenLoc, Value);
}

TypeTraitExpr *TypeTraitExpr::CreateDeserialized(const ASTContext &C,
bool IsStoredAsBool,
unsigned NumArgs) {
void *Mem = C.Allocate(totalSizeToAlloc<TypeSourceInfo *>(NumArgs));
void *Mem = C.Allocate(totalSizeToAlloc<APValue, TypeSourceInfo *>(
IsStoredAsBool ? 0 : 1, NumArgs));
return new (Mem) TypeTraitExpr(EmptyShell());
}

Expand Down
7 changes: 6 additions & 1 deletion clang/lib/AST/ExprConstant.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -12102,7 +12102,12 @@ class IntExprEvaluator
}

bool VisitTypeTraitExpr(const TypeTraitExpr *E) {
return Success(E->getValue(), E);
if (E->isStoredAsBoolean())
return Success(E->getBoolValue(), E);
if (E->getAPValue().isAbsent())
return false;
assert(E->getAPValue().isInt() && "APValue type not supported");
return Success(E->getAPValue().getInt(), E);
}

bool VisitArrayTypeTraitExpr(const ArrayTypeTraitExpr *E) {
Expand Down
7 changes: 6 additions & 1 deletion clang/lib/CodeGen/CGExprScalar.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -724,7 +724,12 @@ class ScalarExprEmitter
}

Value *VisitTypeTraitExpr(const TypeTraitExpr *E) {
return llvm::ConstantInt::get(ConvertType(E->getType()), E->getValue());
if (E->isStoredAsBoolean())
return llvm::ConstantInt::get(ConvertType(E->getType()),
E->getBoolValue());
assert(E->getAPValue().isInt() && "APValue type not supported");
return llvm::ConstantInt::get(ConvertType(E->getType()),
E->getAPValue().getInt());
}

Value *VisitConceptSpecializationExpr(const ConceptSpecializationExpr *E) {
Expand Down
Loading
Loading