C++26 adopts compile-time reflection at Sofia
Reflection turns program structure into compile-time values that ordinary C++ code can query. Splicing connects those values back to types and expressions without providing arbitrary token injection.
A library that formats every member of a structure needs to know which members exist. Without a language facility for obtaining that information, the programmer has to supply metadata or depend on external code generation and implementation-specific techniques. The reflection work adopted at Sofia gives C++ code a standard way to query selected program structure during constant evaluation.
Herb Sutter's June 21 meeting report records adoption of the reflection papers and completion of the C++26 feature freeze. The central P2996R13 paper, dated June 20, defines reflection values and the mechanisms that use them. C++26 remains a working draft moving toward its committee-draft comment ballot.
The adoption is a set of coordinated features. Alongside the central reflection paper, the meeting adopted annotations and expansion statements, among other supporting work. That matters because querying structure and generating useful typed operations from it require more than one isolated operator.
I have mixed feelings about C++'s accumulation of language mechanisms, but templates' ability to remove runtime overhead remains persuasive. Reflection interests me where it replaces handwritten metadata and indirect template machinery with a direct query about the program. The cost still belongs to compilation and diagnostics, so understanding the mechanism matters more than calling it automatic code generation.
Program structure becomes a value
P2996 represents reflected entities with the opaque type std::meta::info. The prefix operator ^^ obtains a reflection value for a supported operand. Compile-time functions can then inspect that value and derive other reflection values from it.
This arrangement differs from inventing a separate template type for every reflected entity. A reflection is a value that can be stored in a compile-time collection and passed to a function. The functions in std::meta use constant evaluation to answer questions about what that value represents.
The paper explains why it chooses one opaque type. A family of reflection types would encode the language's current categories into the standard library's type hierarchy. Those categories can evolve. A single value type also permits a collection containing reflections of different kinds of entities, such as a mixed list of type and non-type template arguments.
The opacity is deliberate. Users depend on the documented query operations, not the compiler's internal representation of a declaration. The standard interface therefore need not expose one implementation's syntax-tree layout or allocate a public class for every internal compiler node.
Splicing makes the information usable in source
A reflection value is information about an entity. A splicer connects that information back to a grammatical element of C++ source. P2996 uses [: ... :] for this purpose, with the appropriate surrounding syntax determining whether the reflection is used as a type, member, or another supported construct.
The paper's simplest example reflects a type and then uses that reflection to declare an object. This illustrates adopted draft syntax and requires an implementation that supports it.
constexpr auto reflected_type = ^^int;
typename [:reflected_type:] value = 42;
The declared object has type int. The example separates obtaining the reflection value from using it in a type position. That separation allows a compile-time computation to choose among reflection values before the splicer uses its result.
The mechanism is not equivalent to emitting an arbitrary string and asking the compiler to parse it as source. The permitted splicing contexts and the reflected entity's kind still matter. P2996 explicitly treats more general code injection as future work, while providing useful operations within the adopted feature's narrower scope.
There is nevertheless a generation capability in the adopted paper. define_aggregate can define selected aggregate structure from reflection descriptions. That is more specific than unrestricted token injection, and more powerful than a system that can only print type names. The distinction is necessary to describe what the initial facility actually enables.
A member query can drive a reusable operation
The paper includes queries such as nonstatic_data_members_of and enumerators_of. These provide sequences of reflection values for code to examine. A formatter or serialization helper can use the sequence instead of maintaining a second manual list of names and members.
For enumerations, P2996 demonstrates comparing a runtime value against enumerators obtained during reflection. A spliced enumerator supplies its typed value, and an identifier query supplies its name. The example shows how compile-time structural knowledge can generate ordinary runtime comparisons without requiring a runtime reflective object model.
The benefit is not that every possible serialization policy can now be inferred. A structure's members do not establish which fields belong in a network protocol or which representations remain compatible over time. Reflection supplies structural information. The library or application still chooses the semantics of the operation it builds from that information.
The paper's examples also include class layout and conversion of a structure to a tuple. Those examples illustrate different uses of the same value-and-query model. They do not imply that a compiler can infer an application's ownership policy merely by listing its data members.
Expansion statements complement reflection
The Sofia report names P1306 expansion statements among the adopted work. They make compile-time iteration over reflection data useful when each element must contribute a separately formed operation. The report describes the syntax through template for, which appears in the central paper's examples.
A normal runtime loop iterates values while its body has a fixed compiled form. Reflection-driven work can require each iteration to refer to a different member or enumerator through a compile-time reflection. Expansion statements support that pattern without requiring the programmer to construct a separate pack-expansion helper for every traversal.
P2996 documents a workaround for experimental implementations that do not yet support expansion statements. That is an implementation-status detail, not evidence that expansion statements were omitted from the Sofia adoption. The paper and meeting report describe different aspects of the same work, one showing prototype constraints and the other recording draft-standard decisions.
Access and names still need defined rules
Reflecting on members introduces questions about access control and context. P2996 includes an access_context facility and shows its use in member queries. The public reflection interface therefore describes more than the physical existence of a member. It also has to account for where a query occurs and which declarations it can obtain.
Names need similar care. An identifier intended for programmatic use and a display string intended for diagnostics have different purposes. The paper exposes distinct queries rather than promising that every human-readable rendering is a stable source identifier. A library should use the query whose semantics match what it does with the result.
These details matter when reflection leaves a toy example. A formatting helper may only need a useful name for display, while a binding generator needs a deliberate mapping between source entities and an external interface. The same reflection value can support both operations without making their naming requirements identical.
Structural automation leaves policy choices intact
A useful distinction in the paper's examples is between discovering members and selecting the operation applied to them. Enumerating a structure gives a library a sequence of potential inputs. It does not require every one of those inputs to appear in an externally visible result. A library can apply its own rules after obtaining the reflections.
This is why reflection can reduce duplicated declarations without becoming a complete interface specification. A debug display may intentionally include every accessible member. A persistent representation may need a smaller, stable selection whose meaning survives a source refactor. Both can use the same query machinery, but they have different contracts with their consumers.
The compiler's knowledge is valuable because it comes from the program being compiled. The remaining choice is whether a structural change should automatically alter the generated operation. Reflection makes that choice easier to implement, while leaving the author responsible for deciding which changes should propagate to external users.
Feature freeze leaves implementation and review ahead
The June milestone settles the intended feature set for the next draft stage. It does not mean every compiler already implements the reflection facility. P2996 describes experimental EDG and Clang implementations and explicitly notes their incomplete coverage. A build using one of those implementations is different from a portable deployment across every ordinary compiler release.
I expect the most useful reflection libraries to make their policies visible. Removing a handwritten member list can reduce duplication, but a reader still needs to know which members participate and how their values are interpreted. The adopted mechanism supplies compile-time access to program structure. The application-specific contract remains in the code that decides what to generate from it.