Ch24.4: Support and Adoption
1. What Actually Implements It
Reflection is in C++26, but "in the standard" is not the same as "in your compiler". The support table at the time of writing:
| Implementation | Reflection | Notes |
|---|---|---|
| GCC 16 | Yes, partial library support |
The first mainstream compiler with a C++26 reflection implementation. The language
features and most of <meta> are present; some library facilities and
corner cases are still being completed. Use -std=c++26 and check the
release notes for your exact build.
|
Bloomberg clang-p2996 |
Yes, reference implementation | A fork of Clang maintained alongside the proposal. It is the implementation used to demonstrate many P2996 examples. It is not upstream Clang; do not assume the same flags or the same completeness. |
| EDG | Yes, implementation available | EDG's front end implements the proposal and is available on Compiler Explorer. It is a front end, not a complete toolchain you can drop into a normal build. |
| MSVC | No public implementation | At the time of writing there is no public reflection implementation in MSVC. Code that uses reflection does not build there. |
| Upstream Clang | No complete implementation | Reflection has not landed as a complete feature in upstream Clang. Check the current compiler support table before relying on it. |
The practical consequence: reflection is ready for experiments, internal tools, and projects that can pin a compiler. It is not yet a feature you can put in a library that must build on every compiler your users have.
2. Feature-Test Macros
The feature-test macros tell you whether the language and the library are present:
-
__cpp_impl_reflection— the language feature. The C++26 value is202506L. -
__cpp_lib_reflection— the<meta>library. The C++26 value is202506L. -
__cpp_lib_define_static—define_static_string,define_static_array,define_static_object. The C++26 value is202506L.
#if defined(__has_include)
# if __has_include(<meta>)
# include <meta>
# endif
#endif
#if defined(__cpp_impl_reflection) && __cpp_impl_reflection >= 202506L && \
defined(__cpp_lib_reflection) && __cpp_lib_reflection >= 202506L
# define HAS_CXX26_REFLECTION 1
#else
# define HAS_CXX26_REFLECTION 0
#endif
Do not guess from the compiler name. A vendor may ship a partial implementation, and a fork may define the macro while missing a function you need. Guard the language feature and the library feature separately, and guard the specific facility if it has its own macro.
A feature-test macro is a promise about the feature, not a promise about every corner case. Test the code you actually use. A small compile-only test in CI is worth more than a matrix of version numbers.
3. A Portability Boundary
The clean way to adopt reflection is to isolate it behind one header and one interface:
reflection/
detect.h // feature macros, HAS_CXX26_REFLECTION
enum_names.h // enum_to_string, string_to_enum
json.h // generic serializer
json_fallback.h // hand-written or macro-based implementation
api.h // includes one of the two, same public names
The rest of the program includes api.h and never sees ^^ or
[: :]. On a compiler with reflection, the implementation is generated. On a
compiler without it, the fallback is the old hand-written or macro-based code. The public API
is identical, so the rest of the program does not care.
This boundary also contains the compile-time cost. A header that reflects every type in the project slows every translation unit that includes it. Keep the reflection-heavy code in a small number of translation units, and expose ordinary functions or types to the rest of the program.
Do not put reflection in a public ABI. A reflection value is a compile-time handle; it is not a stable binary interface. Generated aggregates are fine in an ABI if their layout is stable, but the reflection machinery that produced them is an implementation detail.
4. What to Use Today
If you need the functionality now and cannot pin a reflection-capable compiler, the existing tools still work:
| Tool | What it does | Limit |
|---|---|---|
| Boost.PFR | Aggregate field access and tuple-like iteration without macros | Aggregates only; no names, no enum reflection, no type generation |
| magic_enum | Enum names and parsing by scanning the compiler's own diagnostics | Compiler-specific tricks, limits on enum size and flags |
| Macro tables (X-macros) | One list expands into declarations and visitors | Textual, no type information, still a second declaration |
| External generators | Parse headers or schemas and emit C++ | Build step, another parser, stale output when the input changes |
| Compiler plugins | Full access to the AST | Compiler-specific, fragile across versions, hard to distribute |
These are not wasted skills. A reflection-based implementation still needs the same policy decisions, and the fallback path is often one of these tools. The difference is who writes the member list: you or the compiler.
5. An Adoption Plan
Do not start by rewriting the serializer. Start with the smallest place where duplication hurts and a failure is easy to detect:
-
Enum names. Replace a hand-written
to_stringswitch. The generated function is easy to compare with the old one, and a missing enumerator is a compile-time or test-time failure. - Debug printing. Generate a field printer for internal types. It is not on the hot path, and the output is easy to inspect.
- Serialization for a bounded format. JSON or a configuration file. Keep the policy layer explicit: escaping, optional fields, versions, unknown keys.
- Bindings or a schema. Generate the repetitive wrapper from the declaration only after the first three steps have proven the toolchain stable in your build.
- Public API. Only when every compiler you support has a complete implementation, or when the reflection code is hidden behind a non-reflective ABI.
At every step, keep a fallback. The cost of a fallback is small compared with the cost of discovering that one supported compiler cannot build the release.
6. What "Partial" Means in Practice
A partial implementation usually fails in one of these places:
-
A library function is missing even though the language operator works. For example,
define_static_arraymay be absent while^^andnonstatic_data_members_ofare present. - A corner of the grammar is not implemented: a namespace splice, a template splice, a dependent splice in a template.
- A diagnostic is poor. The code is rejected, but the error points at the wrong line or prints an internal type name.
- A constexpr limit is lower than you expect. A large recursive serializer hits the evaluation depth or the step limit.
The mitigation is the same for all four: keep the reflection code small, test it in isolation, and have a fallback for the facility that is missing. Do not build a reflection-heavy framework on a partial implementation and then discover the gap in a release build.
7. The Direction of the Language
Reflection for C++26 is deliberately a core, not the final form. The proposal itself says that more powerful code injection and generation are desirable directions. The pieces that shipped are the ones the committee could specify and implement:
- reflection values and the
std::metaquery library; - splicers for expressions, types, scopes and templates;
- expansion statements;
- consteval blocks and injected declarations;
- simple aggregate generation.
That is already enough for serialization, enum names, bindings and layout checks. The absence
of general code injection is why define_aggregate only produces simple
aggregates. Future standards may widen the generation side; the mental model — reflect,
query, splice — will not change.
8. Further Reading
- P2996R13: Reflection for C++26 — the proposal, with the examples that became this chapter's patterns.
-
cppreference: Reflection library
— the function-by-function reference for
<meta>. - cppreference: C++26 compiler support — the current implementation matrix. Check it before you rely on a feature.
- Bloomberg clang-p2996 — the reference Clang implementation.
- Reflection in C++26 (P2996) — a tutorial walkthrough with additional examples.
-
cppreference: Splice specifiers
— the grammar of
[: ... :].
Key Takeaways
- Reflection is C++26, but compiler support is uneven. GCC 16 has an implementation; Bloomberg's Clang fork and EDG have implementations; MSVC does not.
-
Guard with
__cpp_impl_reflection,__cpp_lib_reflectionand__cpp_lib_define_static, not with compiler names. - Put reflection behind one header and one public interface. Keep a fallback for compilers that cannot build it.
- Start with enum names and debug printers. Move to serialization and bindings only after the toolchain is stable in your build.
- Reflection removes boilerplate, not design. Policy, errors, escaping and versions are still your job.
That is the chapter. The language model is three operators and a library; the engineering work is choosing where to put the boundary and how to fall back.