Reflection Template Library (RTL) is a lightweight, modern C++20 runtime reflection library. It allows introspection and dynamic manipulation of user-defined types — enabling you to access, modify, and invoke objects at runtime without compile-time type knowledge.
RTL is a static library built entirely in modern C++, designed around type-safe tables of function pointers registered by the user. These are internally wrapped in lambdas, offering a clean and efficient runtime access mechanism.
-
Runtime Reflection for C++ – Introspect and manipulate objects dynamically, just like in Java or .NET, but in modern C++.
-
Single Source of Truth – All metadata is exposed through one immutable
rtl::CxxMirror
object, ensuring ABI stability and consistency across plugins, tools, and modules. -
Non-Intrusive & Macro-Free – Register reflection data externally with a clean builder pattern; no macros, no base classes, no global registries.
-
Const-By-Default Safety – Everything is immutable unless explicitly mutable, preventing unintended side-effects in reflective code.
-
Exception-Free Surface – All predictable failures return error codes; no hidden throws.
-
Deterministic Lifetimes – Automatic ownership tracking of
Heap
,Stack
, andSmart-Pointer
instances with zero hidden deep copies. -
Cross-Compiler Consistency – Built entirely on standard C++20, no reliance on compiler extensions.
-
Tooling-Friendly – Architecture designed to power serializers, debuggers, test frameworks, scripting, and editor integrations without compiler context.
-
Path to Higher-Level Abstractions – Lays the foundation for ORMs, plugin systems, game editors, and live scripting directly in C++.
Create an instance of CxxMirror
, passing all type information directly to its constructor — and you're done!
rtl::CxxMirror cxx_mirror({/* register all types here */});
The cxx_mirror
object acts as your gateway to query, introspect, and instantiate all registered types at runtime.
RTL’s API is designed to be small and intuitive. The syntax follows familiar C++ patterns, so working with reflection feels natural.
// Without reflection
Person p("John", 42);
p.setAge(43);
std::cout << p.getName();
// With reflection
auto classPerson = cxx_mirror.getRecord("Person"); // Get the class as 'rtl::Record'.
auto [err, robj] = classPerson->create<alloc::Stack>("John", 42); // Get the instance (robj) as 'rtl::RObject'.
auto setAge = classPerson->getMethod("setAge"); // Get the method as 'rtl::Method'.
setAge->bind(robj).call(43); // Bind the rtl::RObject with rtl::Method and make the call with arguments.
auto getName = classPerson->getMethod("getName");
auto [err2, ret] = getName->bind(robj).call(); // Get return value as rtl::RObject.
std::cout << ret.view<std::string>()->get(); // access return value as std::string.
The semantics don’t feel foreign: creating, binding, and calling are the same ideas you already use in C++ — just expressed through reflection.
-
✅ Function Reflection 🔧 – Register and invoke C-style functions, supporting all kinds of overloads.
-
✅ Class and Struct Reflection 🏗️ – Register and dynamically reflect their methods, constructors, and destructors.
-
✅ Complete Constructor Support 🏗️:
- Default construction.
- Copy/Move construction.
- Any overloaded constructor.
-
✅ Allocation Strategies & Ownership 📂:
- Choose between
Heap
orStack
allocation. - Automatic move semantics for ownership transfers.
- Scope-based destruction for
Heap
allocated instances.
- Choose between
-
✅ Member Function Invocation 🎯:
- Static methods.
- Const/Non-const methods.
- Any overloaded method, Const & RValue based as well.
-
✅ Perfect Forwarding 🚀 – Binds LValue/RValue to correct overload.
-
✅ Zero Overhead Forwarding ⚡ – No temporaries or copies during method forwarding.
-
✅ Namespace Support 🗂️ – Group and reflect under namespaces.
-
✅ Reflected Returns 🔍 – Access return values whose types are unknown at compile time. Validate against the expected type and use them as if the type was known all along.
-
✅ Smart Pointer Reflection 🔗 – Reflect
std::shared_ptr
andstd::unique_ptr
, transparently access the underlying type, and benefit from automatic lifetime management with full sharing and cloning semantics. -
✅ Conservative Conversions 🛡️ – Safely reinterpret reflected values without hidden costs. For example: treat an
int
as achar
, or astd::string
as astd::string_view
/const char*
— with no hidden copies and only safe, non-widening POD conversions. -
✅ Materialize New Types 🔄 – Convert a reflected type
A
into typeB
if they are implicitly convertible. Define custom conversions at registration to make them available automatically. (In Progress) -
🚧 STL Wrapper Support 📦 – Extended support for wrappers like
std::optional
andstd::reference_wrapper
. Return them, forward them as parameters, and handle them seamlessly. (In Progress) -
🚧 Relaxed Argument Matching ⚙️ – Flexible parameter matching for reflective calls, enabling intuitive conversions and overload resolution. (In Progress)
-
❌ Property Reflection: Planned.
-
❌ Enum Reflection: Planned.
-
❌ Composite Type Reflection: Planned.
-
❌ Inheritance Support: Planned.
Create a build directory in the project root folder:
mkdir build && cd build
Generate a build system using Unix Makefiles or Visual Studio in CMake (use a compiler with C++20):
cmake -G "<Generator>"
To build, use any IDE applicable to the generator, or build straight from CMake:
cmake --build .
Run the CxxRTLTestApplication binary generated in the ../bin
folder. (Tested with Visual Studio 2022, GNU 14 & Clang 19)
In this example, we'll reflect a simple Person class. Person.h
:
class Person {
int age;
std::string name;
public:
Person();
Person(const std::string, int);
void setAge(int);
void setName(const std::string, const std::string&);
int getAge() const;
std::string getName() const;
};
Manually register the class and its members when creating a CxxMirror
object.
#include "RTLibInterface.h" // Single header: provides all registration & access interfaces.
#include "Person.h" // User-defined types to be reflected.
using namespace rtl::access;
using namespace rtl::builder;
const CxxMirror& MyReflection()
{
static const CxxMirror cxxReflection({
// Register member functions
Reflect().record<Person>("Person").method("setAge").build(&Person::setAge),
Reflect().record<Person>("Person").method("getAge").build(&Person::getAge),
Reflect().record<Person>("Person").method("setName").build(&Person::setName),
Reflect().record<Person>("Person").method("getName").build(&Person::getName),
// Registering any method (including but not limited to constructors) will
// automatically reflect the copy-constructor & destructor (if accessible).
Reflect().record<Person>("Person").constructor().build(), // Default constructor
Reflect().record<Person>("Person").constructor<std::string, int>().build() // Parameterized constructor
});
return cxxReflection;
}
Registration syntax:
Reflect().nameSpace("..") // Optional: specify namespace if the type is enclosed in one.
.record<..>("..") // Register class/struct type (template parameter) and its name (string).
.method("..") // Register function by name.
.build(*); // Pass function pointer.
Reflect().nameSpace("..")
.record<..>("..")
.constructor<..>() // Register constructor with template parameters as signature.
.build(); // No function pointer needed for constructors.
In main.cpp
, use the Person
class without directly exposing its type:
#include "RTLibInterface.h" // Reflection access interface.
// True runtime reflection – no compile-time access to types.
// Works without even knowing what it's reflecting.
extern const rtl::CxxMirror& MyReflection();
using namespace rtl::access;
int main()
{
// Lazily-initialized reflection system (singleton-style, pay-only-when-you-use).
// Get 'class Person' — returns a 'Record' representing the reflected class.
std::optional<Record> classPerson = MyReflection().getClass("Person");
/* Create an instance of 'class Person' using the default constructor.
Choose between heap or stack allocation with 'alloc::Heap' or 'alloc::Stack'.
Returns: std::pair<error, RObject>. RObject is empty if:
* error != error::None (creation or reflection call failure).
* OR the reflected function is 'void'.
'RObject' wraps a type-erased object, which can be:
* An instance created via reflection (constructor).
* OR a value returned from any reflection-based call.
Internally:
* Uses std::unique_ptr for heap-allocated reflection-created instances.
* Return values are unmanaged.
* Copy/move behave as value-type copies:
- Heap: follows semantics of unique_ptr.
- Stack: creates distinct object copies.
*/ auto [err0, person0] = classPerson->create<alloc::Heap>();
// Ensure object was created successfully.
if (err0 != error::None)
return -1;
// Create instance via parameterized constructor.
auto [err1, person1] = classPerson->create<alloc::Stack>(std::string("John Doe"), int(42));
// Fetch a reflected method — returns optional 'Method'.
std::optional<Method> setAge = classPerson->getMethod("setAge");
if(!setAge)
return -1;
// Call method: returns [error code, return value].
auto [err2, ret2] = setAge->bind(person0).call(42);
// Alternative syntax (without bind, slightly slower).
auto [err3, ret3] = (*setAge)(person1)(42);
// Fetch and invoke another reflected method.
std::optional<Method> setName = classPerson->getMethod("setName");
const char* name = "Todd"; // will get converted to std::string due to strict-binding.
std::string surname = "Packer";
// use bind to specify strict-argument types explicitly. (enables Perfect-Forwarding.)
auto [err4, ret4] = setName->bind<string, const string&>(personObj).call(name, surname);
// Fetch method returning a value.
std::optional<Method> getName = classPerson->getMethod("getName");
// Call and retrieve return value.
auto [err5, retName] = getName->bind(personObj).call();
if (err5 == error::None && retName.canViewAs<std::string>())
{
const std::string& nameStr = retName.view<std::string>()->get();
std::cout << nameStr << std::endl;
}
return 0; // Heap/Stack instances cleaned up via scope-based lifetime.
}
- See
CxxRTLTypeRegistration/src/MyReflection.cpp
for more type registration examples. - See
CxxRTLTestApplication/src
for test cases.
Contributions welcome! Report bugs, request features, or submit PRs on GitHub.
GitHub issues or email at reflectcxx@outlook.com
.