Arya Hosseini

Simulating Reflection in C++: A Practical ECS Serialization System

By Arya Hosseini | Thursday, April 2, 2026

It is no surprise that C++ has been the dominant programming language in game development for decades. The games industry has embraced it for good reasons: it offers control over hardware while still providing powerful abstraction tools. Nowadays pretty much all games have an element of C++ in them, even though some popular engines offer alternatives with scripting languages such as C# or Lua, or visual programming systems such as blueprints, the core of most game engines is written in C++.

As a junior programmer, C++26 is the first new standard released since I started learning the language. It introduces several major features which make me excited and terrified at the same time since there is still much to learn from the previous version. Over the past days I've come across multiple posts of people discussing the new compile time reflection coming in C++26. This has historically been a missing piece in C++, and its absence has forced developers to build their own workarounds using templates, metaprogramming, macros, and even dedicated libraries focusing solely on this issue.

Recently, while working on my own ECS based game engine, I ran into this exact limitation. I needed a way to serialize component data into JSON, something that, in other languages, often relies directly on reflection. In C++, however, there is no built in way to iterate over the members of a type or retrieve their names. This blog post is a technical breakdown of the system I built to work around that limitation and integrating it into an ECS. It explores how to simulate reflection in modern C++, the design decisions involved, and how such a system can be used to power features like ECS serialization.

The problem

Let's start with the problem that led me here.

In a typical data oriented game engine, different aspects of an entity are stored in small structs called components. For example, a Transform component might store the position, scale, and rotation of an entity:

To produce the JSON output, we need to associate each field with its name and value. In other words, we need a way to:

The challenge is that C++ provides no built in way to iterate over the members of a type or retrieve their names.

What is reflection and how can it help?

Reflection is the ability of a program to inspect its own types and their members.

In practice, this means being able to:

If C++ supported reflection natively, we could imagine writing something like:

This would allow us to automatically serialize any component without manually specifying its members. However, C++ does not provide this capability (at least prior to C++26), which means we need to take a detour.

Initial workaround

My initial approach to this limitation was to rely on the adl_serializer mechanism provided by nlohmann/json. This uses ADL (Argument Dependent Lookup) to find to_json and from_json functions for user defined types. In practice, this meant writing serialization logic manually for each component.

For example, with our Transform component we could write:

While this approach is straightforward, it quickly becomes difficult to maintain as the number of components grows. Each new component requires its own pair of functions, and even small mistakes, such as typos, missing fields, or incorrect nesting, can lead to subtle and annoying bugs.

More importantly, serialization was not the only system that needed this information. Other engine features, such as an entity inspector, also required access to component fields in a generic way. With this approach, every system would need to duplicate similar logic per type, which does not scale well.

At this point, it became clear to me that I needed a more general solution, something closer to reflection.

Simulating reflection with templates, tuples and member pointers

While working on this problem, I recalled a pattern I had seen before when reading about zero cost abstractions in ECS design. The idea was to describe a type's structure manually using templates and std::tuple, then traverse it generically.

The core of the system looks like this:

Each field is represented as a pair of:

This effectively creates a lightweight, compile time description of the component.

This is possible because C++ provides access to member pointers. These pointers store the memory offset of a member within a type, independent of any specific instance.

To traverse the reflected pairs, a generic visitor can be used:

Visitors: Serialization and Deserialization

The key point in visitors is to utilize function overloading. As seen above, the tuple of pairs is unpacked into function arguments, and those function arguments are used to pick the correct overload in the visitor at compile time, avoiding any runtime overhead.

ECS integration

The following is pseudocode for how this reflection system could be integrated into an ECS design.

As a side note, when I ended up integrating this system into my ECS, I came across an issue when deserializing components. Depending on how fine grained the component design is, you might come across a situation where a component has a member whose value is derived from another member. Take a look at the Collider component:

Components like Collider will need special attention when deserializing their JSON since we won't be storing halfSize, it gets reconstructed from size. Instead, we add an additional method to the component, giving us:

OnAfterDeserialize() can then be called from within DeserializeComponent():

This allows handling the exception without introducing extra layers of abstraction, while still maintaining compile time inlining.

If you are curious about the implementation in my own project, here is a breakdown of serializing an Entity in a type erased ECS design.

Result

This approach replaces per type serialization functions with a data driven, generic system. Instead of writing serialization logic for every component, we define the structure once and reuse it across multiple systems. In other words, while C++ does not provide reflection natively, we can simulate it by describing type structure and building generic algorithms on top of that description. While this isn't true reflection, it still provides a way to overcome this restriction without the use of macros, which I often find intimidating and hard to debug as a much less experienced programmer.

There is always room for improvements when it comes to making software. From my time programming, I've come to realize that the ideal will never be achieved since what's ideal is never fully known. That being said, I should mention that I have a nice improvement in mind that I have yet to implement. The core idea is using a struct instead of a std::pair to contain the structural metadata, which would allow visitors to receive richer per field information. Some pseudocode might look like:

This could allow more possibilities for customization and richer visitor overloads, but at the time of writing I haven't written a concrete implementation.

Ending Thoughts

With C++26 on the horizon, we can expect a standardized reflection capability. In this last part I wanted to showcase how reflection is expected to look like in C++26 and how much it would simplify the design presented in this blog. Based on the published proposals at the time of writing, here is what we can expect:

Thank you for coming here and reading this blog. I hope you found it informative and relevant. I'm still navigating the maze of C++, but I hope the ideas presented in this blog were interesting and valid. If you would like to have a discussion with me or offer your insight, my contact can be found at the top right corner of the page. Please don't hesitate to reach out.

References