C++ BUT IT HAS PYTHON'S DIRS()
Reflected types:
- reflect::FieldInfo (size=32, align=8)
. name @+0 : const char * (set=yes)
. offset @+8 : std::uint32_t (set=yes)
. type_name @+16 : const char * (set=yes)
. set_fn @+24 : reflect::SetFn (set=yes)
- reflect::TypeInfo (size=40, align=8)
. name @+0 : const char * (set=yes)
. type_id @+8 : std::uint64_t (set=yes)
. size @+16 : std::uint32_t (set=yes)
. align @+20 : std::uint32_t (set=yes)
. fields @+24 : const struct reflect::FieldInfo * (set=yes)
. field_count @+32 : std::uint32_t (set=yes)
- reflect::TypeRange (size=16, align=8)
. begin @+0 : const struct reflect::TypeInfo *const * (set=yes)
. end @+8 : const struct reflect::TypeInfo *const * (set=yes)
- demo::Foo (size=48, align=8)
. x @+0 : int (set=yes)
. y @+8 : double (set=yes)
. name @+16 : std::string (set=yes)
- demo::Bar (size=56, align=8)
. f @+0 : struct demo::Foo (set=yes)
. z @+48 : int (set=yes)
BEFORE: Foo{ x=1, y=2.5, name="old" }
BEFORE: Bar{ f={ x=1, y=2.5, name="old" }, z=7 }
AFTER: Foo{ x=123, y=9.25, name="hello from set()" }
AFTER: Bar{ f={ x=-1, y=0.5, name="nested assignment" }, z=42 }
A small proof-of-concept that gives C++ a dir()-like experience.
It works by:
- using Clang LibTooling at build time to scan your translation unit(s),
- generating a
.reflect.cppfile containing metadata + per-field setter thunks, - registering pointers to that metadata into an ELF linker section (
reflect_types), - exposing a tiny runtime API to enumerate types and set fields on live objects.
This is not C++ standard reflection. It’s build-time AST extraction + runtime metadata.
What you get
- Enumerate all reflected types at runtime (
reflect::all_types()) - Inspect struct/class layout:
- type name,
sizeof,alignof - field name, byte offset, type string
- type name,
- Set fields on live objects safely using generated assignment thunks:
- supports trivial fields (
int,double, etc.) - supports non-trivial fields (
std::string, nested structs) via real C++ assignment
- supports trivial fields (
How it works (high level)
- CMake exports
compile_commands.json. reflect_toolruns with-p <build_dir>so Clang uses the exact flags used by your build.- The tool walks the Clang AST (records + fields), computes offsets via
ASTRecordLayout, then emits:reflect::FieldInfo[]reflect::TypeInfoset_*thunks for settable fieldsREFLECT_SECTIONpointers into thereflect_typessection
reflect_runtimeuses__start_reflect_types/__stop_reflect_types(ELF) to producereflect::all_types().
Build & run
cmake -S . -B build -DCMAKE_BUILD_TYPE=Debug
cmake --build build -j
./build/examples/demo/demoUsing it
Typical flow:
#include "reflect/runtime.hpp" const reflect::TypeInfo* ti = reflect::find_by_name("demo::Foo"); reflect::set_field(&foo, ti, "x", 123); reflect::set_field(&foo, ti, "name", std::string("hello"));
set_field() works only when a field has a generated set_fn. Fields that are const, refs, arrays, or bitfields are marked non-settable.
Notes / limitations
- Linux/ELF PoC: relies on linker section start/stop symbols. (macOS/Windows need different plumbing.)
- Access control: setters are compiled “from the outside”. Private/protected fields will not be settable without an opt-in/friend strategy.
- Methods, base classes, templates, and rich type graphs are out of scope for this PoC.
Directory layout
reflect_tool/— Clang-based generator (build-time)reflect_runtime/— minimal runtime API + metadata structsexamples/demo/— demo types + executable