· 6 min read ·

C++26 Reflection Can Read Your Types. Generating New Ones Requires Patience.

Source: isocpp

A thread on r/cpp via isocpp.org is making the rounds for a compelling reason: it shows a make_strong_typedef utility that generates fully distinct wrapper types from an existing class, forwarding all public member functions, with almost no user boilerplate.

struct Item { /* name(), price() as methods */ };

struct FoodItem;
struct BookItem;
struct MovieItem;

consteval {
    make_strong_typedef(^^FoodItem, ^^Item);
    make_strong_typedef(^^BookItem, ^^Item);
    make_strong_typedef(^^MovieItem, ^^Item);
}

Three fully distinct types. Each wraps Item and forwards its interface. FoodItem is not BookItem is not Item. The compiler enforces all of it. If you have been waiting for C++ to solve the strong typedef problem for twenty years, this looks like the payoff.

It is largely real. But it is worth understanding precisely which parts of C++26 make it work, because the demo quietly depends on an experimental feature that is not actually in the C++26 standard.

Two Proposals, One Demo

C++26 reflection, formally P2996, gives you compile-time introspection of C++ entities as first-class values. The ^ operator (written as ^^ in some EDG documentation variants) produces a std::meta::info value from a type, function, variable, or enumerator. From there, the std::meta namespace exposes consteval functions that enumerate and query those values:

consteval auto count_public_methods(std::meta::info type) {
    std::size_t n = 0;
    for (auto m : std::meta::members_of(type))
        if (std::meta::is_function(m) && std::meta::is_public(m)) ++n;
    return n;
}

This part is unambiguously in C++26. It was accepted into the working draft at the Wrocław WG21 meeting in November 2024. Bloomberg maintains a public Clang fork with a working implementation, and both it and the EDG reference compiler are available on Compiler Explorer. You can iterate over struct members in a normal for loop, call std::meta::name_of() and std::meta::type_of(), filter with predicates, pass reflections around as values. All of that works in standard C++26.

The part the demo also depends on is P3294, “Code Injection for C++26”. This proposal extends reflection with the ability to programmatically generate new declarations and inject them into types. The queue_injection { ... } syntax in the r/cpp thread is the EDG experimental implementation of this idea. The author explicitly notes this, but the caveat can get lost in the excitement.

P3294 did not make C++26. It is currently targeting C++29.

The distinction matters because without code injection, P2996 is read-only. You can examine what a type contains. You cannot synthesize a new type by reflection alone.

What Introspection Alone Can and Cannot Do

To understand the gap, consider what make_strong_typedef actually has to accomplish. It needs to:

  1. Declare a new struct with a private Item value_ member.
  2. Generate forwarding constructors.
  3. Enumerate every public method on Item and synthesize a matching method on the new type that delegates to value_.

Step three is where code injection becomes necessary. Enumerating the methods is pure P2996 introspection, easy. But actually creating new method declarations in the target struct requires injecting new declarations into that struct’s body. That is P3294.

In standard C++26 without code injection, you can get close but not all the way:

// What you CAN do with P2996 alone: generate source code as a string
consteval std::string generate_strong_typedef(std::meta::info new_type,
                                             std::meta::info source) {
    std::string out;
    out += "struct " + std::string(std::meta::name_of(new_type)) + " {\n";
    out += "    " + std::string(std::meta::name_of(source)) + " value_;\n";
    for (auto m : std::meta::member_functions_of(source)) {
        if (!std::meta::is_public(m)) continue;
        // Emit a forwarding wrapper...
    }
    out += "};\n";
    return out;
}

This is the two-stage build approach: a first pass uses P2996 to inspect types and emit generated source, then a second compilation pass compiles that output. It works, but it is not the same as consteval {} blocks that inject code in-place. You have a build system dependency and a code generation artifact to manage.

The EDG experimental queue_injection syntax is a bridge over this gap, but it is exactly that: experimental. Using it means your code will not compile on the Bloomberg Clang fork, which more closely tracks the standard. The author of the r/cpp thread notes this honestly; it just is not the headline.

