rsmith updated this revision to Diff 424379.
rsmith added a comment.
- Fix some code that was making incorrect assumptions that PseudoObjectExpr is
only used for ObjC properties.
Repository:
rG LLVM Github Monorepo
CHANGES SINCE LAST ACTION
https://reviews.llvm.org/D124221/new/
https://reviews.llvm.org/D124221
Files:
clang/docs/LanguageExtensions.rst
clang/docs/ReleaseNotes.rst
clang/include/clang/Basic/Builtins.def
clang/include/clang/Basic/DiagnosticSemaKinds.td
clang/lib/AST/Expr.cpp
clang/lib/Sema/SemaChecking.cpp
clang/lib/Sema/SemaStmt.cpp
clang/test/CodeGenCXX/builtin-reflect-struct.cpp
clang/test/Sema/builtin-dump-struct.c
clang/test/SemaCXX/builtin-reflect-struct.cpp
Index: clang/test/SemaCXX/builtin-reflect-struct.cpp
===================================================================
--- /dev/null
+++ clang/test/SemaCXX/builtin-reflect-struct.cpp
@@ -0,0 +1,119 @@
+// RUN: %clang_cc1 -std=c++20 -verify %s
+
+namespace std {
+ typedef decltype(sizeof(int)) size_t;
+
+ template <class E> struct initializer_list {
+ const E *data;
+ size_t size;
+
+ constexpr initializer_list(const E *data, size_t size)
+ : data(data), size(size) {}
+ constexpr initializer_list() : data(), size() {}
+
+ constexpr const E *begin() const { return data; }
+ constexpr const E *end() const { return data + size; }
+ };
+}
+
+struct ConstexprString {
+ constexpr ConstexprString(const char *p) : data(nullptr) {
+ auto size = __builtin_strlen(p) + 1;
+ data = new char[size];
+ __builtin_memcpy(data, p, size);
+ }
+ constexpr explicit ConstexprString(const char *p, const char *q) : data(nullptr) {
+ auto p_size = __builtin_strlen(p);
+ auto q_size = __builtin_strlen(q);
+ data = new char[p_size + q_size + 1];
+ __builtin_memcpy(data, p, p_size);
+ __builtin_memcpy(data + p_size, q, q_size + 1);
+ }
+ constexpr ConstexprString(ConstexprString &&o) : data(o.data) { o.data = nullptr; }
+ constexpr ConstexprString &operator=(ConstexprString &&o) {
+ delete[] data;
+ data = o.data;
+ o.data = nullptr;
+ return *this;
+ }
+ constexpr ~ConstexprString() { delete[] data; }
+ char *data;
+
+ friend constexpr ConstexprString operator+(const ConstexprString &a, const ConstexprString &b) {
+ return ConstexprString(a.data, b.data);
+ }
+ friend constexpr bool operator==(const ConstexprString &a, const ConstexprString &b) {
+ return __builtin_strcmp(a.data, b.data) == 0;
+ }
+};
+
+template<typename T> constexpr ConstexprString ToString(const T &t);
+
+struct FieldOrBase {
+ template <typename Base>
+ constexpr FieldOrBase(const Base *base) : repr(ToString(*base)) {}
+ template <typename T>
+ constexpr FieldOrBase(const char *name, const T &field)
+ : repr(name + ConstexprString(" = ") + ToString(field)) {}
+ template <typename T>
+ constexpr FieldOrBase(const char *name, T field, int bitwidth)
+ : repr(name + ConstexprString(" : ") + ToString(bitwidth) +
+ ConstexprString(" = ") + ToString(field)) {}
+ ConstexprString repr;
+};
+
+constexpr ConstexprString FieldsToString(std::initializer_list<FieldOrBase> elements) { // expected-note {{here}}
+ ConstexprString result = "{";
+ bool first = true;
+ for (const auto &element : elements) {
+ if (!first)
+ result = result + ", ";
+ result = result + element.repr;
+ first = false;
+ }
+ return result + "}";
+}
+
+template<typename T> constexpr ConstexprString ToString(const T &t) {
+ if constexpr (__is_class(T)) {
+ return __builtin_reflect_struct(&t, FieldsToString);
+ } else {
+ // Assume it's an integer.
+ T value = t;
+ ConstexprString s = "";
+ bool negative = false;
+ if (value < 0) {
+ value = -value;
+ negative = true;
+ }
+ while (value > 0) {
+ char str[2] = {char('0' + value % 10), '\0'};
+ s = ConstexprString(str) + s;
+ value /= 10;
+ }
+ if (negative)
+ s = "-" + s;
+ return s;
+ }
+}
+
+struct A { int x, y, z : 3; int : 4; };
+struct B : A { int p, q; struct { int anon1, anon2; }; union { int anon3; }; };
+
+static_assert(ToString(B{1, 2, 3, 4, 5, 6, 7, 8}) == "{{x = 1, y = 2, z : 3 = 3}, p = 4, q = 5, anon1 = 6, anon2 = 7, anon3 = 8}");
+
+void errors(B b) {
+ __builtin_reflect_struct(); // expected-error {{too few arguments to function call, expected 2, have 0}}
+ __builtin_reflect_struct(1); // expected-error {{too few arguments to function call, expected 2, have 1}}
+ __builtin_reflect_struct(1, 2); // expected-error {{expected pointer to struct as 1st argument to '__builtin_reflect_struct', found 'int'}}
+ __builtin_reflect_struct(&b, 2); // expected-error {{called object type 'int' is not a function or function pointer}}
+ __builtin_reflect_struct(&b, FieldsToString, 0); // expected-error {{too many arguments to function call, expected single argument 'elements', have 2 arguments}}
+}
+
+[[nodiscard]] int nodiscard(int);
+int yesdiscard(int);
+void test_discard() {
+ struct Empty {} empty;
+ __builtin_reflect_struct(&empty, nodiscard); // expected-warning {{ignoring return value of function declared with 'nodiscard' attribute}}
+ __builtin_reflect_struct(&empty, yesdiscard);
+}
Index: clang/test/Sema/builtin-dump-struct.c
===================================================================
--- clang/test/Sema/builtin-dump-struct.c
+++ clang/test/Sema/builtin-dump-struct.c
@@ -14,15 +14,15 @@
__builtin_dump_struct(); // expected-error {{too few arguments to function call, expected 2, have 0}}
__builtin_dump_struct(1); // expected-error {{too few arguments to function call, expected 2, have 1}}
- __builtin_dump_struct(1, 2); // expected-error {{passing 'int' to parameter of incompatible type structure pointer: type mismatch at 1st parameter ('int' vs structure pointer)}}
+ __builtin_dump_struct(1, 2); // expected-error {{expected pointer to struct as 1st argument to '__builtin_dump_struct', found 'int'}}
__builtin_dump_struct(&a, 2); // expected-error {{passing 'int' to parameter of incompatible type 'int (*)(const char *, ...)': type mismatch at 2nd parameter ('int' vs 'int (*)(const char *, ...)')}}
- __builtin_dump_struct(b, goodfunc); // expected-error {{passing 'void *' to parameter of incompatible type structure pointer: type mismatch at 1st parameter ('void *' vs structure pointer)}}
+ __builtin_dump_struct(b, goodfunc); // expected-error {{expected pointer to struct as 1st argument to '__builtin_dump_struct', found 'void *'}}
__builtin_dump_struct(&a, badfunc1); // expected-error {{passing 'int (*)(const char *)' to parameter of incompatible type 'int (*)(const char *, ...)': type mismatch at 2nd parameter ('int (*)(const char *)' vs 'int (*)(const char *, ...)')}}
__builtin_dump_struct(&a, badfunc2); // expected-error {{passing 'int (*)(int, ...)' to parameter of incompatible type 'int (*)(const char *, ...)': type mismatch at 2nd parameter ('int (*)(int, ...)' vs 'int (*)(const char *, ...)')}}
__builtin_dump_struct(&a, badfunc3); // expected-error {{passing 'void (*)(const char *, ...)' to parameter of incompatible type 'int (*)(const char *, ...)': type mismatch at 2nd parameter ('void (*)(const char *, ...)' vs 'int (*)(const char *, ...)')}}
__builtin_dump_struct(&a, badfunc4); // expected-error {{passing 'int (*)(char *, ...)' to parameter of incompatible type 'int (*)(const char *, ...)': type mismatch at 2nd parameter ('int (*)(char *, ...)' vs 'int (*)(const char *, ...)')}}
__builtin_dump_struct(&a, badfunc5); // expected-error {{passing 'int (*)(void)' to parameter of incompatible type 'int (*)(const char *, ...)': type mismatch at 2nd parameter ('int (*)(void)' vs 'int (*)(const char *, ...)')}}
- __builtin_dump_struct(a, goodfunc); // expected-error {{passing 'struct A' to parameter of incompatible type structure pointer: type mismatch at 1st parameter ('struct A' vs structure pointer)}}
+ __builtin_dump_struct(a, goodfunc); // expected-error {{expected pointer to struct as 1st argument to '__builtin_dump_struct', found 'struct A'}}
}
void valid_uses(void) {
Index: clang/test/CodeGenCXX/builtin-reflect-struct.cpp
===================================================================
--- /dev/null
+++ clang/test/CodeGenCXX/builtin-reflect-struct.cpp
@@ -0,0 +1,48 @@
+// RUN: %clang_cc1 -std=c++20 -triple x86_64-linux-gnu %s -emit-llvm -o - | FileCheck %s
+
+// CHECK: @[[F_STR:.*]] = {{.*}} constant [2 x i8] c"f\00",
+// CHECK: @[[G_STR:.*]] = {{.*}} constant [2 x i8] c"g\00",
+
+struct A { int n; };
+struct B { int n; };
+struct C : A, B { int f, g; };
+
+struct Base {
+ Base(void*);
+ void *p;
+};
+struct Field {
+ Field(const char*, int&);
+ int n;
+};
+
+struct Expected {
+ Base a, b;
+ Field f, g;
+};
+
+int consume(int, Expected);
+
+C c;
+
+// CHECK: %[[VAL:.*]] = alloca %[[STRUCT_EXPECTED:.*]],
+// CHECK: call void @_ZN4BaseC1EPv(ptr {{.*}}, ptr {{.*}} @c)
+// CHECK: %[[BASE_B:.*]] = {{.*}} getelementptr inbounds (i8, ptr @c, i64 4)
+// CHECK: call void @_ZN4BaseC1EPv(ptr {{.*}}, ptr {{.*}} %[[BASE_B]])
+// CHECK: call void @_ZN5FieldC1EPKcRi(ptr {{.*}}, ptr {{.*}} @[[F_STR]], ptr {{.*}} getelementptr inbounds (%struct.C, ptr @c, i32 0, i32 2))
+// CHECK: call void @_ZN5FieldC1EPKcRi(ptr {{.*}}, ptr {{.*}} @[[G_STR]], ptr {{.*}} getelementptr inbounds (%struct.C, ptr @c, i32 0, i32 3))
+// CHECK: %[[RET:.*]] = call {{.*}} i32 @_Z7consumei8Expected(i32 {{.*}} 42, ptr noundef byval(%[[STRUCT_EXPECTED]]) {{.*}} %[[VAL]])
+// CHECK: store i32 %[[RET]], ptr @k
+int k = __builtin_reflect_struct(&c, consume, 42);
+
+// A discarded volatile lvalue produced by the builtin should not be loaded
+// from.
+struct Empty {} empty;
+volatile int &volatile_return(int);
+// CHECK-LABEL: define {{.*}}check_volatile
+void check_volatile() {
+ // CHECK: call {{.*}}volatile_return
+ // CHECK-NOT: load volatile
+ // CHECK: }
+ __builtin_reflect_struct(&empty, volatile_return);
+}
Index: clang/lib/Sema/SemaStmt.cpp
===================================================================
--- clang/lib/Sema/SemaStmt.cpp
+++ clang/lib/Sema/SemaStmt.cpp
@@ -341,7 +341,7 @@
return DiagnoseUnusedExprResult(POE->getSemanticExpr(0), DiagID);
if (isa<ObjCSubscriptRefExpr>(Source))
DiagID = diag::warn_unused_container_subscript_expr;
- else
+ else if (isa<ObjCPropertyRefExpr>(Source))
DiagID = diag::warn_unused_property_expr;
} else if (const CXXFunctionalCastExpr *FC
= dyn_cast<CXXFunctionalCastExpr>(E)) {
Index: clang/lib/Sema/SemaChecking.cpp
===================================================================
--- clang/lib/Sema/SemaChecking.cpp
+++ clang/lib/Sema/SemaChecking.cpp
@@ -366,6 +366,114 @@
return false;
}
+static ExprResult SemaBuiltinReflectStruct(Sema &S, CallExpr *TheCall) {
+ if (TheCall->getNumArgs() < 2 && checkArgCount(S, TheCall, 2))
+ return ExprError();
+
+ SourceLocation Loc = TheCall->getBeginLoc();
+
+ // First argument should be a pointer to a struct.
+ ExprResult PtrArgResult = S.DefaultLvalueConversion(TheCall->getArg(0));
+ if (PtrArgResult.isInvalid())
+ return ExprError();
+ Expr *PtrArg = PtrArgResult.get();
+ QualType PtrArgType = PtrArg->getType();
+ if (!PtrArgType->isPointerType() ||
+ !PtrArgType->getPointeeType()->isRecordType()) {
+ S.Diag(PtrArg->getBeginLoc(), diag::err_expected_struct_pointer_argument)
+ << 1 << TheCall->getDirectCallee() << PtrArgType;
+ return ExprError();
+ }
+ const RecordDecl *RD = PtrArgType->getPointeeType()->getAsRecordDecl();
+
+ // Build an OpaqueValueExpr so we can refer to the argument more than once.
+ auto *ArgOVE = new (S.Context)
+ OpaqueValueExpr(Loc, PtrArg->getType(), PtrArg->getValueKind(),
+ PtrArg->getObjectKind(), PtrArg);
+
+ // Start with all the provided arguments after the callee.
+ SmallVector<Expr *, 8> Args(TheCall->arg_begin() + 2, TheCall->arg_end());
+
+ SmallVector<Expr *, 32> Elts;
+
+ // Add a 'static_cast<Base*>(arg)' argument for each base class.
+ if (const CXXRecordDecl *CXXRD = dyn_cast<CXXRecordDecl>(RD)) {
+ for (const auto &Base : CXXRD->bases()) {
+ QualType BaseType = S.Context.getPointerType(S.Context.getQualifiedType(
+ Base.getType(), PtrArgType->getPointeeType().getQualifiers()));
+ ExprResult BasePtr = S.BuildCXXNamedCast(
+ Loc, tok::kw_static_cast,
+ S.Context.getTrivialTypeSourceInfo(BaseType, Loc), ArgOVE,
+ SourceRange(Loc, Loc), SourceRange(Loc, Loc));
+ if (BasePtr.isInvalid())
+ return ExprError();
+ Elts.push_back(BasePtr.get());
+ }
+ }
+
+ // Add a '{"field_name", arg->field_name}' argument for each field.
+ // Add a '{"field_name", arg->field_name, bitwidth}' argument for each
+ // bit-field.
+ for (auto *D : RD->decls()) {
+ auto *IFD = dyn_cast<IndirectFieldDecl>(D);
+ auto *FD = IFD ? IFD->getAnonField() : dyn_cast<FieldDecl>(D);
+ if (!FD || (FD->isUnnamedBitfield() || FD->isAnonymousStructOrUnion()))
+ continue;
+
+ Expr *Name = S.Context.getPredefinedStringLiteralFromCache(FD->getName());
+
+ // FIXME: Access check?
+ ExprResult Field =
+ IFD ? S.BuildAnonymousStructUnionMemberReference(
+ CXXScopeSpec(), Loc, IFD,
+ DeclAccessPair::make(IFD, IFD->getAccess()), ArgOVE, Loc)
+ : S.BuildFieldReferenceExpr(
+ ArgOVE, /*IsArrow=*/true, Loc, CXXScopeSpec(), FD,
+ DeclAccessPair::make(FD, FD->getAccess()),
+ DeclarationNameInfo(FD->getDeclName(), Loc));
+ if (Field.isInvalid())
+ return ExprError();
+
+ Expr *Width = nullptr;
+ if (FD->isBitField()) {
+ QualType SizeT = S.Context.getSizeType();
+ llvm::APInt BitWidth(S.Context.getIntWidth(SizeT),
+ FD->getBitWidthValue(S.Context));
+ Width = IntegerLiteral::Create(S.Context, BitWidth, SizeT, Loc);
+ }
+
+ Expr *Parts[] = {Name, Field.get(), Width};
+ MultiExprArg PartList = Parts;
+ if (!Width)
+ PartList = PartList.drop_back(1);
+
+ ExprResult InitList = S.BuildInitList(Loc, PartList, Loc);
+ if (InitList.isInvalid())
+ return ExprError();
+
+ Elts.push_back(InitList.get());
+ }
+
+ // Wrap all the reflection data in '{'...'}'.
+ ExprResult ReflectionData = S.BuildInitList(Loc, Elts, Loc);
+ Args.push_back(ReflectionData.get());
+
+ // Call the second argument with the assembled argument list.
+ ExprResult RealCall =
+ S.BuildCallExpr(/*Scope=*/nullptr, TheCall->getArg(1),
+ TheCall->getBeginLoc(), Args, TheCall->getRParenLoc());
+ if (RealCall.isInvalid())
+ return ExprError();
+
+ // The result of the real call is the result of the builtin call.
+ TheCall->setType(RealCall.get()->getType());
+ TheCall->setValueKind(RealCall.get()->getValueKind());
+
+ Expr *SemanticForm[] = {ArgOVE, RealCall.get()};
+ return PseudoObjectExpr::Create(S.Context, TheCall, SemanticForm,
+ /*ResultIdx*/ 1);
+}
+
static bool SemaBuiltinCallWithStaticChain(Sema &S, CallExpr *BuiltinCall) {
if (checkArgCount(S, BuiltinCall, 2))
return true;
@@ -2022,9 +2130,8 @@
const QualType PtrArgType = PtrArg->getType();
if (!PtrArgType->isPointerType() ||
!PtrArgType->getPointeeType()->isRecordType()) {
- Diag(PtrArg->getBeginLoc(), diag::err_typecheck_convert_incompatible)
- << PtrArgType << "structure pointer" << 1 << 0 << 3 << 1 << PtrArgType
- << "structure pointer";
+ Diag(PtrArg->getBeginLoc(), diag::err_expected_struct_pointer_argument)
+ << 1 << FDecl << TheCall->getArg(0)->getType();
return ExprError();
}
@@ -2069,6 +2176,8 @@
TheCall->setType(Context.IntTy);
break;
}
+ case Builtin::BI__builtin_reflect_struct:
+ return SemaBuiltinReflectStruct(*this, TheCall);
case Builtin::BI__builtin_expect_with_probability: {
// We first want to ensure we are called with 3 arguments
if (checkArgCount(*this, TheCall, 3))
Index: clang/lib/AST/Expr.cpp
===================================================================
--- clang/lib/AST/Expr.cpp
+++ clang/lib/AST/Expr.cpp
@@ -2457,8 +2457,12 @@
}
// Objective-C++ extensions to the rule.
- if (isa<PseudoObjectExpr>(E) || isa<ObjCIvarRefExpr>(E))
+ if (isa<ObjCIvarRefExpr>(E))
return true;
+ if (const auto *POE = dyn_cast<PseudoObjectExpr>(E)) {
+ if (isa<ObjCPropertyRefExpr, ObjCSubscriptRefExpr>(POE->getSyntacticForm()))
+ return true;
+ }
return false;
}
@@ -2708,23 +2712,35 @@
}
case ObjCPropertyRefExprClass:
+ case ObjCSubscriptRefExprClass:
WarnE = this;
Loc = getExprLoc();
R1 = getSourceRange();
return true;
case PseudoObjectExprClass: {
- const PseudoObjectExpr *PO = cast<PseudoObjectExpr>(this);
+ const auto *POE = cast<PseudoObjectExpr>(this);
- // Only complain about things that have the form of a getter.
- if (isa<UnaryOperator>(PO->getSyntacticForm()) ||
- isa<BinaryOperator>(PO->getSyntacticForm()))
- return false;
+ // For some syntactic forms, we should always warn.
+ if (isa<ObjCPropertyRefExpr, ObjCSubscriptRefExpr>(
+ POE->getSyntacticForm())) {
+ WarnE = this;
+ Loc = getExprLoc();
+ R1 = getSourceRange();
+ return true;
+ }
- WarnE = this;
- Loc = getExprLoc();
- R1 = getSourceRange();
- return true;
+ // For others, we should never warn.
+ if (auto *BO = dyn_cast<BinaryOperator>(POE->getSyntacticForm()))
+ if (BO->isAssignmentOp())
+ return false;
+ if (auto *UO = dyn_cast<UnaryOperator>(POE->getSyntacticForm()))
+ if (UO->isIncrementDecrementOp())
+ return false;
+
+ // Otherwise, warn if the result expression would warn.
+ const Expr *Result = POE->getResultExpr();
+ return Result && Result->isUnusedResultAWarning(WarnE, Loc, R1, R2, Ctx);
}
case StmtExprClass: {
Index: clang/include/clang/Basic/DiagnosticSemaKinds.td
===================================================================
--- clang/include/clang/Basic/DiagnosticSemaKinds.td
+++ clang/include/clang/Basic/DiagnosticSemaKinds.td
@@ -8467,6 +8467,8 @@
def err_overflow_builtin_bit_int_max_size : Error<
"__builtin_mul_overflow does not support 'signed _BitInt' operands of more "
"than %0 bits">;
+def err_expected_struct_pointer_argument : Error<
+ "expected pointer to struct as %ordinal0 argument to %1, found %2">;
def err_atomic_load_store_uses_lib : Error<
"atomic %select{load|store}0 requires runtime support that is not "
Index: clang/include/clang/Basic/Builtins.def
===================================================================
--- clang/include/clang/Basic/Builtins.def
+++ clang/include/clang/Basic/Builtins.def
@@ -1602,8 +1602,9 @@
BUILTIN(__builtin_operator_new, "v*z", "tc")
BUILTIN(__builtin_operator_delete, "vv*", "tn")
BUILTIN(__builtin_char_memchr, "c*cC*iz", "n")
-BUILTIN(__builtin_dump_struct, "ivC*v*", "tn")
+BUILTIN(__builtin_dump_struct, "ivC*v*", "t")
BUILTIN(__builtin_preserve_access_index, "v.", "t")
+BUILTIN(__builtin_reflect_struct, "v.", "t")
// Alignment builtins (uses custom parsing to support pointers and integers)
BUILTIN(__builtin_is_aligned, "bvC*z", "nct")
Index: clang/docs/ReleaseNotes.rst
===================================================================
--- clang/docs/ReleaseNotes.rst
+++ clang/docs/ReleaseNotes.rst
@@ -68,6 +68,10 @@
Randomizing structure layout is a C-only feature.
+- Clang now supports a ``__builtin_reflect_struct`` builtin. This builtin
+ provides a simple and flexible way to reflect on the fields and base classes
+ of an object of struct or class type.
+
Bug Fixes
------------------
- ``CXXNewExpr::getArraySize()`` previously returned a ``llvm::Optional``
Index: clang/docs/LanguageExtensions.rst
===================================================================
--- clang/docs/LanguageExtensions.rst
+++ clang/docs/LanguageExtensions.rst
@@ -2407,6 +2407,81 @@
function whose signature must be: ``int (*)(const char *, ...)`` and must
support the format specifiers used by ``printf()``.
+``__builtin_reflect_struct``
+----------------------------
+
+**Syntax**:
+
+.. code-block:: c++
+
+ __builtin_reflect_struct(&some_struct, some_func, args...);
+
+**Examples**:
+
+.. code-block:: c++
+
+ struct S {
+ int x, y;
+ int z : 4;
+ };
+
+ struct Field {
+ const char *name;
+ int value;
+ int bitwidth;
+ };
+
+ bool print(int arg1, int arg2, std::initializer_list<Field> fields) {
+ for (auto f : fields) {
+ std::cout << f.name;
+ if (f.bitwidth)
+ std::cout << " : " << f.bitwidth;
+ std::cout << " = " << f.value << std::endl;
+ }
+ return true;
+ }
+
+ bool func(int arg1, int arg2) {
+ S s = {1, 2, 3};
+ return __builtin_reflect_struct(&s, print, arg1, arg2);
+ }
+
+Example output:
+
+.. code-block:: none
+
+ x = 1
+ y = 2
+ z : 4 = 3
+
+**Description**:
+
+The ``__builtin_reflect_struct`` function provides simple reflection for a
+class, struct, or union. The first argument of the builtin should be a pointer
+to the type to reflect. The second argument ``f`` should be some callable
+expression, and can be a function object or an overload set. The builtin calls
+``f``, passing any further arguments ``args...`` followed by an initializer
+list describing the struct value.
+
+.. code-block:: none
+
+ // '__builtin_reflect_struct(&s, print, arg1, arg2)' calls:
+ print(arg1, arg2, {{"x", 1}, {"y", 2}, {"z", 3, 4}})
+
+The initializer list contains the following components:
+
+* For each direct base class, the address of the base class, in declaration
+ order.
+* For each field, ``{"field_name", p->field_name}`` for a regular field, and
+ ``{"field_name", p->field_name, bit_width}`` for a bit-field, in declaration
+ order.
+
+The result of the builtin is the result of the call to function ``f``.
+
+This builtin can be used in constant expressions.
+
+Query for this feature with ``__has_builtin(__builtin_reflect_struct)``
+
.. _langext-__builtin_shufflevector:
``__builtin_shufflevector``
_______________________________________________
cfe-commits mailing list
[email protected]
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits