A tiny, header-only runtime reflection and enum-to-string helper for C++20. It lets you:
- Describe a type’s fields once (name, type, offset, size, flags).
- Auto-register that metadata into a global registry at static init.
- Read/write fields at runtime (type-checked), with optional
onChange. - Detect “container-ish” types (vector/map/set/tuple/optional/variant/string/enum).
- Provide a unified
AnyToStringRuntime()that can stringify primitives, strings, registered enums, and custom field stringify functions.
For a complete usage walkthrough, see
example.cpp.
-
stress::FieldInfo: per-field metadataname,type(std::type_index),offset,size- flags:
isSerializable,isPrivate,isReadonly containerKind(auto-detected)- optional hooks:
onChange,toString
-
stress::TypeInfo: per-type metadatatypeNameproperties(std::vector<FieldInfo>)
stress::gTypeRegistry[typeid(T)] -> TypeInfostress::gToStringRegistry[typeid(EnumType)] -> ToStringFn
These are filled automatically via the macros below.
Inside stress::access:
-
getFieldPtr(obj, field)(void* pointer arithmetic viafield.offset) -
setFieldValue(obj, field, value)- fails if
field.isReadonlyortypeid(Value)mismatch - calls
field.onChangeif present
- fails if
-
getFieldValue(obj, field, out) -
AnyToStringRuntime(field, ptr)- handles
std::string, registered enums, customFieldInfo::toString - otherwise returns
"<unregistered: ...>"
- handles
-
Make your type inherit from
stress::Reflectable<T>using the macros:STRESS_STRUCT(T)/STRESS_CLASS(T)(or_INHERITvariants)
-
Provide a
fields()function returning a tuple ofFieldInfo:STRESS_FIELDS(STRESS_FIELD(a), STRESS_FIELD(b), ...)
-
The
STRESS_FIELDS(...)macro also instantiates:static inline ::stress::AutoRegister<Self> __auto_reg{};which registers the type intogTypeRegistryat program start.
Once registered, you can query:
stress::getTypeInfo<T>()(by template type)stress::getTypeInfo(typeid(T))(by runtimetype_index)
All STRESS_*FIELD* macros expand to calls that create stress::FieldInfo via:
stress::makeField(name, memberPtr, isSerializable, isPrivate, isReadonly)stress::makeFieldWithToString(...)(custom stringify thunk)
-
STRESS_FIELD(member)Public, not serializable, not readonly. -
STRESS_PRIVATE_FIELD(member)Marks field as “private” for tooling/UI (does not enforce C++ access control). -
STRESS_READONLY_FIELD(member)Blocks runtimeset(...)calls. -
STRESS_FIELD_SERIALIZABLE(member)Same asSTRESS_FIELDbutisSerializable = true.
STRESS_FIELD_WITH_TOSTRING_BODY(member, BODY)Lets you embed a tiny lambda body that returnsstd::string. Useful for types that don’t haveoperator<<and you want a nice display string.
This header includes a small “X-macro” enum pattern that gives you:
EnumToString(EnumType)(aconstexpr std::string_viewfunction)EnumTypeValues(array of all enumerators)EnumTypeCount- registration into
gToStringRegistryso runtime stringify works
STRESS_ENUM_DEFINE(EnumType, LIST_MACRO)STRESS_ENUM_REGISTER(EnumType)STRESS_ENUM_DEFINE_VALUES(EnumType, LIST_MACRO)STRESS_ENUM_DEFINE_AND_REGISTER(EnumType, LIST_MACRO)(the usual one)STRESS_ENUM_DECLARE_DEFINE_REGISTER(EnumType, Underlying, LIST_MACRO)(declares enum + all helpers)
The registration uses an internal AutoRegisterFn to insert a ToStringFn into gToStringRegistry[typeid(EnumType)].
There is a bunch of other macros listed in stress_reflection.hpp.
stress::ContainerKind is computed at compile time via getContainerKind<T>(), based on C++20 concepts / traits:
StringLike→StringEnumLike→EnumMapLike→MapSetLike→SetFixedArrayLike(C array orstd::array) →FixedArrayVectorLike(iterable +size()+operator[], excluding string/map/set) →VectorTupleLike→TupleOptionalLike→OptionalVariantLike→Variant- else →
None
This is stored into FieldInfo::containerKind automatically by makeField().
Base class your types inherit from. Provides:
static TypeInfo makeTypeInfo()static void registerType()bool set(field, value)bool get(field, out)
Used by STRESS_FIELDS(...) to auto-register a reflectable type.
Builds FieldInfo, computes:
- member offset (
member_offset) - size (
sizeof(Member)) - readonly flag (explicit or
constmember) - container kind
Main runtime stringifier:
- checks string, then enum registry, then
FieldInfo::toString
- Header-only, global state:
gTypeRegistryandgToStringRegistryareinlineglobals. - Static initialization order: registration happens during static init; if you query registries very early (before those translation units run), you can hit missing entries.
- Offsets:
member_offsetuses a non-zero “fake base” pointer to avoid null warnings. This pattern assumes typical object layout rules for standard data members. - “Private” is a flag, not enforcement:
isPrivateis metadata only. - Type checking: runtime
set/getrequire exacttypeidmatches.
-
See
example.cppfor:- declaring a reflectable struct/class
- registering fields
- defining + registering enums
- iterating
TypeInfo.properties - runtime set/get + runtime stringify