How This Compares to Rust

Rust has solved a version of this problem with the newtype pattern, and the comparison is instructive. A Rust tuple struct creates a genuinely distinct type:

struct Meters(f64);
struct Kilograms(f64);

Meters and Kilograms are distinct types with no implicit conversion between them. The inner f64 is accessible via .0. The pattern is idiomatic and zero-cost.

The forwarding problem still exists. If you want Meters to support Add, Display, PartialOrd, and so on, you have two options: derive them explicitly where the trait is derive-able, or implement them manually. The derive_more crate extends the set of derivable traits significantly, letting you write:

#[derive(Add, Display, PartialOrd, PartialEq)]
struct Meters(f64);

This is broadly similar to Jonathan Boccara’s NamedType library for C++, where you list which “skills” your strong type opts into. Both require you to enumerate the operations you want rather than inheriting a complete interface automatically.

What the C++26 reflection demo promises, and what Rust does not yet offer, is total interface forwarding: every public method on the source type becomes available on the wrapper, without listing them one by one. In C++, this makes sense because classes can have arbitrary member functions that are part of their public contract. Automatically forwarding all of them means adding a method to Item propagates to FoodItem, BookItem, and MovieItem without touching their definitions.

That is genuinely useful for the case where the source type is a rich domain object with a stable interface. It is less clearly useful for primitive types like double, where the interface is operators rather than named methods, and where selective opt-in via NamedType’s skill system is actually the right design: you probably do not want Meters * Meters to return Meters.

Where the Standard Actually Stands

Haskell’s newtype with GeneralizedNewtypeDeriving is still the cleanest point in the design space:

newtype Meters = Meters Double deriving (Show, Eq, Ord, Num, Fractional)

One line. The derived instances come from the underlying type. The type is distinct. This has been in GHC since version 6.8 and is completely unremarkable in Haskell codebases.

C++ is converging on something equivalent, but in pieces across multiple standards. P2996 gives you the introspection layer in C++26. P3294 will give you code injection in C++29, assuming it stays on track. The consteval {} block syntax (P3289) that makes the r/cpp demo readable is also part of the companion proposal set around reflection.

For production code targeting C++26 compilers shipping in 2027 or 2028, the two-stage build approach is what will actually be available: write a small reflection-powered code generator, run it during your build, compile the output. That is less elegant than consteval { make_strong_typedef(^^FoodItem, ^^Item); } but it is real and portable.

For the EDG or Bloomberg Clang experimental builds on Compiler Explorer, the demo in the r/cpp thread works today, right now, in a browser. It is a proof of concept in the honest sense: it proves the concept is sound and the design is right. The machinery to make it standard is already written and being refined.

What the Demo Gets Right

Setting aside the injection caveat, the design of make_strong_typedef in the r/cpp thread is the right shape for this problem. It demonstrates several things that matter:

The std::meta::member_functions_of (or members_of filtered by is_function) enumeration can drive forwarding wrapper generation. This is a genuine use of P2996 introspection, not a workaround. The interface of the source type is discovered, not manually maintained.

The author also notes that aggregate initialization is not fully handled and that some features are underdeveloped. This is honest about the current state: the compiler support exists, but the library layer for building these patterns needs refinement.

The use of consteval {} blocks at namespace scope for type generation is also worth noting. It reads like an imperative program: call make_strong_typedef once per target type, and the compiler executes that at compile time. The eventual model for metaclasses in C++ is visible here, even if metaclass as a keyword never ships: class transformations as consteval functions, applied to types by name.

For anyone following C++26 metaprogramming seriously, P2996 is the right place to start. The Bloomberg Clang fork lets you experiment with real code today. The strong typedef pattern specifically will need to wait for P3294 to be production-ready, but the introspection primitives that make it possible are already locked in.

The problem has been understood for thirty years. The language finally has the machinery to solve it in library code rather than syntax. The full solution will arrive in two stages, which is a reasonable price for getting the design right.

Was this interesting?