r/wg21
P4005R0 - A proposal for guaranteed-(quick-)enforced contracts WG21
Posted by u/evolution_watcher_24 · 8 hr. ago

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.

▲ 287 points (89% upvoted) · 42 comments
sorted by: best
u/AutoModerator 1 point 8 hr. ago pinned comment

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.

u/senior_segfault_enjoyer 156 points 7 hr. ago

Oh great, another contracts paper. We're at P4005 now? The paper numbers alone tell you how many trees have died for this feature.

u/daily_template_wizard 89 points 7 hr. ago

Contracts for contracts for C++. Inception, but the spinning top never stops.

u/compile_time_victims_club 67 points 6 hr. ago

I've been tracking contracts papers since P0542. I've watched them go through more revisions than my marriage went through counselors.

u/sg21_survivor 94 points 7 hr. ago
It is ill-formed to mix guaranteed-enforced assertions and P2900 assertions in the same function declaration. That is the most future-compatible approach given the C++26 time frame.

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 pre vs entry_cond. This is not composition - it's fragmentation.

u/contracts_optimist_2025 42 points 6 hr. ago

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.

u/sg21_survivor 38 points 5 hr. ago

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?

u/contracts_optimist_2025 29 points 4 hr. ago

Counterpoint: P3911's pre! occupies the same syntactic namespace as pre, 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.

u/sg21_survivor 23 points 3 hr. ago

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.

u/just_use_rust_already 12 points 7 hr. ago

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.

u/not_a_real_compiler_dev 203 points 6 hr. ago 🏆

Sir, this is a Wendy's. And the Wendy's has 40 years of deployed code that isn't getting rewritten.

u/actually_reads_papers 312 points 6 hr. ago 🏆🏆
the selection mechanism for which evaluation semantic is used for any such guaranteed-enforced assertion is implementation-defined

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) or quick_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_enforce by 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 ignore is 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_enforce has the same property. The difference is that P4005 can never fall back to ignore, which is the real safety guarantee. The handler issue is orthogonal. I'm nitpicking the marketing, not the mechanism.

u/yet_another_cpp_dev 67 points 5 hr. ago

Wait, quick_enforce can 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?

u/linker_error_enthusiast 89 points 4 hr. ago

That's essentially what quick_enforce is - 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.

u/linker_error_enthusiast 78 points 6 hr. ago

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 to entry_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.

u/boost_contributor_emeritus 45 points 5 hr. ago

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.

u/linker_error_enthusiast 56 points 4 hr. ago

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.

u/constexpr_everything_42 124 points 6 hr. ago

entry_cond? return_cond? Who named these, a Java developer?

I thought we were going with pre/post/assert like normal people.

u/daily_template_wizard 167 points 5 hr. ago

At least it's not [[expects: x >= 0]]. My IDE still hasn't recovered from C++20's brief flirtation with contract attributes.

u/former_committee_lurker 23 points 5 hr. ago

And the bikeshedding has officially commenced. I give it three meetings before someone proposes require as a compromise, which will then conflict with a concepts library from 2018.

u/exception_safety_nerd 67 points 5 hr. ago

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:

bool log_and_check(int x) {
    std::clog << "checking: " << x << "\n";
    return x >= 0;
}
void f(int x) entry_cond(log_and_check(x));

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.

u/actually_reads_papers 43 points 4 hr. ago

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 about mutable members?), 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.

u/throwaway_contracts_84729 -3 points 5 hr. ago

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?

u/senior_segfault_enjoyer 178 points 4 hr. ago

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.

u/former_committee_lurker 34 points 4 hr. ago

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.

[deleted] 5 hr. ago

[removed by moderator]

u/yet_another_cpp_dev 12 points 4 hr. ago

What did they say?

u/paper_trail_2019 1 point 4 hr. ago

Crypto spam. Rule 3.

u/virtual_dispatch_enjoyer 56 points 4 hr. ago

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:

struct Base {
    virtual void f(int x) entry_cond(x >= 0);
};
struct Derived : Base {
    void f(int x) entry_cond(x >= -10) override;
};

Which entry_cond applies when you call through a Base*? 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.

u/exception_safety_nerd 23 points 3 hr. ago

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.

u/not_a_real_compiler_dev 45 points 4 hr. ago

committee gonna committee

u/embedded_for_20_years 89 points 3 hr. ago
The full form of this proposal hasn't been implemented yet. All the constituent bits have been

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 assert in 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 Expects gives us in release builds. But I want to see a prototype before we commit to keywords that can never be un-added.

u/sg21_survivor 34 points 2 hr. ago

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.

u/linker_error_enthusiast 56 points 2 hr. ago

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.

u/compile_time_victims_club 23 points 3 hr. ago

Can someone please just ship networking before we finish contracts season 4?

u/not_a_real_compiler_dev 12 points 2 hr. ago

Networking was sacrificed so we could have this argument. Be grateful.

u/xX_undefined_behavior_Xx 201 points 3 hr. ago 🏆

mandatory_assert sounds like what I tell myself every morning about going to the gym. Guaranteed enforcement. Never ignored. Still doesn't happen.

u/constexpr_everything_42 34 points 2 hr. ago

No formal wording though? Just "pseudo-specification"? For an EWG paper?

u/boost_contributor_emeritus 12 points 1 hr. ago

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."

u/former_boost_dev 8 points 47 minutes ago

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.