short

The hidden cost of <meta> -- and the three-line fix

· english · audience: working-cpp

Every post in the wro.cpp reflection series starts with #include <experimental/meta> (or <meta> on GCC 16.1). None of them mention what that include costs. Vittorio Romeo measured it — twice — and the answer is worth knowing before you ship reflection to production.

The headline number

Romeo’s March 2026 article (GCC 16.0.1) and May 2026 follow-up (GCC 16.1.1) converge on the same finding:

The <meta> header costs ~181ms to parse. The reflection algorithm itself costs ~0.07ms per enumerator.

The header is roughly 2500x more expensive than the logic. Enabling -freflection without including any header adds zero overhead (33.2ms vs 33.9ms — within noise). The tax is not reflection; the tax is the standard library machinery that <meta> pulls in: <ranges>, <vector>, <string_view>, <optional>, and their transitive dependencies.

The benchmark data

Romeo’s enum-to-string comparison (May 2026, GCC 16.1.1, i9-13900K) measured four approaches across enum sizes from 4 to 1024 enumerators:

ApproachHeader cost (ms)Algorithm cost at N=256 (ms)Per-enumerator (ms)
X-macro (const char*)25.76.8~0.027
X-macro (string_view)136.017.0~0.06
enchantum (__PRETTY_FUNCTION__)147.137.0varies
C++26 reflection180.834.2~0.07

The reflection algorithm scales at 0.07ms per enumerator — the same order as hand-written X-macros using string_view. At N=1024, reflection (74.2ms algorithm cost) beats enchantum (124.9ms) and is within 8% of X-macros with string_view (68.5ms). The only thing faster is raw const char* X-macros — which nobody writes by choice.

The entire cost difference is the header.

PCH: the three-line fix

Romeo tested precompiled headers, and they cut the <meta> overhead by 2.3x:

ConfigurationHeader only (ms)With N=256 enum (ms)
Plain #include <meta>180.8215.0
With PCH73.897.5
With modules397.4423.2

The PCH stanza in CMake:

target_precompile_headers(my_target PRIVATE
  <meta>
  <ranges>
)

That is the three-line fix. Every project using reflection should add it. The <meta> header is parsed once, serialized to disk, and reused across every TU in the target. At 500 TUs with N=16 enumerators each, the difference is ~94 seconds (plain) vs ~40 seconds (PCH) — nearly a minute saved on a clean build.

Modules: surprisingly worse (for now)

The intuition is that import std; should help even more than PCH. Romeo’s data says otherwise — on GCC 16.1, modules are 2.2x slower than plain includes:

ConfigurationHeader only (ms)
Plain include180.8
PCH73.8
Modules397.4

This is a GCC 16.1 implementation artifact, not a fundamental limitation. Module compilation in GCC is young; the binary module interface (BMI) format is not yet optimized for the scale of <meta>. Expect this to improve in GCC 17 and future Clang releases. Today, PCH is the right answer.

The deeper fix: P3429

Jonathan Mueller’s P3429R1 “<meta> should minimize standard library dependencies” proposes the structural fix: make <meta> stop pulling in <ranges>, <vector>, and <optional>. Replace std::vector returns with a lightweight std::meta::info_array; return const char* instead of std::string_view from identifier_of(); eliminate the std::optional dependency in data_member_spec.

The paper has prototype implementation experience on Bloomberg’s clang-p2996. The changes are source-compatible for most code (a few examples need explicit std::ranges::to<std::vector>()). If adopted, it would bring <meta> closer to <type_traits> weight — tens of milliseconds, not hundreds.

P3429 has not been adopted yet. Watch the Brno meeting (June 8-13) for movement.

What this means for the reflection series

Every example in the wro.cpp series compiles in isolation — a single TU, a single #include. For hands-on learning, the 181ms is invisible. For production code that reflects types across hundreds of TUs, the 181ms compounds. The fix is mechanical:

  1. Today: add <meta> (and <ranges> if you use it) to your PCH. Three lines of CMake.
  2. Watch: GCC module performance will improve. When it does, import std; replaces the PCH.
  3. Long-term: P3429 (or something like it) makes the PCH unnecessary.

The reflection itself — the ^^T, the nonstatic_data_members_of, the identifier_of — is effectively free. The cost is the standard library. That is a solvable problem.


Data sources: Vittorio Romeo’s compile-time article (March 6, 2026, GCC 16.0.1) and enum-to-string comparison (May 12, 2026, GCC 16.1.1). Hardware: i9-13900K, 32GB DDR5-6400, Fedora 44.

See also: Post 5: Goodbye magic_enum (the wro.cpp enum-to-string using the same API Romeo benchmarked), GCC 16.1 ships reflection (the compiler these benchmarks ran on).