Strong types in C++ have always been a frustrating half-measure. The language gives you using Meters = double and typedef double Meters, both of which create aliases rather than distinct types. You can pass a Meters where a Kilograms is expected and the compiler will say nothing. The problem has been understood for a long time, but the solutions have consistently involved either macros, heavy template machinery, or manual duplication, none of which compose well.
C++26 static reflection changes the calculus here. A thread on r/cpp demonstrates a make_strong_typedef utility that generates fully distinct wrapper types from an existing type at compile time, forwarding all public member functions automatically. It is a proof of concept, but it points at something real: reflection makes an entire category of metaprogramming problems tractable that previously required either compromises or significant ceremony.
The Traditional Strong Typedef Problem
Before getting into what reflection enables, it is worth understanding why the existing approaches fall short. The canonical template approach looks like this:
template <typename T, typename Tag>
class StrongType {
T value_;
public:
explicit StrongType(T v) : value_(v) {}
T get() const { return value_; }
T& get() { return value_; }
};
using Meters = StrongType<double, struct MetersTag>;
using Kilograms = StrongType<double, struct KilogramsTag>;
This gets you type safety for primitive values, and Jonathan Boccara’s NamedType library builds a polished version of this pattern with CRTP-based skill mixins for arithmetic, comparison, and I/O. But the template approach only works cleanly for value types wrapping scalars. If you have a class with a rich interface, you either manually re-expose each method through the wrapper, or you inherit and expose using Base::method declarations. Both are fragile; add a method to the base type and the wrapper silently goes out of date.
BOOST_STRONG_TYPEDEF is a macro that generates a more complete wrapper, but it only handles a narrow set of operations and has all the usual macro problems around scoping and composition. It was useful when it was written; it has not aged well.
The fundamental issue is that before C++26, there was no way to introspect the public interface of an arbitrary type at compile time and generate code based on that introspection. You could template over a type, but you could not enumerate its methods. Reflection is precisely that capability.
What C++26 Reflection Adds
The core of C++26 static reflection, specified in P2996, is a reflection operator ^^ that produces a std::meta::info value representing a language entity, combined with the ability to query and splice these reflection objects within consteval contexts.
The ^^ operator works on types, functions, variables, namespaces, and other entities:
constexpr std::meta::info t = ^^int; // reflects the type int
constexpr std::meta::info f = ^^std::printf; // reflects the function
Once you have a reflection object, you can query it: get its name, enumerate its members, inspect base classes, check access specifiers. The splice operator [: :] is the inverse: it takes a reflection object and inserts the reflected entity back into the code at the splice site.
The consteval { } block syntax (a companion proposal) lets you run arbitrary compile-time code as a top-level statement, which is what allows the make_strong_typedef pattern to work without a dedicated consteval function at the call site:
consteval {
make_strong_typedef(^^FoodItem, ^^Item);
make_strong_typedef(^^BookItem, ^^Item);
make_strong_typedef(^^MovieItem, ^^Item);
}
Inside make_strong_typedef, the implementation can enumerate all public members of Item using std::meta::members_of, then generate corresponding declarations in the target type that delegate to a stored instance of the base type. The generated types are fully distinct: FoodItem and BookItem do not share a common base class and are not implicitly convertible.
Comparison With Rust and Haskell
This brings C++26 meaningfully closer to what Rust has offered from its first stable release. The Rust newtype pattern is idiomatic and trivial:
struct Meters(f64);
struct Kilograms(f64);
These are distinct types with zero runtime overhead. You access the inner value via .0, and if you want the type to support arithmetic or formatting, you implement the relevant traits. Derive macros can automate much of this:
#[derive(Debug, Clone, Copy, PartialEq, PartialOrd)]
struct Meters(f64);
What Rust does not give you automatically is forwarding a rich method interface. If the inner type has twenty methods you want to expose, you still write delegation code or use a crate like derive_more. The C++26 reflection approach, if taken to completion, would outperform Rust here: it reflects the actual public interface and generates forwarding wrappers for all of it, including methods the library author had not anticipated needing.
Haskell’s newtype goes even further at the language level. A Haskell newtype is truly zero-cost, erased entirely at runtime, and the language provides GeneralizedNewtypeDeriving to automatically derive any typeclass that the wrapped type supports:
newtype Meters = Meters Double deriving (Show, Eq, Ord, Num)
This is the cleanest version of the idea. The C++26 reflection approach is more verbose and requires the make_strong_typedef utility to be carefully written, but it operates on the same principle: generate a distinct type that wraps the original, and automatically expose the interface.
The queue_injection Caveat
The example code in the original post uses queue_injection { ... }, noting that this syntax was part of EDG’s experimental reflection implementation but was not integrated into the C++26 standard. Without it, generating members into a type that is being defined requires a two-stage build: a first pass generates source code via reflection, and a second pass compiles the result.
This is a meaningful limitation. The standard C++26 approach to injecting new members uses std::meta::define_class or similar facilities, which work within the constraints of single-pass compilation. The queue injection mechanism would have allowed in-place type mutation during the consteval phase, which is a more powerful but also more semantically complex operation. The committee’s caution here is not unreasonable; the interaction with the normal rules around incomplete types, ODR, and template instantiation is non-trivial.
In practice, the two-stage approach works and is how other reflection-heavy languages have handled code generation for years. It is less ergonomic, but the end result is the same: generated types that are indistinguishable from hand-written ones.
What This Pattern Enables Beyond Strong Types
The make_strong_typedef example is instructive because it demonstrates a pattern that extends well beyond type safety for numeric units. Anywhere you currently maintain a wrapper type that delegates to an underlying type, reflection can automate that delegation:
- Proxy types that add logging or instrumentation around every method call
- Mocking and test double generation that mirrors a real type’s interface
- Versioned type wrappers that add deprecation annotations to specific methods
- Capability-restricted views that expose only a subset of an interface
All of these currently require either macros, external code generators, or significant hand-maintenance. C++26 reflection moves them into the language.
The compile-time cost is the obvious concern, and it is real: reflecting over a type with many members in a consteval context does add to build times. But it adds at compile time only, and the generated code is identical to what you would write by hand. There is no runtime overhead relative to manually written wrappers, which is the same guarantee that template metaprogramming has always offered.
C++26 will not be the last word on this. The reflection facilities landing in the standard are powerful but incomplete; aggregate initialization for reflected types, as noted in the source post, is still under-specified. These are the kinds of rough edges that get smoothed over the following standard cycle. What C++26 delivers is the foundational mechanism, and the make_strong_typedef example shows that the mechanism is already sufficient for non-trivial use cases.
For anyone who has written the same forwarding wrapper by hand more than twice, that is worth paying attention to.