The Real Cost of C++26 Reflection: A Benchmark of Enum-to-String Conversion
Enum-to-string conversion is often described as the "hello world" of reflection. While it seems trivial, it is a fundamental requirement for logging, serialization, and debugging in professional C++ projects. With the release of GCC 16 and the introduction of C++26 reflection, developers now have a native way to handle this without the boilerplate of the past.
However, the primary concern for any C++ developer adopting new language features is the impact on build times. A recent benchmark by Vittorio Romeo explores whether the native reflection in C++26 is a viable replacement for "the old ways," specifically X-macros and template-based parsing tricks.
Three Approaches to Enum-to-String
To measure the cost, three distinct implementations were benchmarked using GCC 16.1.1 on a high-performance setup (i9-13900K).
1. C++26 Reflection
Using the new <meta> header, this approach is idiomatic and requires no macros or boilerplate. It uses std::meta::enumerators_of to iterate through the enum values at compile time.
2. Enchantum (C++17)
enchantum is a header-only library that uses __PRETTY_FUNCTION__ parsing tricks to derive enum names. It provides a clean call site but relies on scanning a configured range of values to find matches.
3. X-Macros (The Preprocessor Way)
This C-style approach defines a list of enumerators in a macro. A single macro expansion then generates both the enum class definition and a to_string function containing a switch statement. This was tested in two variants: one returning std::string_view and one returning a raw const char* to eliminate standard library dependencies.
The Benchmark Results
The benchmarks varied the number of enumerators (N) from 4 to 1024 to observe how compile time scales.
Total Per-Translation Unit (TU) Compile Time
| N | X-macro (const char*) |
X-macro (string_view) |
enchantum |
Reflection |
|---|---|---|---|---|
| Baseline | 25.8 ms | 25.7 ms | 25.8 ms | 25.7 ms |
| Header only | 25.7 ms | 136.0 ms | 147.1 ms | 180.8 ms |
| 4 | 26.6 ms | 137.6 ms | 170.6 ms | 186.7 ms |
| 1024 | 54.7 ms | 204.5 ms | 272.0 ms | 255.0 ms |
Key Findings on Scaling
- The X-macro (
const char*) is the undisputed speed king. Because it avoids all standard library headers, it remains nearly at the baseline for small enums. - Reflection is asymptotically fast. The actual reflection algorithm costs approximately 0.07 ms per enumerator, which is nearly identical to the hand-rolled
switchin the X-macro version (~0.06 ms). enchantumscales differently. Its cost is tied to the configured scan range rather than the number of enumerators, meaning a small enum costs nearly as much as a medium one.
The "Header Tax"
The most significant insight from the data is that the perceived cost of reflection is not the reflection itself, but the inclusion of the <meta> header.
The cost of C++26 reflection is not the reflection. It’s
<meta>.
Including <meta> adds a baseline overhead of approximately 155 ms per translation unit. In a large project with 500 TUs, this "header tax" can add nearly 80 seconds to a clean build. This highlights a recurring theme in C++: the most expensive part of compilation is often the standard library headers rather than the language features themselves.
Optimizing the Build: PCH vs. Modules
To mitigate the header tax, the benchmark tested Precompiled Headers (PCH) and C++20 Modules.
- PCH (The Winner): Precompiling
<meta>resulted in a 2.3x speedup, making reflection faster than bothenchantumand thestring_viewX-macro variant. - Modules (The Surprise): In GCC 16, using modules actually slowed down compilation by approximately 2.2x compared to plain
#include. The author notes this is likely a transient implementation detail of the current GCC 16 release, where thestdmodule is a wrapper around a large header unit.
Practical Recommendations for Developers
For those integrating C++26 reflection into a production codebase, the following strategies are recommended:
- Prioritize PCH over Modules: Until module implementations mature, PCH is the most effective way to eliminate the
<meta>overhead. - Minimize Header Exposure: Do not include the enum-to-string header in other headers. Keep it in
.cppfiles to prevent the header tax from multiplying across the include graph. - Stick to X-Macros for Ultra-Fast Builds: If your project requires sub-10-second clean builds, the overhead of
<meta>is a non-starter. The rawconst char*X-macro approach remains the most efficient. - Caution for Library Authors: Be wary of exposing reflection in public headers. Forcing every consumer of your library to include
<meta>can significantly degrade their build performance.
Community Perspectives
The discussion around these results highlights a divide in the C++ community. Some argue that compile-time cost is secondary to runtime performance and developer ergonomics. Others, coming from C or using custom code generators, argue that external build-step scripts (e.g., using Python or libclang) are more debuggable and offer infinite capabilities without bloating the compiler's workload.
Ultimately, C++26 reflection provides a massive ergonomic win, replacing ugly macro boilerplate with a type-safe, native mechanism. However, the "bill" for this convenience is paid in milliseconds per translation unit—a cost that can be managed, but not ignored.