Video summary
Practical Reflection With C++26 - Barry Revzin - CppCon 2025
Main summary
Key takeaways
Summary of technological concepts and features (C++26 Reflection + “SOA vector”)
Barry Revzin (CppCon 2025) frames C++26 reflection as a goal of making libraries as easy to use as “one-line changes”, rather than as dull examples like enum -> string. His primary teaching vehicle is implementing a Structure-of-Arrays (SoA) vector in standard C++26 using reflection-powered code generation.
Origin / motivation
- A colleague recommended Andrew Kelly’s talk on data-oriented design (Zigg), highlighting that a simple code change can switch from an array-of-structs to struct-of-arrays efficiently.
- Revzin wants to demonstrate that with C++26 reflection, libraries can become similarly ergonomic:
- users don’t manually write the boilerplate
- the library generates it
What the talk does (step-by-step build)
1. Storage layout
-
Starts with a “simple” struct type (with internal padding), e.g.:
cpp struct square { char x; long y; ... }; -
Compares AoS vs SoA:
- AoS: a contiguous array of
square - SoA:
- separate contiguous buffers per member (e.g., one for
x, one fory) - maintains shared
sizeandcapacityonce, rather than per member
- separate contiguous buffers per member (e.g., one for
- AoS: a contiguous array of
2. Reflection-driven “define aggregate” technique
Uses C++26 reflection to complete an incomplete type by populating it with a specific set of non-static data members.
Key pattern:
- declare an incomplete type
- in a
constdeclaration context:- loop over reflected members
- call
std::define_aggregate(via reflection) to complete the type
3. Access control via “access contexts”
Reflection can cooperate with privacy using an access context:
unchecked: access all membersunprivileged: public-only- “current”: only what’s accessible in the current scope (e.g., friends)
For the tutorial, Revzin assumes a global unchecked-like context to keep focus on the core mechanics.
4. Reserved member-name conflict problem
If the library-generated SoA wrapper includes member names like size_ / capacity_, it breaks for user types that already define those names.
Solution:
- keep
size/capacityoutside the reflected/generated member set - generate only per-original-member pointer fields inside
5. Constraints on the element type T
SoA vector support is not universal. Requirements include:
Tmust be an aggregate (needed for reconstruction / decomposition)Tmust have no base classes (simplifies reconstruction via list-initialization)- members must be independently destructible
- supports destruction of member-storage without cross-dependencies
6. Custom trait predicates implemented as functions (not templates)
Reflection changes how to express type properties.
- Instead of variable templates like
is_aggregate_v, use functions such asis_aggregate_type(...) - When a trait depends on a template instantiation, it uses reflection primitives:
substitute+extractsubstitute: perform template argument substitution on reflected templatesextract: retrieve a value (e.g., a boolean) from a reflected entity
Container operations implemented with reflection
7. push_back
- Checks capacity; grows storage if needed.
- Uses placement construction per member storage:
- construct
xinto thexarray slot - construct
yinto theyarray slot - etc.
- construct
- Exception safety is postponed (explicitly not delved into).
8. grow / reallocation strategy
- Grows each member buffer (each pointer) independently.
- Relocation/move behavior is described using C++26 concepts (mentions relocate / move-destroy).
9. Different compile-time iteration strategies
Revzin presents several ways to iterate over reflected members and corresponding storages:
- Option 1: structured bindings with packs from pointers and values, then fold with
construct_at - Option 2: C++26 expansion statements using
views::iotaorviews::indices - Option 3: build a tuple-based “zip” of member references and construct via tuples
- A more range-based zip exists but hits a lifetime/allocation limitation.
- Workaround:
define_static_arrayavoids allocation by promoting a constexpr-computed range into static storage.
- Workaround:
10. Detour: implementing define_static_array
Implements the idea:
- take a range
- lift elements into constant template parameters via reflection
- instantiate a reflected array
It uses the fact that reflection can represent both types and values uniformly as info.
Observability: turning the structure into printable/debuggable output
11. Creating “spans” view into member buffers
Provides a span-of-members layout:
spans.xis aspan<char>spans.yis aspan<long>
This allows printing SoA members independently.
12. Automatic formatting via annotations
Instead of writing custom formatters for each generated wrapper type:
- introduces an annotation mechanism (e.g., “derive debug”)
- the formatter detects annotated types
- formatting then proceeds via reflection of members
Reflection utilities and ideas used:
annotations_of_with_typeto filter annotations by their tag type- reflection-based equality to compare annotation payloads without requiring
operator==for arbitrary payload types
13. Formatting details
Printed output includes:
- type name
- base classes (if any)
- non-static data members:
- their names
- their values
Type names and identifiers come from reflection helpers like:
display_string_ofidentifier_of
Mutable element access: proxy + “write-back”
14. v[i] returns a proxy (not a real T)
Because members live in separate arrays, mutation needs indirection.
Implements:
- a proxy type holding lvalue references to each member’s element (
char&,long&, etc.) - an assignment operator that:
- assigns from a
T - destructures
Tinto the member references
- assigns from a
- a conversion operator that:
- builds a
Tfrom the member references
- builds a
15. Formatting proxy correctly
If the proxy is formatted as its own reflected type, output is ugly (it reveals internal proxy_base structure).
Fix:
- introduce an annotation/trait like
format_asto declare:- “format this proxy as if it were
T”
- “format this proxy as if it were
- the formatter uses conditional logic/inheritance to format either:
- the proxy
- or underlying
T
Bonus: an API akin to “named member selection”
16. A with-like / w^? idea using reflection
Implements a function selected:
- takes:
- a function reflection
- a type reflection
- produces an array of the corresponding reflected members for parameter names
- emits a compile-time error if a parameter name doesn’t exist on the type
This supports selecting member fields by name order, then invoking a callback for each element index.
Tooling / implementation notes discussed
- Current implementations
- Most up-to-date reflection support is Dan Katz’s Clang fork, available on Compile Explorer under “reflection C++26”
- GCC work is underway by another developer
- compile-time benchmarking isn’t yet meaningful early on
- Debugging
- given the “define aggregate” workflow, debugging leans on printing/reflection introspection of generated members (e.g., offsets / produced members)
Open questions raised in Q&A (and answers)
- Can reflection observe attributes?
- Not in the current model (“you cannot reflect on attributes”).
- Can code generation like getters/setters be produced now?
- Not yet; current reflection/codegen mechanisms are limited (no true “generate members/getters” yet).
- Does “compile-time exception” become compile-time error?
- Yes: meta-exceptions thrown during constant evaluation produce compile-time diagnostics
meta exceptioncan include a message plus location info
- Unchecked access vs breaking changes when private members change
- Access contexts are explicit (
unchecked,unprivileged,current) - library authors can choose whether to depend on public-only or all members
- Access contexts are explicit (
- Why not allow
constevalfunctions with non-constant parameters?- Discussed as fundamental: functions must behave consistently across runtime argument variations
substitute/extracthelps lift constants into compile-time context safely
- Standardizing formatting/member-name annotations
- Likely: “default member-wise formatting” is a good candidate for future standardization
- annotations may become standardized utilities, similar to how language features can emerge from libraries
Main speakers / sources
- Speaker: Barry Revzin
- software engineer at Jump Trading
- involved in C++ standardization since 2016
- author/blogger; also a writer (noted as “swims writer” in the source)
- Referenced sources:
- Andrew Kelly — “practical guide to applying data oriented design” (Zigg language creator)
- Victoria — keynote mentioned for data-oriented design
- Dan Katz — Clang fork implementing reflection C++26
- Matt — referenced in audience thanks (possibly related to Calabrese / Compile Explorer context)
- Range-v3 — and related ideas like zip/transform/actions as conceptual parallels