Skip to content

Commit 9251076

Browse files
authored
Merge pull request #102 from ReflectCxx/release
Sync up.
2 parents 634a624 + d178307 commit 9251076

28 files changed

Lines changed: 4168 additions & 5814 deletions

README.md

Lines changed: 40 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -14,11 +14,11 @@
1414

1515
RTL provides type-safe run-time reflection for C++, combining compile-time guarantees with run-time flexibility.
1616

17-
It enables name-based discovery and invocation of functions, constructors, and object members through a non-intrusive, type-safe reflection system that follows modern C++ idioms. For example, consider the following function
17+
It enables name-based discovery and invocation of functions, constructors, and object members through a non-intrusive, type-safe reflection system that follows modern C++ idioms. For example, consider the following function:
1818
```c++
1919
std::string complexToStr(float real, float img);
2020
```
21-
Using RTL, you can discover this function by name and invoke it dynamically
21+
Using RTL, you can discover this function by name and call it dynamically:
2222
```c++
2323
rtl::function<std::string(float, float)> cToStr = cxx::mirror().getFunction("complexToStr")
2424
->argsT<float, float>()
@@ -32,7 +32,7 @@ if(cToStr) { // Function materialized?
3232
3333
**Performance**
3434

35-
RTL’s reflective calls are comparable to `std::function` for fully type-erased dispatch, and achieve lower call overhead *(just a function-pointer hop)* when argument and return types are known.
35+
RTL’s reflective calls are comparable to `std::function`, and achieve lower overhead when argument and return types are fully specified.
3636

3737
## Design Highlights
3838

@@ -42,32 +42,25 @@ RTL’s reflective calls are comparable to `std::function` for fully type-erased
4242

4343
* ***Zero-Overhead by Design*** – Metadata can be registered and resolved lazily. Reflection introduces no runtime cost beyond the features explicitly exercised by the user.
4444

45-
* ***Cross-Compiler Consistency***Implemented entirely in standard C++20, with no compiler extensions or compiler-specific conditional behavior.
45+
* ***Hot-Loop Ready***Typed reflection calls exhibit near-zero overhead and scale like direct calls, making RTL suitable for performance-critical and tight-loop workloads. *([Performance Summary](docs/benchmark_summary.md))*
4646

47-
* ***Tooling-Friendly Architecture*** – Reflection data is encapsulated in a single immutable, lazily-initialized structure that can be shared with external tools and frameworks without compile-time type knowledge – suitable for serializers, debuggers, test frameworks, scripting engines, and editors.
48-
49-
50-
[![Design Features](https://img.shields.io/badge/Doc-Design%20Features-blue)](./text-design-docs/DESIGN_PRINCIPLES_AND_FEATURES.md)
51-
[![RTL Syntax & Semantics](https://img.shields.io/badge/Doc-Syntax_&_Semantics-blueviolet)](./text-design-docs/RTL_SYNTAX_AND_SEMANTICS.md)
47+
* ***Tooling-Friendly Architecture*** – Reflection metadata is encapsulated in a single immutable, lazily-initialized structure that can be shared with external tools and frameworks without compile-time type knowledge – suitable for serializers, debuggers, test frameworks, scripting engines, and editors.
5248

5349
## A Quick Preview: Reflection That Looks and Feels Like C++
5450

55-
First, create an instance of `rtl::CxxMirror`
51+
First, create an instance of `rtl::CxxMirror`:
5652
```c++
5753
auto cxx_mirror = rtl::CxxMirror({ /* ...register all types here... */ });
5854
```
59-
The `cxx_mirror` object provides access to the runtime reflection system. It enables querying, introspection, and instantiation of registered types without requiring compile-time type knowledge at the call site.
60-
It can reside in any translation unit. To make it globally accessible and ensure it is initialized only when needed, a singleton access interface can be used –
55+
The `cxx_mirror` object provides access to the runtime reflection system. It references metadata for all registered entities and supports name-based lookup. The object may reside in any translation unit. To make it globally accessible while ensuring lazy initialization, a singleton access interface can be used:
6156
```c++
6257
// MyReflection.h
63-
namespace rtl { class CxxMirror; } // Forward declaration, no includes here!
64-
struct cxx { static rtl::CxxMirror& mirror(); }; // The Singleton.
58+
namespace rtl { class CxxMirror; } // Forward declaration, no includes here.
59+
struct cxx { static rtl::CxxMirror& mirror(); }; // The singleton interface.
6560
```
66-
define and register everything in an isolated translation unit
61+
define and register everything in an isolated translation unit:
6762
```c++
6863
// MyReflection.cpp
69-
#include <rtl_builder.h> // Reflection builder interface.
70-
7164
rtl::CxxMirror& cxx::mirror() {
7265
static auto cxx_mirror = rtl::CxxMirror({ // Inherently thread safe.
7366
// Register free(C-Style) function -
@@ -82,95 +75,82 @@ rtl::CxxMirror& cxx::mirror() {
8275
return cxx_mirror;
8376
}
8477
```
85-
`cxx_mirror` is an immutable, stack-allocated, value-type object and is safe to copy, but when used as a singleton (as shown above), implicit copies (e.g., `auto mirror = cxx::mirror();`) can unintentionally violate the singleton semantics, so `rtl::CxxMirror` restricts such copy construction.
86-
8778
### RTL in action:
8879

89-
**[Explore the demo code](https://github.com/ReflectCxx/RTL-Demo)**
90-
91-
Lookup the `Person` class by its registered name –
80+
Lookup the `Person` class by its registered name:
9281
```c++
9382
std::optional<rtl::Record> classPerson = cxx::mirror().getRecord("Person");
9483
if (!classPerson) { /* Class not registered. */ }
9584
```
96-
`rtl::CxxMirror` provides two lookup APIs that return reflection metadata objects: `rtl::Record` for any registered type (class, struct or pod) and `rtl::Function` for non-member functions.
85+
`rtl::CxxMirror` returns two reflection metadata objects: `rtl::Record` for any registered type (class, struct, or POD) and `rtl::Function` for non-member functions.
9786
98-
From `rtl::Record`, registered member functions can be queried as `rtl::Method`. These are metadata descriptors (not callables) and are returned as `std::optional`, which will be empty if the requested entity is not found.
87+
From `rtl::Record`, registered member functions can be obtained as `rtl::Method`. These are metadata descriptors (not callables). Callable entities are materialized by explicitly providing the argument types we intend to pass.
9988
100-
Callables are materialized by explicitly providing the argument types we intend to pass. If the signature is valid, the resulting callable can be invoked safely.
101-
For example, the overloaded constructor `Person(std::string, int)` –
89+
For example, the overloaded constructor `Person(std::string, int)`:
10290
```c++
10391
rtl::constructor<std::string, int> personCtor = classPerson->ctorT<std::string, int>();
104-
if (!personCtor) { /* Constructor with expected signature not found. */ }
10592
```
106-
Or the default constructor
93+
Or the default constructor:
10794
```c++
10895
rtl::constructor<> personCtor = classPerson->ctorT<>();
10996
```
110-
Instances can be created on the `Heap` or `Stack` with automatic lifetime management
97+
Instances can be created on the `Heap` or `Stack` with automatic lifetime management:
11198
```c++
11299
auto [err, robj] = personCtor(rtl::alloc::Stack, "John", 42);
113100
if (err != rtl::error::None) { std::cerr << rtl::to_string(err); } // Construction failed.
114101
```
115-
The constructed object is returned wrapped in `rtl::RObject`. Heap-allocated objects are internally managed via `std::unique_ptr`, while stack-allocated objects are stored directly in `std::any`.
102+
The constructed object is returned as an `rtl::RObject` in the variable `robj`.
116103
117-
Now, Lookup a member-function by name
104+
Looking up a member function by name:
118105
```c++
119106
std::optional<rtl::Method> oGetName = classPerson->getMethod("getName");
120107
if (!oGetName) { /* Member function not registered */ }
121108
```
122-
And materialize a complete type-aware caller
109+
And materialize a complete type-aware caller:
123110
```c++
124-
rtl::method<Person, std::string()> getName = oGetName->targetT<Person>()
125-
.argsT().returnT<std::string>();
126-
if (!getName) {
111+
rtl::method<Person, std::string()> getName = oGetName->targetT<Person>().argsT()
112+
.returnT<std::string>();
113+
if (!getName) { // Member function with expected signature not found.
127114
std::cerr << rtl::to_string(getName.get_init_err());
128115
}
129116
else {
130117
Person person("Alex", 23);
131118
std::string nameStr = getName(person)(); // Returns string 'Alex'.
132119
}
133120
```
134-
The above `getName` invocation is effectively a native function-pointer hop, since all types are known at compile time.
121+
The above `getName` invocation is effectively a **native function-pointer hop**, since all types are known at compile time.
135122

136-
If the concrete type `Person` is not accessible at the call site, its member functions can still be invoked by erasing the target type and using `rtl::RObject` instead. The previously constructed instance (`robj`) is passed as the target
123+
If the concrete type `Person` is not accessible at the call site, its member functions can still be invoked by erasing the target type and using `rtl::RObject` instead. The previously constructed instance (`robj`) is passed as the target:
137124
```c++
138-
rtl::method<rtl::RObject, std::string()> getName = oGetName->targetT()
139-
.argsT().returnT<std::string>();
125+
rtl::method<rtl::RObject, std::string()> getName = oGetName->targetT().argsT()
126+
.returnT<std::string>();
140127
auto [err, ret] = getName(robj)(); // Invoke and receive return as std::optional<std::string>.
141128
if (err == rtl::error::None && ret.has_value()) {
142-
std::cout << ret.value();
129+
std::string nameStr = ret.value();
143130
}
144131
```
145-
If the return type is also not known at compile time,`rtl::Return` can be used
132+
If the return type is also not known at compile time,`rtl::Return` can be used:
146133
```c++
147-
rtl::method<rtl::RObject, rtl::Return()> getName = oGetName->targetT()
148-
.argsT().returnT();
134+
rtl::method<rtl::RObject, rtl::Return()> getName = oGetName->targetT().argsT().returnT();
135+
149136
auto [err, ret] = getName(robj)(); // Invoke and receive rtl::RObject as return, wrapping std::string underneath.
150137
if (err == rtl::error::None && ret.canViewAs<std::string>()) {
151-
const std::string& name = ret.view<std::string>()->get();
152-
std::cout << name; // Safely view the returned std::string.
138+
std::string nameStr = ret.view<std::string>()->get(); // Safely view the returned std::string.
153139
}
154140
```
141+
**[Explore the demo code here](https://github.com/ReflectCxx/RTL-Demo)**
142+
155143
### How RTL Fits Together
156144

157145
At a high level, every registered C++ type is encapsulated as an `rtl::Record`. Callable entities (functions, member functions and constructors) are materialized through `rtl::Function`, `rtl::Method` and `rtl::Record`, all of which are discoverable via `rtl::CxxMirror`.
158146

159-
RTL provides the following callable entities, designed to be as lightweight and performant as `std::function` (and in many micro-benchmarks, faster when fully type-aware):
160-
161-
`rtl::function<>` – Free (non-member) functions
147+
👉 Deep Dive
162148

163-
`rtl::constructor<>` – Constructors
164-
165-
`rtl::method<>` – Non-const member functions
166-
167-
`rtl::const_method<>` – Const-qualified member functions
168-
169-
`rtl::static_method<>` – Static member functions
170-
171-
These callable types are regular value types: they can be copied, moved, stored in standard containers, and passed around like any other lightweight object.
172-
173-
When invoked, only type-erased callables return an `rtl::error`, with results provided as `rtl::RObject` *(when both the return and target types are erased)* or as `std::optional<T>` *(when only the target type is erased)*, while fully type-aware callables return `T` directly with no error (by design).
149+
[![Design Traits](https://img.shields.io/badge/Doc-Design%20Traits-blue)](./docs/DESIGN_PRINCIPLES_AND_FEATURES.md)
150+
&nbsp;
151+
[![RTL Syntax & Semantics](https://img.shields.io/badge/Doc-Syntax_&_Semantics-blueviolet)](./docs/RTL_SYNTAX_AND_SEMANTICS.md)
152+
&nbsp;
153+
[![Benchmark Summary](https://img.shields.io/badge/Doc-Benchmark%20Summary-teal)](./docs/benchmark_summary.md)
174154

175155
### How to Build (Windows / Linux)
176156
```sh
@@ -238,4 +218,4 @@ If you’re interested in advancing practical runtime reflection in C++ and supp
238218

239219
##
240220

241-
***C++ joins the reflection party! – why should Java have all the fun?***
221+
***C++ joins the reflection party! – why should Java have all the fun?***
Lines changed: 119 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,119 @@
1+
### 🪶 Registration Model and Metadata Lifetime
2+
3+
RTL does not use macro-based reflection, implicit static initialization at program startup, or centralized global registries.
4+
All registration is performed lazily and explicitly by user code, and registered metadata persists for the lifetime of the process.
5+
6+
For every registered type, method, or function, RTL creates a **dedicated dispatch object** that encapsulates:
7+
8+
* The callable function-pointer
9+
* The associated reflection metadata
10+
11+
These dispatch objects are defined in:
12+
13+
```
14+
rtl/dispatch/function_ptr.h
15+
rtl/dispatch/method_ptr.h
16+
```
17+
18+
Each dispatch object is:
19+
20+
* Created **exactly once per unique registration**
21+
* Stored in a process-lifetime (`static std::list`)
22+
* Reused across all `rtl::CxxMirror` instances
23+
24+
Repeated registration attempts always resolve to the same existing object. This ensures deterministic behavior –
25+
metadata identity is stable regardless of initialization order or how many translation units register the same type.
26+
27+
`rtl::CxxMirror` does not own or duplicate this metadata. It encapsulates references to it as a lightweight, ordinary object. Mirrors may be created with different type sets, and the same registration statements can be materialized multiple times.
28+
29+
For example:
30+
31+
```cpp
32+
rtl::type().member<Person>().method("getName").build(&Person::getName);
33+
```
34+
35+
will always yield the same metadata and dispatch object for `Person::getName`.
36+
The lifetime of registered metadata is independent of any individual `rtl::CxxMirror` instance and persists for the duration of the program.
37+
38+
---
39+
40+
### ⚡ Reflective Call Materialization and Performance
41+
42+
RTL employs a two-phase invocation model. Metadata queries return lightweight descriptors such as `rtl::Function` and `rtl::Method`, which must be explicitly **materialized** into callable entity by specifying the expected signature.
43+
44+
This deferred materialization acts as a compile-time contract: the user declares the argument and return types they intend to use, and RTL validates and prepares an optimized invocation path accordingly.
45+
46+
Performance depends on how much type information is provided:
47+
48+
* Fully specified signatures compile to **direct function-pointer calls**, faster than `std::function`.
49+
* Type-erased signatures invoke through a lightweight dispatch layer whose performance **is comparable** to `std::function` under real workloads.
50+
51+
By requiring explicit materialization, RTL produces lightweight, reusable callable entity that behave like ordinary value-type objects and can be stored in standard containers.
52+
53+
At call time, RTL performs no dynamic allocations, no RTTI lookups, no `void*` stuff, and no hidden metadata traversals. The runtime cost is explicit, minimal, and comparable to what a developer would implement manually for equivalent type safety and flexibility.
54+
55+
---
56+
57+
### 🛡 Exception-Free Guarantee
58+
59+
RTL is designed to be exception-free. In practice, any exceptions that occur are almost always introduced by user code and merely propagate through RTL.
60+
61+
For all predictable failure cases, RTL reports errors through explicit error codes(`rtl::error`) rather than throwing exceptions. Critical assumptions are validated before execution, ensuring that failure conditions are detected early and handled in a controlled manner.
62+
63+
This design promotes predictable behavior and avoids unexpected control flow during reflective operations.
64+
65+
> *Exception-handling behavior has not yet been exhaustively stress-tested across all edge cases, but the system is architected to avoid exception-based control flow by design.*
66+
67+
---
68+
69+
### 🎁 Smart Pointer Handling
70+
71+
RTL supports working with objects managed by `std::unique_ptr` and `std::shared_ptr` in a manner consistent with standard C++ usage.
72+
73+
Heap-allocated objects created through RTL are internally managed using smart pointers to ensure safe ownership and lifetime control. These details are not imposed on the user: reflected objects can be accessed either through their smart-pointer representation or through views of the underlying type `T`.
74+
75+
When cloning or transferring reflected objects, RTL preserves the ownership semantics of the original type:
76+
77+
* Objects intended to be shared can be accessed through shared ownership.
78+
* Uniquely owned objects retain their uniqueness.
79+
* Copyable values can be duplicated to produce independent instances.
80+
81+
This design allows developers to work with reflected objects using the same ownership and lifetime expectations they would apply in ordinary C++ code, without requiring special handling for reflection-specific wrappers.
82+
83+
Reflection semantics are aligned with standard C++ object semantics, ensuring consistent behavior regardless of whether an object is accessed directly or through a smart pointer.
84+
85+
---
86+
87+
### 💡 Tooling-Friendly Architecture
88+
89+
**RTL** separates the *generation* of reflection metadata from its *consumption*. This makes it ideal not just for runtime introspection, but also for external tools like:
90+
91+
* Code generators
92+
* Serialization pipelines
93+
* Game or UI editors
94+
* Live scripting or plugin systems
95+
96+
#### ✨ The Mirror & The Reflection
97+
98+
> *A client system hands off a `rtl::CxxMirror` to RTL — and RTL sees its reflection.*
99+
100+
The mirror is a **single object**, typically returned from a function like:
101+
102+
```cpp
103+
extern const rtl::CxxMirror& MyReflection();
104+
```
105+
106+
This function is:
107+
108+
* **Externally linkable** — can live in any translation unit or even dynamic module
109+
* **Lazy** — doesn’t require metadata unless explicitly accessed
110+
* **Pure** — returns a complete, immutable view of reflection metadata
111+
112+
#### 📎 Why This Matters for Tooling
113+
114+
This design turns RTL into a **pluggable, runtime-agnostic consumer** of metadata. You can:
115+
116+
* Reflect types from external libraries
117+
* Link in auto-generated metadata modules
118+
* Expose your reflection system to scripts or tools without tight coupling
119+
* Swap different `rtl::CxxMirror` sources depending on build mode (dev/editor/runtime)

0 commit comments

Comments
 (0)