GitHub - maya-undefined/CPP_BUT_WITH_PYTHONS_DIRS: gives C++ a `dir()`-like experience.

3 min read Original article ↗

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.cpp file 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
  • 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

How it works (high level)

  1. CMake exports compile_commands.json.
  2. reflect_tool runs with -p <build_dir> so Clang uses the exact flags used by your build.
  3. The tool walks the Clang AST (records + fields), computes offsets via ASTRecordLayout, then emits:
    • reflect::FieldInfo[]
    • reflect::TypeInfo
    • set_* thunks for settable fields
    • REFLECT_SECTION pointers into the reflect_types section
  4. reflect_runtime uses __start_reflect_types/__stop_reflect_types (ELF) to produce reflect::all_types().

Build & run

cmake -S . -B build -DCMAKE_BUILD_TYPE=Debug
cmake --build build -j
./build/examples/demo/demo

Using 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 structs
  • examples/demo/ — demo types + executable