Document: P4008R0
Author: Zhiyi Lin
Date: 2026-02-08
Audience: EWG
Target: C++29 / C++31
Proposes splitting the standard into three fixed modules: std.core (non-excludable low-level primitives), std.modern (safe modern library), and std.legacy (C compatibility pitfalls like array decay, C-style casts, implicit narrowing). Users opt into "clean mode" with exclude std.legacy. The pitch: keep full backward compatibility by default, let new code opt out of historical baggage per translation unit.
This is essentially what Profiles (P3970R0, P3984R0) are trying to do, but with a different mechanism. Profiles use compiler-enforced rule sets. This uses module-level opt-out. The DG has already committed to the Profiles framework. I'm not sure EWG will entertain a parallel approach when the committee has repeatedly voted for Profiles as the direction.
Not every paper needs to align with the DG's preferred framework. The idea of tying safety modes to modules rather than compiler flags is worth exploring even if Profiles win. Different mechanisms can inform each other.
The vision is appealing but the details are thin. "Array decay is disabled" - what does that mean for functions that take
const char*? String literals decay to pointers everywhere. Do you lose string literals in clean mode? The paper doesn't address this. The boundary between "legacy pitfall" and "essential C interop" is much fuzzier than the three-module split implies.Exactly. Every C API uses array decay. Every platform header. Every system call wrapper.
exclude std.legacywould break interop with the OS unless you exempt FFI boundaries, at which point you need the complexity of Profiles to specify where the rules apply and where they don't.The axioms are catchy but they assume a clean separation that doesn't exist.
reinterpret_castis low-level and dangerous. Pointer arithmetic is low-level and dangerous. These are instd.core(non-excludable). The paper's own framework means you can't opt out of the most dangerous features.The "teachable again" angle resonates. New developers starting in clean mode without array decay, C-style casts, or implicit narrowing would have a much better experience. Whether this specific mechanism is the right way to achieve it is debatable, but the goal is worth pursuing.
exclude std.legacyas new syntax is a non-starter for C++29. New keywords and syntax additions need EWG review, CWG wording, compiler implementation, and years of transition. The paper targets C++29 Phase 1 but the mechanism requires language changes that typically take two cycles to stabilize.The paper says "not dialect fragmentation." But if clean-mode code can't call legacy-mode code without adapters, and legacy-mode code can't include clean-mode headers that use features legacy mode would reject... that's fragmentation. The interop story needs much more detail.
R0 papers are for opening discussion, not for shipping. The vision is clear even if the mechanism needs work. If the author engages with EWG feedback and iterates, the core idea - per-TU opt-out of specific legacy behaviors - could evolve into something useful. Or it could merge into the Profiles effort.
"Builds on C++20 Modules" - but modules control visibility and linkage, not language semantics. Making
import std.legacychange which language features are available would be a fundamental extension of what modules do. It's not just "using mature infrastructure."The "axioms" section reads like a manifesto. I appreciate the energy but EWG papers usually benefit from less philosophy and more wording.
The code example replaces
(int)dwithstd::narrow_cast<int>(d). That's already best practice. You don't need a module system to enforce it - a linter does the job. The paper is proposing heavy machinery for something clang-tidy already checks.