[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
This commit is contained in:
cor3ntin 2025-03-18 20:50:56 +01:00 committed by GitHub
parent 0619892cab
commit bc8b19c757
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
20 changed files with 532 additions and 65 deletions

View File

@ -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
======

View File

@ -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.

View File

@ -51,6 +51,7 @@
#include <cstdint>
#include <memory>
#include <optional>
#include <variant>
namespace clang {
@ -2765,20 +2766,16 @@ public:
/// \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) {}
@ -2786,6 +2783,10 @@ class TypeTraitExpr final
return getNumArgs();
}
size_t numTrailingObjects(OverloadToken<APValue>) const {
return TypeTraitExprBits.IsBooleanTypeTrait ? 0 : 1;
}
public:
friend class ASTStmtReader;
friend class ASTStmtWriter;
@ -2798,7 +2799,13 @@ public:
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.
@ -2806,11 +2813,20 @@ public:
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; }

View File

@ -954,11 +954,13 @@ protected:
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 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.

View File

@ -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: "

View File

@ -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)

View File

@ -6116,7 +6116,8 @@ public:
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

View File

@ -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) {

View File

@ -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>

View File

@ -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,
@ -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());
}

View File

@ -12102,7 +12102,12 @@ public:
}
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) {

View File

@ -724,7 +724,12 @@ public:
}
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) {

View File

@ -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 auto *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) {
@ -1485,7 +1527,7 @@ static bool checkMemberDecomposition(Sema &S, ArrayRef<BindingDecl*> Bindings,
CXXCastPath BasePath;
DeclAccessPair BasePair =
findDecomposableBaseClass(S, Src->getLocation(), OrigRD, BasePath);
const CXXRecordDecl *RD = cast_or_null<CXXRecordDecl>(BasePair.getDecl());
const auto *RD = cast_or_null<CXXRecordDecl>(BasePair.getDecl());
if (!RD)
return true;
QualType BaseType = S.Context.getQualifiedType(S.Context.getRecordType(RD),
@ -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,56 @@ void Sema::CheckCompleteDecompositionDeclaration(DecompositionDecl *DD) {
DD->setInvalidDecl();
}
std::optional<unsigned> Sema::GetDecompositionElementCount(QualType T,
SourceLocation Loc) {
const ASTContext &Ctx = getASTContext();
assert(!T->isDependentType());
Qualifiers Quals;
QualType Unqual = Context.getUnqualifiedArrayType(T, Quals);
Quals.removeCVRQualifiers();
T = Context.getQualifiedType(Unqual, Quals);
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;
}
const 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 auto *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)

View File

@ -5066,6 +5066,10 @@ static bool CheckUnaryTypeTraitTypeCompleteness(Sema &S, TypeTrait UTT,
case UTT_IsInterfaceClass:
return true;
// We diagnose incomplete class types later.
case UTT_StructuredBindingSize:
return true;
// C++14 [meta.unary.prop]:
// If T is a non-union class type, T shall be a complete type.
case UTT_IsEmpty:
@ -5813,6 +5817,34 @@ static ExprResult CheckConvertibilityForTypeTraits(
return Result;
}
static APValue EvaluateSizeTTypeTrait(Sema &S, TypeTrait Kind,
SourceLocation KWLoc,
ArrayRef<TypeSourceInfo *> Args,
SourceLocation RParenLoc,
bool IsDependent) {
if (IsDependent)
return APValue();
switch (Kind) {
case TypeTrait::UTT_StructuredBindingSize: {
QualType T = Args[0]->getType();
SourceRange ArgRange = Args[0]->getTypeLoc().getSourceRange();
std::optional<unsigned> Size =
S.GetDecompositionElementCount(T, ArgRange.getBegin());
if (!Size) {
S.Diag(KWLoc, diag::err_arg_is_not_destructurable) << T << ArgRange;
return APValue();
}
llvm::APSInt V =
S.getASTContext().MakeIntValue(*Size, S.getASTContext().getSizeType());
return APValue{V};
break;
}
default:
llvm_unreachable("Not a SizeT type trait");
}
}
static bool EvaluateBooleanTypeTrait(Sema &S, TypeTrait Kind,
SourceLocation KWLoc,
ArrayRef<TypeSourceInfo *> Args,
@ -6014,9 +6046,12 @@ bool Sema::CheckTypeTraitArity(unsigned Arity, SourceLocation Loc, size_t N) {
enum class TypeTraitReturnType {
Bool,
SizeT,
};
static TypeTraitReturnType GetReturnType(TypeTrait Kind) {
if (Kind == TypeTrait::UTT_StructuredBindingSize)
return TypeTraitReturnType::SizeT;
return TypeTraitReturnType::Bool;
}
@ -6047,6 +6082,12 @@ ExprResult Sema::BuildTypeTrait(TypeTrait Kind, SourceLocation KWLoc,
return TypeTraitExpr::Create(Context, Context.getLogicalOperationType(),
KWLoc, Kind, Args, RParenLoc, Result);
}
case TypeTraitReturnType::SizeT: {
APValue Result =
EvaluateSizeTTypeTrait(*this, Kind, KWLoc, Args, RParenLoc, Dependent);
return TypeTraitExpr::Create(Context, Context.getSizeType(), KWLoc, Kind,
Args, RParenLoc, Result);
}
}
llvm_unreachable("unhandled type trait return type");
}

View File

@ -2135,9 +2135,15 @@ void ASTStmtReader::VisitUnresolvedLookupExpr(UnresolvedLookupExpr *E) {
void ASTStmtReader::VisitTypeTraitExpr(TypeTraitExpr *E) {
VisitExpr(E);
E->TypeTraitExprBits.IsBooleanTypeTrait = Record.readInt();
E->TypeTraitExprBits.NumArgs = Record.readInt();
E->TypeTraitExprBits.Kind = Record.readInt();
E->TypeTraitExprBits.Value = Record.readInt();
if (E->TypeTraitExprBits.IsBooleanTypeTrait)
E->TypeTraitExprBits.Value = Record.readInt();
else
*E->getTrailingObjects<APValue>() = Record.readAPValue();
SourceRange Range = readSourceRange();
E->Loc = Range.getBegin();
E->RParenLoc = Range.getEnd();
@ -4298,8 +4304,9 @@ Stmt *ASTReader::ReadStmtFromStream(ModuleFile &F) {
}
case EXPR_TYPE_TRAIT:
S = TypeTraitExpr::CreateDeserialized(Context,
Record[ASTStmtReader::NumExprFields]);
S = TypeTraitExpr::CreateDeserialized(
Context, Record[ASTStmtReader::NumExprFields],
Record[ASTStmtReader::NumExprFields + 1]);
break;
case EXPR_ARRAY_TYPE_TRAIT:

View File

@ -2140,9 +2140,15 @@ void ASTStmtWriter::VisitUnresolvedLookupExpr(UnresolvedLookupExpr *E) {
void ASTStmtWriter::VisitTypeTraitExpr(TypeTraitExpr *E) {
VisitExpr(E);
Record.push_back(E->TypeTraitExprBits.IsBooleanTypeTrait);
Record.push_back(E->TypeTraitExprBits.NumArgs);
Record.push_back(E->TypeTraitExprBits.Kind); // FIXME: Stable encoding
Record.push_back(E->TypeTraitExprBits.Value);
if (E->TypeTraitExprBits.IsBooleanTypeTrait)
Record.push_back(E->TypeTraitExprBits.Value);
else
Record.AddAPValue(E->getAPValue());
Record.AddSourceRange(E->getSourceRange());
for (unsigned I = 0, N = E->getNumArgs(); I != N; ++I)
Record.AddTypeSourceInfo(E->getArg(I));

View File

@ -368,7 +368,10 @@ std::optional<SVal> SValBuilder::getConstantVal(const Expr *E) {
case Stmt::TypeTraitExprClass: {
const auto *TE = cast<TypeTraitExpr>(E);
return makeTruthVal(TE->getValue(), TE->getType());
if (TE->isStoredAsBoolean())
return makeTruthVal(TE->getBoolValue(), TE->getType());
assert(TE->getAPValue().isInt() && "APValue type not supported");
return makeIntVal(TE->getAPValue().getInt());
}
case Stmt::IntegerLiteralClass:

View File

@ -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
}

View File

@ -1158,6 +1158,12 @@ template void f16<int>(int, __remove_volatile(int));
template <typename T> void f17(T, __remove_restrict(T)) {}
template void f17<int>(int, __remove_restrict(int));
// CHECK-LABEL: @_ZN6test553f17IiEEvT_u17__remove_restrictIS1_E
struct S{};
template <class T> void f18(decltype(__builtin_structured_binding_size(T))) {}
template void f18<S>(__SIZE_TYPE__);
// CHECK: void @_ZN6test553f18INS_1SEEEvDTu33__builtin_structured_binding_sizeT_EE
} // namespace test55
namespace test56 {

View File

@ -0,0 +1,231 @@
// RUN: %clang_cc1 %s -std=c++2c -fsyntax-only -verify
// RUN: %clang_cc1 %s -std=c++2c -fsyntax-only -verify -fexperimental-new-constant-interpreter
struct S0 {};
struct S1 {int a;};
struct S2 {int a; int b; static int c;};
struct S3 {double a; int b; int c;};
struct S4 {int a: 1; int b :2;};
struct S5 {int : 1; int b :2;};
struct S6 {union {int a;}; }; // #note-anon-union
struct S7 {int a[];};
struct SD : S1 {};
struct SE1 : S1 { int b;};
class P1 {int a;}; // #note-private
union U1 {};
union U2 {int a;};
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@-1 {{static assertion failed due to requirement '__builtin_structured_binding_size(S1) == 0'}} \
// expected-note@-1 {{expression evaluates to '1 == 0'}}
static_assert(__builtin_structured_binding_size(S1) == 1);
static_assert(__builtin_structured_binding_size(S2) == 2);
static_assert(__builtin_structured_binding_size(S3) == 3);
static_assert(__builtin_structured_binding_size(S4) == 2);
static_assert(__builtin_structured_binding_size(S5) == 2);
// expected-error@-1 {{static assertion failed due to requirement '__builtin_structured_binding_size(S5) == 2'}} \
// expected-note@-1 {{expression evaluates to '1 == 2'}}
static_assert(__builtin_structured_binding_size(S6) == 2);
// expected-error@-1 {{static assertion failed due to requirement '__builtin_structured_binding_size(S6) == 2'}} \
// expected-error@-1 {{cannot decompose class type 'S6' because it has an anonymous union member}} \
// expected-note@-1 {{expression evaluates to '1 == 2'}}
// expected-note@#note-anon-union {{declared here}}
static_assert(__builtin_structured_binding_size(S7) == 1);
static_assert(__builtin_structured_binding_size(SD) == 1);
static_assert(__builtin_structured_binding_size(SE1) == 1);
// expected-error@-1 {{cannot decompose class type 'SE1': both it and its base class 'S1' have non-static data members}} \
// expected-error@-1 {{type 'SE1' cannot be decomposed}} \
// expected-error@-1 {{static assertion expression is not an integral constant expression}}
static_assert(__builtin_structured_binding_size(U1) == 0);
// expected-error@-1 {{type 'U1' cannot be decomposed}} \
// expected-error@-1 {{static assertion expression is not an integral constant expression}}
static_assert(__builtin_structured_binding_size(U2) == 0);
// expected-error@-1 {{type 'U2' cannot be decomposed}} \
// expected-error@-1 {{static assertion expression is not an integral constant expression}}
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(decltype(__builtin_complex(0., 0.))) == 2);
int VLASize; // expected-note {{declared here}}
static_assert(__builtin_structured_binding_size(int[VLASize]) == 42);
// expected-error@-1 {{type 'int[VLASize]' cannot be decomposed}} \
// expected-warning@-1 {{variable length arrays in C++ are a Clang extension}} \
// expected-note@-1 {{read of non-const variable 'VLASize' is not allowed in a constant expression}} \
// expected-error@-1 {{static assertion expression is not an integral constant expression}}
struct Incomplete; // expected-note {{forward declaration of 'Incomplete'}}
static_assert(__builtin_structured_binding_size(Incomplete) == 1);
// expected-error@-1 {{incomplete type 'Incomplete' where a complete type is required}} \
// expected-error@-1 {{type 'Incomplete' cannot be decomposed}} \
// expected-error@-1 {{static assertion expression is not an integral constant expression}}
static_assert(__builtin_structured_binding_size(Incomplete[]) == 1);
// expected-error@-1 {{type 'Incomplete[]' cannot be decomposed}} \
// expected-error@-1 {{static assertion expression is not an integral constant expression}}
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@-1 {{static assertion failed due to requirement '__builtin_structured_binding_size(P1) == 0'}} \
// expected-note@-1 {{expression evaluates to '1 == 0'}} \
// expected-error@-1 {{cannot decompose private member 'a' of 'P1}} \
// expected-note@#note-private {{implicitly declared private here}}
void func(int array[14], int x = __builtin_structured_binding_size(decltype(array)));
//expected-error@-1 {{type 'decltype(array)' (aka 'int *') cannot be decomposed}}
struct SM {
static int array[14];
static_assert(__builtin_structured_binding_size(decltype(array)) == 14);
};
template <typename Ty, int N = __builtin_structured_binding_size(Ty)> // #tpl-1
struct T {
static constexpr int value = N;
};
T<int> t1;
// expected-error@#tpl-1 {{type 'int' cannot be decomposed}} \
// expected-error@#tpl-1 {{non-type template argument is not a constant expression}} \
// expected-note@-1 {{in instantiation of default argument for 'T<int>' required here}} \
// expected-note@-1 {{while checking a default template argument used here}} \
static_assert(T<S3>::value == 3);
static_assert(is_destructurable<S0>);
static_assert(is_destructurable<const S0>);
static_assert(is_destructurable<volatile S0>);
static_assert(!is_destructurable<S0&>);
static_assert(is_destructurable<S1>);
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(is_destructurable<const T0>);
static_assert(is_destructurable<volatile T0>);
static_assert(!is_destructurable<T0&>);
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@-1 {{cannot decompose this type; 'std::tuple_size<TSizeError>::value' is not a valid integral constant expression}} \
// expected-error@-1 {{type 'TSizeError' cannot be decomposed}} \
// expected-error@-1 {{static assertion expression is not an integral constant expression}}
static_assert(!is_destructurable<TSizeError>);
}
struct S {
int x;
int y;
static_assert(__builtin_structured_binding_size(S) == 2);
//expected-error@-1 {{incomplete type 'S' where a complete type is required}} \
// expected-error@-1 {{type 'S' cannot be decomposed}} \
// expected-error@-1 {{static assertion expression is not an integral constant expression}} \
// expected-note@-4 {{definition of 'S' is not complete until the closing '}'}}
};
// 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@-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@-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' cannot be decomposed}}