You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Copy file name to clipboardExpand all lines: README.md
+40-60Lines changed: 40 additions & 60 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -14,11 +14,11 @@
14
14
15
15
RTL provides type-safe run-time reflection for C++, combining compile-time guarantees with run-time flexibility.
16
16
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:
18
18
```c++
19
19
std::string complexToStr(float real, float img);
20
20
```
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:
@@ -32,7 +32,7 @@ if(cToStr) { // Function materialized?
32
32
33
33
⚡ **Performance**
34
34
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.
36
36
37
37
## Design Highlights
38
38
@@ -42,32 +42,25 @@ RTL’s reflective calls are comparable to `std::function` for fully type-erased
42
42
43
43
****Zero-Overhead by Design*** – Metadata can be registered and resolved lazily. Reflection introduces no runtime cost beyond the features explicitly exercised by the user.
44
44
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))*
46
46
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.
****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.
52
48
53
49
## A Quick Preview: Reflection That Looks and Feels Like C++
54
50
55
-
First, create an instance of `rtl::CxxMirror` –
51
+
First, create an instance of `rtl::CxxMirror`:
56
52
```c++
57
53
auto cxx_mirror = rtl::CxxMirror({ /* ...register all types here... */ });
58
54
```
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:
61
56
```c++
62
57
// 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.
`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
-
87
78
### RTL in action:
88
79
89
-
**[Explore the demo code](https://github.com/ReflectCxx/RTL-Demo)**
90
-
91
-
Lookup the `Person` class by its registered name –
`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.
97
86
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.
99
88
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)`:
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:
111
98
```c++
112
99
auto [err, robj] = personCtor(rtl::alloc::Stack, "John", 42);
113
100
if (err != rtl::error::None) { std::cerr << rtl::to_string(err); } // Construction failed.
114
101
```
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`.
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.
135
122
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:
auto [err, ret] = getName(robj)(); // Invoke and receive rtl::RObject as return, wrapping std::string underneath.
150
137
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.
153
139
}
154
140
```
141
+
**[Explore the demo code here](https://github.com/ReflectCxx/RTL-Demo)**
142
+
155
143
### How RTL Fits Together
156
144
157
145
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`.
158
146
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
162
148
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).
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.
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
0 commit comments