Document: P4005R0
Author: Ville Voutilainen
Date: 2026-02-02
Audience: EWG
Ville Voutilainen is back with a contracts paper that takes a different approach to guaranteed enforcement. Instead of extending P2900's contract syntax with always-enforced variants (that's P3911's lane), this paper proposes entirely new keywords: entry_cond, return_cond, and mandatory_assert. These are guaranteed to be checked - never ignored, never just observed - using either enforce or quick_enforce semantics.
The key design choice: if you declare entry_cond(x >= 0) on a function, that condition is checked on every call - including through function pointers. No opt-out. The paper also proposes that mixing these new guaranteed assertions with P2900 assertions on the same declaration is ill-formed, creating a clean separation between the two worlds.
Perhaps the spiciest bit: the paper suggests implementations should mangle entry_cond expressions into function names, so changing a contract gives you a linker error rather than a silent behavioral change. Intentional ABI breaks as a safety feature.
No formal wording yet - the paper uses "pseudo-specification" - but follows the design requirements laid out in P3919R0.
P4005R0 - "A proposal for guaranteed-(quick-)enforced contracts" by Ville Voutilainen. EWG | 2026-02-02 | wg21.link/p4005r0
Reminder: Be civil. Paper authors sometimes read these threads.
Oh great, another contracts paper. We're at P4005 now? The paper numbers alone tell you how many trees have died for this feature.
Contracts for contracts for C++. Inception, but the spinning top never stops.
I've been tracking contracts papers since P0542. I've watched them go through more revisions than my marriage went through counselors.
I've been following contracts since SG21 was chartered, and this paper crystallizes the fundamental strategic question the committee has been dancing around:
1. P2900 ships in C++26 with configurable semantics (ignore/observe/enforce/quick_enforce)
2. There is clear demand for a "guaranteed, cannot be ignored" tier (see P3919R0)
3. P3911 solves this by adding always-enforced variants to P2900's existing syntax
4. P4005 solves this by creating an entirely separate keyword vocabulary
The "ill-formed to mix" rule isn't a limitation - it's a thesis statement. Voutilainen is arguing that guaranteed enforcement is sufficiently different from P2900's model that it needs its own design space.
But this means your codebase has two contract dialects. Your tools need to understand both. Your developers need to know when to use
prevsentry_cond. This is not composition - it's fragmentation.I see the fragmentation concern, but consider: the P2900 design was built around a specific philosophical commitment - that the checking semantic is a deployment choice, not a source-code choice. The whole ignore/observe/enforce model exists because P2900 intentionally decouples "what to check" from "how to check."
Guaranteed enforcement is a rejection of that philosophy for a subset of assertions. You're saying "this one is not negotiable." That's a fundamentally different contract, and different syntax makes that visible.
P3911 tries to graft this onto P2900 with
pre!syntax, but that creates the illusion that it's just P2900 with a stronger mode. It's not. The ODR implications, the mangling story, the exception handling rules - they're all different.But P3911 manages to solve the same design requirements from P3919R0 without introducing new keywords into the language. That's not nothing. Every new keyword is a cost. Every new syntax form is something developers have to learn, tools have to parse, and future proposals have to interact with.
And the "two dialects" problem is real. I've worked on codebases that had three different assertion macro frameworks because each team picked a different one. The standard was supposed to fix that. Now we're standardizing two?
Counterpoint: P3911's
pre!occupies the same syntactic namespace aspre, which means they share parsing rules, overload resolution interactions, and tooling expectations. If guaranteed enforcement needs different rules (and this paper argues it does - no constification, no exception translation, mandatory ABI encoding), then sharing syntax creates hidden complexity.I'd rather have two clearly different things that are obviously different than two similar-looking things with subtly different semantics. We have enough of the latter in C++ already.
Fair point on the "same syntax, different semantics" hazard. I'll concede that.
I still think the cost of new keywords is being underweighted, but the argument that guaranteed enforcement is philosophically distinct from P2900's model is stronger than I initially gave it credit for. This is ultimately an EWG design judgment call.
Meanwhile Rust has had this since 1.0 with zero committee meetings. But sure, let's debate the spelling of "your function must not receive garbage" for another decade.
Sir, this is a Wendy's. And the Wendy's has 40 years of deployed code that isn't getting rewritten.
I'm not even trolling. Rust's type system guarantees preconditions at compile time through the type system. This paper is adding runtime checks with new keywords and calling it safety. These are different levels of assurance and pretending they're equivalent is dishonest.
Rust's borrow checker doesn't do arbitrary precondition checking. You can't express "x must be between 0 and 100" in the type system without newtype wrappers and fallible constructors. Contracts and type-level guarantees are complementary, not competing. Nobody's pretending they're equivalent.
So let me get this straight. The paper is called "guaranteed-(quick-)enforced contracts." The word "guaranteed" is in the title. The entire pitch is "unlike P2900, these can never be ignored or observed." And then, buried in the semantics section, we find that whether you get
enforce(calls your violation handler before termination) orquick_enforce(just terminates, no handler) is... implementation-defined.That's not a minor detail. The violation handler is the mechanism by which production systems do logging, telemetry, graceful shutdown. If the implementation can choose
quick_enforceby default and your handler never runs, you've guaranteed enforcement of the check but not guaranteed that your code gets to react to the failure.This is still meaningfully stronger than P2900's model where
ignoreis a valid semantic. I won't pretend otherwise. But "guaranteed enforcement" and "guaranteed your handler runs" are different claims, and the paper title sells both.Edit: To be fair, P2900's
quick_enforcehas the same property. The difference is that P4005 can never fall back toignore, which is the real safety guarantee. The handler issue is orthogonal. I'm nitpicking the marketing, not the mechanism.Wait,
quick_enforcecan skip the violation handler entirely? So if I'm relying on my violation handler to flush logs before the process dies... it just doesn't run?How is this different from
__builtin_trap()with extra steps?That's essentially what
quick_enforceis - the rationale is performance. Calling the violation handler involves a function call, potential exception handling setup, and whatever the handler itself does. For hot paths in embedded or HFT, that overhead matters even in the failure case.The guarantee is: "this condition is always checked." The question of what happens when it fails has two answers, and the implementation picks which one. Both answers end in termination.
The mangling suggestion is the most interesting part of this paper, and I suspect most people will skip right past it.
The idea: if
entry_cond(x >= 0)is mangled into the function's symbol name, then changing the condition toentry_cond(x > 0)changes the mangled name. Any translation unit compiled against the old declaration gets a linker error instead of a silent behavioral change.This is the paper's answer to the cross-TU consistency problem that P2900 punts to the build system. If your contract changes, you must recompile everything that calls the function. The linker enforces this mechanically.
Two concerns though:
1. Mangling arbitrary expressions is not trivial. GCC already does this for concept constraints, but contract predicates can be significantly more complex than concept requirements.
2. This is marked as QoI (quality of implementation), not normative. An implementation that doesn't mangle - and therefore doesn't detect stale callers - is conforming. That undermines the safety story.
Intentional ABI break as a safety net. That's... actually refreshing? After years of the committee treating ABI stability as an immovable constraint, here's a paper that says "breaking ABI is the feature, not the bug."
I've been burned by exactly this scenario - contract change in a library header, stale caller compiled against the old declaration, silent misbehavior at runtime. A linker error would have saved me a week of debugging.
GCC already mangles constraint expressions for concepts (the Itanium ABI addendum covers this). The machinery exists. The question is whether the committee is willing to make it normative rather than leaving it as QoI.
entry_cond?return_cond? Who named these, a Java developer?I thought we were going with
pre/post/assertlike normal people.At least it's not
[[expects: x >= 0]]. My IDE still hasn't recovered from C++20's brief flirtation with contract attributes.And the bikeshedding has officially commenced. I give it three meetings before someone proposes
requireas a compromise, which will then conflict with a concepts library from 2018.The "no constification" decision is doing a lot of heavy lifting here. In P2900, contract predicates undergo a const transformation - the predicate is evaluated as-if the parameters were
const. This paper explicitly rejects that.Which means:
This is legal, and the logging side effect happens on every call. Under P2900 with constification, you'd need
const-callable stream operations for the same thing.I concede this is more flexible. But it means contract predicates become a side-channel for arbitrary computation, and "guaranteed enforcement" means "this code definitely runs on every call, including through function pointers." That's a lot of power and a lot of surface area for misuse.
This is intentional. The paper explicitly calls out no-constification as a difference from P2900. The rationale is that constification adds its own complexity (what's
const? what aboutmutablemembers?), and guaranteed-enforced predicates are supposed to be simple and predictable. The side effects are the feature - you're supposed to be able to log, collect telemetry, or instrument in a guaranteed-enforced check.I don't even care about the technical details anymore. Contracts have been "almost done" since 2018. P0542, P2388, P2900, now P3911 and P4005. At what point do we admit the committee is structurally incapable of shipping a contract facility without immediately needing a sequel?
Contracts have been discussed for longer than some of my junior devs have been programming. Well, programming C++. They've been alive longer. I think.
P2900 is in C++26. It shipped. The non-unanimous plenary vote was dramatic but it's in the working draft. P4005 is about adding a stronger tier on top - this is the next phase, not the same phase replaying.
[removed by moderator]
What did they say?
Crypto spam. Rule 3.
Did anyone else notice this paper says absolutely nothing about virtual functions?
The paper says entry_cond is checked "whenever a function is called" and emphasizes "Not Even for Indirect Calls." Great. But what about:
Which
entry_condapplies when you call through aBase*? Liskov says the derived class can weaken preconditions. But "guaranteed enforcement" suggests the declared condition must always hold. These are in tension and the paper doesn't address it.P3911 at least acknowledges the inheritance question.
P3911 gestures at inheritance but doesn't fully resolve it either. The virtual dispatch + contracts interaction has been unsolved since Eiffel. "Ill-formed to override with a different
entry_cond" is one approach, but it's restrictive enough to limit the feature's usefulness in polymorphic codebases.committee gonna committee
In embedded we call this "we've tested all the components individually but never powered on the assembled board." The integration is where the surprises live.
I don't doubt that always-enforced assertions exist (every
assertin release mode is one). I don't doubt that expression mangling works (concepts proved that). But the combination of guaranteed enforcement + new ODR rules + no constification + exception passthrough + mangled signatures is a system, and systems have emergent behavior that unit testing the parts doesn't reveal.For what it's worth, in my domain (bare-metal ARM) I want guaranteed enforcement badly. We can't afford the "maybe the assertion is compiled out" lottery that GSL's
Expectsgives us in release builds. But I want to see a prototype before we commit to keywords that can never be un-added.At least he's upfront about it, unlike some proposals that claim "implementation experience" from a fork that nobody outside the author's team has used.
The "constituent bits" line is doing yeoman's work in that paragraph. "We've implemented parts 1 through 4 separately, never together, but trust us - the integration will be fine." Where have I heard that before.
Can someone please just ship networking before we finish contracts season 4?
Networking was sacrificed so we could have this argument. Be grateful.
mandatory_assertsounds like what I tell myself every morning about going to the gym. Guaranteed enforcement. Never ignored. Still doesn't happen.No formal wording though? Just "pseudo-specification"? For an EWG paper?
It's an R0 targeting EWG direction, not a wording paper. This is how the process works. Wording comes after EWG says "yes, pursue this direction."
Just realized we now have P2900 (configurable contracts), P3911 (non-ignorable contracts within P2900), and P4005 (non-ignorable contracts outside P2900). Three proposals solving the same problem with different tradeoffs. EWG is going to love this.