r/wg21
P3856R5 - New reflection metafunction - is_structural_type (US NB comment 49) WG21
Posted by u/nttp_archaeologist · 6 hr. ago

Document: P3856R5
Author: Jagrut Dave, Alisdair Meredith
Date: 2026-02-11
Audience: LEWG / LWG


You know how C++20 expanded NTTPs to allow class types as template parameters? The standard defines exactly what a "structural type" means — scalar, lvalue reference, or literal class type with all-public, non-mutable direct bases and data members, recursively — and then mandates that certain library types must be structural, and gives users exactly zero portable way to verify their own type qualifies.

P3856R5 closes that gap. It adds std::meta::is_structural_type(info) to the <meta> header (C++26 reflection) and std::is_structural<T> / std::is_structural_v<T> to <type_traits>. The trigger was US NB comment 49 from the C++26 CD ballot — someone finally noticed the oversight and wrote it up as a proper paper.

The interesting detour is Section 3: the authors built a proof-of-concept on P2996 reflection primitives. It mostly works, but reveals gaps in the reflection API that are arguably more notable than the proposal itself. There is no is_constexpr metafunction for querying whether a destructor is constexpr, no is_literal_type (removed from <type_traits> in C++20), no is_lambda_closure_type. The PoC works around all of these with conservative approximations. Captureless lambdas are structural, but the PoC cannot verify it cleanly.

Section 4 contains a design-philosophy argument about type traits vs. reflection metafunctions: they should be implemented independently, not built on each other. That one will age interestingly.

▲ 312 points (89% upvoted) · 23 comments
sorted by: best
u/r_cpp_janitor 1 point 6 hr. agoModerator

Reminder: be civil. Paper authors occasionally stop by these threads.

u/committee_heatdeath_2025 847 points 5 hr. ago

Bloomberg paper detector: online. Alisdair Meredith papers authored: approaching countably infinite. Carry on.

u/nttp_archaeologist 203 points 5 hr. ago

In fairness, Alisdair co-authoring is a fairly reliable quality signal. He has forgotten more about the library than most of us have learned.

u/former_stdlib_impl 312 points 5 hr. ago

The paper buries the lede:

Library implementers must have this functionality internally, but it is not exposed to users.

This undersells the situation. When C++20 shipped class-type NTTPs, every stdlib implementation immediately needed to check structural-ness to implement their own mandates. The way they all did it: undocumented compiler intrinsics. Check libstdc++, libc++, MSVC STL sources — they all have equivalent private machinery. It has been sitting there for five years, unexposed, because there was no standard way to describe the predicate to users.

This paper is standardizing what compiler vendors already had to implement. The question is not "should we add this" — it is "why did this take until the NB comment phase of C++26 to write up."

u/cplusplus_is_fine_actually 445 points 4 hr. ago

Structural type check in C++20: squint at the error message and figure out which member broke it. Structural type check in C++26: is_structural_v<T>. Progress.

u/compiles_first_try 234 points 4 hr. ago

Section 3.1.1, Example 1:

template<typename V>
requires is_structural_v<decltype(V)>
struct const_wrapper { static constexpr auto value = V; };

This does not compile. V is a type parameter. decltype(V) is ill-formed — you cannot call decltype on a type. This should be template<auto V>. The intent is clearly a non-type template parameter constrained to structural types, but the example uses typename throughout the motivating section. Did these get built before submission?

u/const_expression_enjoyer 134 points 3 hr. ago

Same section also uses is_structural_type_v in the example code, but the proposed variable template in the actual wording section is is_structural_v. Two different names for the same thing within the same paper. One of them gets fixed before LWG sees it — hopefully.

u/committee_heatdeath_2025 178 points 3 hr. ago

Ah yes, the classic pipeline: motivation section describes API A, wording section proposes API B, editorial note says "these are the same."

u/library_design_nihilist 156 points 3 hr. ago

Section 4 states:

The authors' preliminary thinking is that type traits and metafunctions should implement the abstract library API specification independently.

I understand the argument — different observable side effects (template instantiations vs. reflection queries). But this creates a correctness surface. Two independent implementations of the same predicate can drift. There is no enforcement mechanism. If is_structural_v<T> and is_structural_type(^^T) give different answers on an edge case, which one is authoritative? The answer "both are normatively specified against [temp.param] so they agree" is satisfying in theory and fragile in practice.

