Strong types have had an awkward existence in C++. The idea is simple and well understood: wrap a primitive or common struct in a distinct named type so the compiler can catch semantic misuse. A UserId and a ProductId might both be integers under the hood, but they represent fundamentally different things, and no function that expects a UserId should silently accept a ProductId. The type system should enforce that distinction. Most C++ developers agree this is correct. Most C++ codebases use raw type aliases anyway.
The reason is boilerplate. Creating a genuinely distinct strong type in C++ requires manually wrapping the underlying type and forwarding whatever parts of its interface you want to expose. The NamedType library by Jonathan Boccara is probably the most widely used solution, and it works through a template wrapper with mixin-style capability injection:
using Kilometers = fluent::NamedType<double, struct KilometersTag,
fluent::Addable, fluent::Comparable, fluent::Printable>;
This gets you a distinct type with a controlled interface. But the underlying value is always a primitive, which means you’re stuck listing capabilities explicitly, and the moment your base type is a struct with real member functions rather than a scalar, the library approach breaks down. You’re left writing the wrapper by hand.
Handwriting a strong typedef for a struct means transcribing its entire public interface, delegating every method call to the inner value, and repeating that process for every distinct type you want. For a struct with ten methods, that’s forty or fifty lines of pure mechanical forwarding code, times however many strong variants you need. Nobody does this in practice. They use using or typedef, which create aliases rather than new types, and the compiler treats them as identical.
This is the problem that a recent discussion on the C++26 reflection r/cpp thread addresses directly. The proposal is to use the reflection machinery in C++26 to generate strong typedefs automatically, letting the compiler write the boilerplate that humans have always avoided writing.
What the Reflection Approach Looks Like
The core of P2996, the reflection proposal that made it into C++26, introduces a ^^ operator that produces a compile-time reflection object representing a type or declaration. These reflection objects can be inspected and manipulated inside consteval contexts, which execute exclusively at compile time. The combination lets you write code that introspects a type’s members and generates new declarations based on what it finds.
The strong typedef pattern builds on this:
struct Item {
std::string name() const { return name_; }
double price() const { return price_; }
private:
std::string name_;
double price_;
};
struct FoodItem;
struct BookItem;
struct MovieItem;
consteval {
make_strong_typedef(^^FoodItem, ^^Item);
make_strong_typedef(^^BookItem, ^^Item);
make_strong_typedef(^^MovieItem, ^^Item);
}
After that consteval block executes, FoodItem, BookItem, and MovieItem are fully distinct types. Each wraps an Item internally and exposes its public interface through forwarded methods. A function taking a FoodItem& will reject a BookItem at compile time, not at runtime, and not through a template trick.
The make_strong_typedef function itself is where the reflection machinery lives. It uses ^^Item to enumerate Item’s public member functions at compile time, then injects corresponding forwarding methods into the target type. In the experimental EDG implementation referenced in the thread, this uses queue_injection, which lets you queue up code to be injected into a class definition. The author notes that queue_injection was not actually merged into the C++26 standard, so the current path for production use would require a two-stage build: one pass to generate the forwarding code, a second to compile against it. That is less elegant but mechanically equivalent.
Why This Matters More Than It Looks
The obvious win is ergonomics. Three strong types from one base struct, declared in four lines. But the implications go further than convenience.
Strong types have historically required you to decide upfront which capabilities to expose, because hand-written wrappers or library-based approaches force an explicit list. With reflection-based generation, the default is to forward the entire public interface, and you opt out of specific methods rather than opting in. That inversion makes strong types much more viable for complex domain types, not just scalars. A PriceInEuros and a PriceInDollars can both be full-featured price objects with methods for formatting, comparison, and arithmetic, and the compiler will still prevent you from mixing them.
The other implication is consistency. When you maintain a hand-written wrapper, any new method added to the base type has to be manually added to every wrapper. With generated wrappers, adding discount() to Item automatically makes it available on FoodItem, BookItem, and MovieItem without touching the typedef declarations. The strong type definitions become a declaration of intent rather than an ongoing maintenance burden.
Comparison to Other Languages
The pattern is well established elsewhere. Haskell’s newtype construct is the canonical reference:
newtype FoodItem = FoodItem Item
newtype BookItem = BookItem Item
Haskell newtypes are zero-cost at runtime since they are erased by the compiler. Crucially, you can derive type class instances automatically, which is the equivalent of forwarding the interface. The GeneralizedNewtypeDeriving extension lets you write deriving (Show, Eq, Ord) and have those instances generated from the wrapped type, rather than writing them yourself.
Rust’s newtype pattern is structurally similar but more manual:
struct FoodItem(Item);
struct BookItem(Item);
These are distinct types, zero-cost at runtime, and the compiler will not let you pass one where the other is expected. The catch is that trait implementations do not forward automatically. If Item implements Display, FoodItem does not inherit that implementation. You write impl Display for FoodItem and delegate to self.0. The newtype_derive crate and similar macros fill this gap procedurally, but it is still an explicit opt-in per trait.
C++26 reflection, when the tooling matures, promises something closer to the Haskell model: declare the strong typedef relationship, get the full interface forwarded, without listing capabilities individually. The make_strong_typedef example in the thread does not yet handle every case (aggregate initialization is noted as incomplete), but the architecture is right.
The Current State and Realistic Timeline
The honest caveat is that you cannot use this today in production code. The reflection proposal (P2996) is in C++26, which has not been finalized as of this writing, and compiler support is still experimental. The EDG-based implementation is the main working prototype. Clang has had exploratory reflection work under the -freflection flag for several years through the Andrew Sutton and Herb Sutter metaclasses proposals, though the API has evolved significantly as the proposals converged on P2996.
The queue_injection mechanism, which makes the one-pass version of this pattern work cleanly, is explicitly experimental and not part of the C++26 standard. The two-stage alternative is workable and is essentially what reflection-based code generation tools have always required, but it adds friction to the build system.
For anyone writing C++ today who needs strong types, the NamedType library remains the most ergonomic production-ready option for scalar types, and hand-written wrappers are unavoidable for complex structs. The reflection-based approach is worth understanding now because it clarifies what the endgame looks like, and because the patterns you would write against it are clean enough to prototype with two-stage generation in the interim.
What Changes When This Ships
The practical impact of mature C++26 reflection for strong types is that the cost of correctness drops to near zero. Right now, the ergonomic path and the safe path diverge: type aliases are easy, strong types are tedious. When generating a strong typedef is four tokens rather than forty lines, teams will default to it.
That shift has second-order effects on API design. Function signatures become more informative without requiring documentation comments to explain what each parameter means. Overload sets that currently rely on argument position to distinguish semantically different inputs can use type distinctions instead. Domain modeling that would have required a full-blown class hierarchy for type safety can use lightweight generated wrappers.
C++ has had the conceptual tools for strong types for a long time. The reflection proposal is the first time it has had the ergonomic tools to match. Whether that changes how the language is used in practice depends on how quickly compiler implementations stabilize, but the direction is clear.