Chinese: README.zh.md
C++26 reflection-based Trait polymorphism: static handle iface, erased handle dyn, and optional field view mirror.
Namespace asf. Conceptually close to Rust trait / dyn Trait, implemented with C++26 reflection and vtable layout.
| Topic | Notes |
|---|---|
| Trait | Interface + impl: Trait<void> method table, Trait<T> bound type |
| Handles | iface keeps the concrete type; dyn erases to a full Ptr + vtable |
| Pointers | Second template parameter is the full erased Ptr: void* / unique_ptr<void> / shared_ptr<void> / weak_ptr<void> / cache_ptr, ... |
| Casts | as / trait_cast / to_dyn / dyn_cast along the Trait inheritance DAG |
| Flags | ASF_HAS_DYN_* for RTTI, intermediate bind, multi-vtable Redirect/cover, single inheritance, ... |
Docs:
- Conceptual guide (Chinese)
- API index (Chinese)
- Pointer semantics (Chinese)
- Doc hub: docs/README.md
Header-only. Needs C++26 reflection (GCC 16+ with -freflection). Add include/ to the include path; GCC usually also needs stdc++exp.
target_include_directories(app PRIVATE path/to/asf_dyn/include)
target_compile_options(app PRIVATE -freflection -std=c++26)
target_link_libraries(app PRIVATE stdc++exp)Single-file example:
g++ -std=c++26 -freflection -Iinclude examples/en/01_static.cpp -lstdc++exp -o 01_staticTests:
cmake -S . -B build -G Ninja -DCMAKE_CXX_COMPILER=g++
cmake --build build
ctest --test-dir build#include "interface.hpp"
#include "cast.hpp"
#include <print>
#include <string>
template <typename T = void>
struct Greeter;
template <>
struct Greeter<void> { // interface
void greet(auto& self);
auto who(auto const& self) -> std::string const& {
return self.name;
}
};
struct Person { // bound value type
std::string name;
};
template <> // final leaf: impl for Person
struct Greeter<Person> final {
void greet(Person& self) {
std::println("hello {}", self.name);
}
};
int main() {
Person p{"Ada"}; // object definition
asf::dyn<Greeter> d{&p};
// If you dyn-bind a base and the whole program never ODR-uses a covering
// final leaf (instantiate_dyn or a pointer/mirror bind like this), pick one
// final and instantiate: template struct asf::instantiate_dyn<Greeter, Person>;
asf::iface<Greeter, Person> i{p};
i->greet();
std::println("iface who = {}", i->who());
d->greet();
std::println("dyn who = {}", d->who());
// factory
auto d2 = asf::to_dyn<Greeter>(&p);
d2->greet();
}Notes:
Greeter<void>is the interface;selfis an object or a mirror.Greeter<Person> finalis the impl leaf.Person p{...};is the object definition. See dyn.md.h->method(...)forwards through a temporary proxy.- Umbrella header
interface.hpp; casts incast.hpp.
More examples: examples/ (en/ / zh/, UTF-8).
| Rust | ASF |
|---|---|
trait Trait { ... } |
Trait<void> (method table) |
impl Trait for T { ... } |
Trait<T> (dyn leaf usually final) |
impl Trait for T (static) |
iface<Trait, T> |
&dyn Trait / &mut dyn Trait |
dyn / c_dyn + void* (non-owning by default) |
Box<dyn Trait> |
unique_dyn<Trait> (unique_ptr<void>; default Deleter is std::default_delete) |
Arc<dyn Trait> / Rc<dyn Trait> |
shared_dyn<Trait> (shared_ptr<void>) |
dyn Trait upcast |
converting ctor / trait_cast (see ASF_HAS_DYN_MULTI_VT) |
Any + downcast |
ASF_HAS_DYN_RTTI + dyn_cast |
| Field reflection (no direct analogue) | mirror<T> / mirror<T, Src> (optional) |
- iface: keeps the concrete type; Trait methods bind statically on Self (object / mirror / src).
as<To>()is a same-object base view.get()is not ref-qualified and returnsSelf: for a plain object that is the storedT&; for a mirror Self it is a copy of the stored mirror (unlike dynget(), which returnsPtr&). Anifaceis always bound (no null state, nooperator bool);==is identity by address. - dyn / c_dyn:
dyn<Trait, Ptr>;Ptris the full erased type (deleter / Alloc included). Aliases:unique_dyn/shared_dyn/cache_dyn, ... - Owning Ptr: default
void*does not own;unique_dyn<Trait[, Deleter]>with omittedDeleterisstd::default_delete<void>; custom Alloc usesalloc_delete<Alloc>; shared / weak / cache: pointers.md. - Pointer bind and
final: vtable forT*/ mirror ->dyncomes from a final leaf; ODR-use a covering final leaf or the link fails. See dyn.md. - sub_dyn: a section slice of one Trait on the leaf table; no secondary upcast.
- Annotations:
[[=imple]]/[[=expl]]/[[=mirror]], ... control conversions and views;staticopts a method out of the dyn vtable; annotations.md. - Feature flags: RTTI, multi-vtable Redirect/cover, single inheritance, ... features.md.
- mirror (optional): field-ref bundle; mirror.md.
| Compiler | Status | Flags |
|---|---|---|
| GCC | 16.x (primary) | -std=c++26 -freflection, often stdc++exp |
MSVC / Clang C++26 reflection support is still evolving; this tree currently tracks GCC 16.x only.
| Macro | Default | Meaning |
|---|---|---|
ASF_HAS_DYN_RTTI |
OFF | Vtable meta header / dyn_cast (experimental; see features.md) |
ASF_HAS_DYN_INTERMEDIATE |
ON | Non-final intermediate Trait may bind dyn; needs final-leaf inject; dyn.md |
ASF_HAS_DYN_MULTI_VT |
ON | Redirect/cover, full-path trait_cast upcast; mutex with ASF_HAS_DYN_SINGLE_INHERIT |
ASF_HAS_DYN_SINGLE_INHERIT |
OFF | Live-edge single inheritance (checked at dyn generation; [[=truncate]]/[[=truncated]] allowed); mutex with MULTI_VT (upcast then follows the MULTI_VT-off section-prefix rule) |
Example:
cmake -S . -B build -G Ninja -DCMAKE_CXX_COMPILER=g++ \
-DASF_HAS_DYN_RTTI=ON -DASF_HAS_DYN_INTERMEDIATE=OFFDetails: docs/en/spec/features.md. Feature flags are also baked into the ABI inline namespace as a binary tag (v0_1_4_b...).
| Header | Contents |
|---|---|
iface.hpp |
iface / to_iface |
dyn.hpp |
dyn / c_dyn / sub_* / alloc_delete / aliases |
cast.hpp |
trait_cast / c_trait_cast / to_dyn / construct / dyn_cast, ... |
ref.hpp |
ref / cref / as_ref |
interface.hpp |
Umbrella (includes dyn.hpp) |
mirror.hpp |
mirror / to_mirror / bind_mirror (optional) |
| English (this file) | README.md |
| Chinese | README.zh.md |
| Guides | docs/en/README.md / docs/zh/README.md |
| API index | docs/en/spec/README.md / docs/zh/spec/README.md |
| Spec body | docs/en/spec/ / docs/zh/spec/ |
Boost Software License 1.0 (same license as Boost). Chosen for a header-only C++ library: source copies keep the notice, compiled binaries need none.