u/metaprogramming_archaeologist 98 points 2 hr. ago

The independence is about implementation strategy, not observable semantics. is_structural_v<T> and is_structural_type(^^T) are both specified normatively against [temp.param]. If they disagree on any type, that is a non-conforming implementation — no different from is_trivially_copyable_v<T> diverging from __is_trivially_copyable(T). We already have this pattern throughout <type_traits>. Section 4 is telling vendors what they should do internally, not claiming divergence is acceptable.

u/library_design_nihilist 67 points 2 hr. ago

Sure, but the PoC in the paper is demonstrably conservative by the paper's own admission. It cannot correctly identify structural-ness for non-aggregate literal class types because is_constexpr(info) does not exist yet. A correct is_structural_v<T> (via compiler intrinsic) would return true for some such types today, while the PoC is_structural_type(info) returns false. The divergence is present right now, in the paper. The "normative spec ensures agreement" argument only holds once P2996 has the missing primitives.

u/metaprogramming_archaeologist 89 points 1 hr. ago

That is the most honest critique of the paper. The PoC being conservative is not the proposal being conservative — the actual wording says is_structural_type(info) returns true iff info reflects a structural type per [temp.param], full stop. Real implementations will use intrinsics, not the PoC. But you are right that the paper does not make this distinction loudly enough. The PoC reads like the intended implementation. It is not. Someone in LWG will ask this exact question, and the authors will need a cleaner answer.

u/ferris_crab_420 45 points 3 hr. ago

Rust solved const generic eligibility in 1.51. Types usable as const generic parameters implement PartialEq. Much simpler than C++, which needs this whole recursive "are all members public and non-mutable and also structural" walk.

u/daily_undefined_behavior 512 points 3 hr. ago

oh we're doing this again

u/async_skeptic 178 points 2 hr. ago

Rust const generics and C++ NTTPs are solving different problems. Rust's PartialEq requirement is about equality semantics so the compiler can deduplicate template instantiations. C++ structural types are about layout and public visibility so the compiler can encode the value in the mangled name. Neither is strictly better — they encode different invariants for different purposes. Also: Rust still does not allow floats as const generic parameters. C++ does. Different design goals, different trade-offs.

u/just_a_cpp_user_9000 78 points 2 hr. ago

Genuine question from someone who does not follow reflection closely: why can't you approximate this with a SFINAE probe? Something like trying to use T as an NTTP and seeing if substitution succeeds? Is the issue that structural-type violations are hard errors rather than substitution failures?

u/impl_defined_happiness 134 points 1 hr. ago

You nailed it — structural-type violations in NTTP context are ill-formed, not substitution failures. SFINAE does not catch them. Even if you managed to make a specific compiler swallow it, you would be relying on implementation-defined behavior. The standard is_structural_v<T> can be a compiler intrinsic: O(1), no template instantiation overhead, guaranteed portable. The SFINAE trick works on some compilers today and breaks quietly when the next version tightens conformance.

u/consteval_everything 267 points 2 hr. ago

The most interesting thing in this paper is buried in Section 3.3:

The implementation could be improved if metafunctions such as "is this a literal type", "is this a lambda closure type", or "is this function constexpr" were available.

std::is_literal_type<T> was deprecated in C++17 and removed in C++20 on the grounds that it was too broad to be useful. Now, six years later, we need the reflection equivalent to implement is_structural_type correctly. The committee removed a building block, then built something that depends on the building block. This is not the first time and will not be the last.

u/null_pointer_wizard 189 points 2 hr. ago

The subtitle "US NB comment 49" is doing a lot of work in that title. This is a proposal responding to a National Body comment on a Committee Draft that is itself the output of a process that began with earlier papers. Somewhere there is a comment that generated a paper that cites a paper that addresses a comment that will generate another paper. The process is a fixed point.

u/xX_move_semantics_Xx -5 points 47 min. ago

[deleted]

u/null_pointer_wizard 34 points 41 min. ago

what did they say

u/daily_undefined_behavior 423 points 38 min. ago

something about Rust, you know the usual