Boilerplate-Free Type Erasure via C++26 Reflection
The experimental library rjk::duck leverages C++26 reflection and metaprogramming features to automate type erasure. By utilizing compile-time annotations, automated vtable generation, and native overload resolution, the single-header library eliminates manual dispatch boilerplate without sacrificing runtime performance.
Implementing custom type erasure in C++ has historically required writing extensive virtual dispatch boilerplate or integrating heavy dependencies like Boost.TypeErasure or Folly.Poly. The experimental library `rjk::duck` eliminates these manual implementation overheads by leveraging the upcoming C++26 reflection APIs. Currently compiling on GCC with the `-std=c++26 -freflection` flags, this single-header library provides owning and non-owning semantics, interface composition, and operator support by offloading interface parsing and vtable generation to the compiler.
Parsing Interfaces via C++26 Annotations
The library uses C++26 annotations to declare interfaces. Users define a standard struct annotated with `[[=rjk::trait]]`. The library evaluates this annotation at compile time using the reflection operator `^^`, which yields a metadata token of type `std::meta::info`.
Using compile-time functions, `rjk::duck` queries these tokens to extract metadata. The trait parsing process filters the interface's members using an unprivileged access context, retaining only public, user-declared member functions while discarding constructors and data members. Each qualified member is then transformed into an internal representation called a tag, defined as `has_fn<"name", signature>`, which captures the function's identifier and type signature.
Automated Vtable Generation and Splice Operations
Rather than relying on manually defined virtual tables, the library generates the underlying `vtable` structure programmatically. Within a `consteval` block, it constructs a dynamic representation of the struct using `define_aggregate`.
The generator loops through the specified traits using the new C++26 `template for` expansion statement, which allows direct iteration over parameter packs. For each trait member, it strips cvref qualifiers, prepends an erased `void*` to the signature, and appends a corresponding function pointer to the member list via `data_member_spec`.
To instantiate the static vtable for a concrete type `T`, the library maps the slots of the generated `vtable` to the actual member functions of `T`. This mapping relies on the new splice operator (`[: expression :]`), which transitions from the reflection domain back to concrete C++ syntax, assigning the resolved function pointers directly to the vtable fields.
Type Erasure and Overload Resolution
At the execution boundary, concrete method calls must undergo type erasure. The library achieves this through an `erased_call` wrapper inside `vtable_fn_maker`. This wrapper accepts an erased `void self` argument, casts it back to `T`, and dispatches the call with `std::forward` arguments using `std::invoke`.
To determine which function to bind to, the library avoids reimplementing the C++ compiler's overload resolution logic. Instead, it utilizes a variadic `overload_set` structure that inherits from multiple callables and introduces their call operators into the scope via using-declarations. By compiling candidates into this native overload set, the library allows standard C++ overload resolution to handle the dispatch mechanics natively.