standards_digest_2025 · 14 hr. ago
P4009R0 - A proposal for solving all of the contracts concerns WG21
Posted by u/standards_digest_2025 · 14 hr. ago

Document: P4009R0 · Author: Ville Voutilainen · Date: 2026-02-09 · Audience: EWG

Ville Voutilainen drops a grenade on the C++26 contracts timeline. The proposal rewrites the contracts surface: bare pre(cond) means guaranteed enforce - no configuration can turn it off - while pre(std::pre(cond)) wraps the predicate in a library function that queries a configuration mechanism to pick between enforce, observe, quick_enforce, or ignore. The core language gets only two evaluation semantics: enforce and ignore. All the P2900 complexity moves to library functions.

The motivation is blunt: the Romanian NB comment objects to the lack of guaranteed enforcement, several vendors and prominent members are unhappy, and Voutilainen argues the committee is "at war" with its own constituency by pushing P2900 through as-is. The paper also punts constification to C++29 and admits the implementation-defined configuration mechanism won't work across modules.

Bold move at this stage of C++26. Whether you think this is a reasonable design alternative or DIS-phase arson, it's worth reading.

▲ 347 points (84% upvoted) · 50 comments
sorted by: best
u/AutoModerator 1 point 14 hr. ago pinned comment

Paper information for P4009R0:

Title: A proposal for solving all of the contracts concerns
Author: Ville Voutilainen
Document: P4009R0
Date: 2026-02-09
Audience: EWG

I am a bot, and this action was performed automatically. Please contact the moderators if you have any questions or concerns.

u/throwaway_83721 267 points 13 hr. ago 🏆

we're going to end up with pre(std::pre(std::pre(pre(x)))) and call it progress

u/linker_error_420 87 points 13 hr. ago

std::pre(std::pre(std::pre(... recursive template instantiation depth exceeded. Contracts for C++ has been successfully optimized to C++ for Contracts.

u/build_system_victim 34 points 12 hr. ago

compile-time contracts: the assertion is that your build finishes before the heat death of the universe

u/pre_and_post_mortem 234 points 11 hr. ago 🏆 🏆

Did anyone else actually trace through the evaluation model here?

pre(std::pre(x >= 0))

When the configuration says "ignore," std::pre() returns std::ignore. The language pre keyword sees the return type is std::ignore_t and uses the "ignore" semantic. Sounds clean.

But std::pre(x >= 0) is a function call. x >= 0 is a function argument. It's evaluated before std::pre runs, regardless of what the configuration says.

The entire point of P2900's "ignore" semantic is that the predicate is not evaluated. You have an expensive invariant check like is_sorted(vec.begin(), vec.end()) and in production, ignore means don't pay for it. With this design, you always pay.

The paper says "QoI will do what's expected there" for the N-evaluation question, but the evaluation-on-ignore issue isn't QoI - it's a fundamental consequence of the library approach. Unless std::pre is compiler magic masquerading as a library function, which kind of defeats the purpose of making it "library-geared."

This is the load-bearing claim of the paper and I don't think it holds weight.

u/library_design_nerd 89 points 10 hr. ago

This is the key insight. The paper explicitly says the goal is to "avoid lambdas as a mechanism for deferring evaluation" - but it doesn't actually defer evaluation. It just changes what happens after evaluation. That's a categorically different thing.

P2900's ignore: predicate is not evaluated.
P4009's ignore: predicate is evaluated, result is discarded.

These have very different performance profiles for anything beyond trivial boolean checks.

u/pre_and_post_mortem 43 points 10 hr. ago

Right. And it's not just performance. If your predicate has observable side effects - logging, metrics, whatever - P2900-ignore is silent. P4009-ignore still triggers them. That's a semantic difference that library authors care about.

u/implementer_at_large 67 points 9 hr. ago

"QoI will do what's expected there" is doing more heavy lifting in this paper than std::ignore is.

u/actually_read_the_paper 52 points 9 hr. ago

To steelman the approach: the paper's position is that genuinely expensive predicates should use the configurable form, and std::pre() as a library function could be implemented with compiler support that elides evaluation. The paper says "implementation-defined configuration mechanism" - that gives compilers room to make std::pre special.

Not saying it's convincing, but "always evaluates" isn't necessarily the design intent - it's the consequence if you read std::pre as a normal function. An implementation could treat it as a builtin.

u/skill_issue_2026 203 points 13 hr. ago

pre(std::pre(x >= 0)) is the "simpler" syntax?

Am I reading this right?

u/yet_another_cpp_dev 178 points 12 hr. ago

It's simpler in the same way that C++ is a simple language

u/actually_read_the_paper 61 points 11 hr. ago

To be fair, bare pre(x >= 0) is still available and means guaranteed enforce. The std::pre() wrapping is only for configurable semantics. So the simplest form is simple.

The framing as "simpler overall" is a stretch though.

u/compiles_first_try 92 points 11 hr. ago

finally, a standard that values job security for those of us who explain contract semantics in code review

u/standards_process_cynic 156 points 12 hr. ago

So let me get this straight.

We removed contracts from C++20. Spent 5 years redesigning them in SG21. Got P2900 into C++26. And now, in the DIS phase, we're considering tearing it up again?

Is this a standards body or a Sisyphus cosplay?

u/not_on_the_committee 48 points 11 hr. ago

"extraordinary situation" is doing a lot of heavy lifting in that paper

u/committee_process_watcher 72 points 10 hr. ago

Dismissing it as process theatrics misses the point. The Romanian NB comment is real and it has formal weight. There are national bodies who have said "we cannot use this feature as designed." That's not a Reddit complaint - it's a ballot-level objection with teeth.

Whether P4009 is the right response is debatable. Whether the concern it addresses is legitimate is not.

u/yet_another_cpp_dev 19 points 9 hr. ago

Genuine question: has an NB comment ever actually killed a C++ feature at the DIS stage?

u/teaching_track 89 points 9 hr. ago

From a teaching perspective, I currently explain pre(cond) as "this function requires cond to be true." One sentence. Students get it.

Explaining pre(std::pre(cond)) means I have to cover:

1. The language keyword pre
2. A library function std::pre() that queries a configuration mechanism
3. That configuration mechanism picks a semantic (enforce, observe, ignore, quick_enforce)
4. Based on the semantic, std::pre() returns either a bool or std::ignore
5. The language keyword interprets the return type to decide what to do

That's five abstraction layers for what should be a one-layer concept. Even if students only use the bare form, they'll encounter std::pre() in every library and every blog post within a month of the feature shipping.

u/constexpr_or_die 143 points 8 hr. ago

the real contract is the friends we confused along the way

u/enterprise_assertions_guy 37 points 8 hr. ago

Your students should be using bare pre(cond) then. That form is still there, still simple. The std::pre() wrapping exists for organizations that need configurable semantics. Different audiences, different needs.

Not everything in the standard needs to be teachable to first-year students.

u/teaching_track 52 points 7 hr. ago

In theory, sure. In practice, "best practices" guides will standardize on std::pre() because it's the flexible form. The first Stack Overflow answer will say "always use std::pre() for production code." And now every student encounters the complex form first.

We've seen this with auto and trailing return types. The simple form exists, but the internet decided the complex form is "correct."

u/not_a_real_language_lawyer 87 points 13 hr. ago

every time I think contracts is done, another paper drops that says "actually, have we considered..."

u/constexpr_or_die 142 points 12 hr. ago

the contracts cinematic universe at this point has more sequels than fast and furious

u/daily_template_wizard 56 points 12 hr. ago

P2900 is like a TV show that keeps getting renewed when everyone thought the series finale already aired

u/compiler_person_42 78 points 8 hr. ago

Speaking as someone who works on a major compiler toolchain: the "implementations are experimental anyway" argument in this paper cuts both ways.

Yes, P2900 implementations aren't production-hardened yet. But we've invested real engineering effort implementing P2900's four evaluation semantics, its interaction with constant evaluation, the violation handler model. "Just change the design" isn't free even for experimental code.

More concerning is this:

the suggested implementation-defined facility will not work across modules or across TUs

That's not a minor caveat. If the feature can't work across the boundaries that C++20 introduced as a headline feature, you're shipping a known incompatibility.

u/former_committee_intern 27 points 7 hr. ago

this is why we can't have nice things

u/the_real_assertion 34 points 7 hr. ago

The implementation cost argument is overstated. The core language change here is small: two semantics (enforce, ignore) instead of four. That's less to implement, not more. The library part is new work, but library work is inherently lower-risk than core language machinery.

And the module point - yes, the paper acknowledges a limitation. But P2900's interaction with modules isn't exactly solved either. Contract checking across module boundaries has open questions in both designs.

u/compiler_person_42 52 points 6 hr. ago

Lower risk? Look at the diagnostic quality story.

The paper proposes source_location as a fallback for the expression text that current contract assertions display. source_location gives you file and line. It does not give you x >= 0 && x < size().

The paper's answer is to invent a future "facility that captures code in a nearby context in text form." That's not a solution. That's a TODO comment dressed in committee language.

You're asking implementers to ship something with degraded diagnostics now, on the promise of a fix later. That's the kind of thing that makes compiler teams nervous.

u/the_real_assertion 28 points 5 hr. ago

The diagnostic quality concern is legitimate. I'll give you that.

But is your position that we should adopt a more complex language mechanism with four configurable semantics - because the simpler one loses expression text in diagnostics? That's an engineering tradeoff, not a showstopper. Especially since the paper explicitly outlines how implementations can solve it with their own mechanisms today.

u/compiler_person_42 41 points 5 hr. ago

I'll concede the core language delta is small. Two semantics is simpler than four, full stop.

But the total system - core language + library functions + implementation-defined configuration + the future diagnostic facility + the future constification keyword + the module interop story - that's not a small delta. That's spreading the complexity across four features and three release cycles, and hoping they all land.

I'd rather have one feature that's self-contained in C++26, even if it's more complex at the language level, than a design that requires C++29 to be complete.

u/definitely_not_a_delegate 71 points 7 hr. ago

I want to make a distinction that keeps getting lost in these threads.

The Romanian NB comment isn't "contracts bad." It's specifically about the lack of guaranteed enforcement - the ability to say "this contract MUST be checked, always, no build configuration can override this, and the program terminates if it fails."

P2900 has enforce, but any build can configure it away. Bare pre(cond) in this proposal IS guaranteed enforce - nothing can turn it off. That directly addresses the NB concern.

Whether the rest of the P4009 apparatus is worth it is a separate question. But on the specific NB complaint, this paper has an answer that P2900 currently does not.

u/standards_process_cynic 93 points 6 hr. ago

So the solution to "we want guaranteed enforcement" is an entire design overhaul that also changes configurable enforcement, punts constification, breaks diagnostics, and admits it won't work with modules?

That's using a nuclear submarine to deliver a pizza.

u/contracts_watcher 39 points 6 hr. ago

This is exactly right. And it raises the obvious question: could you solve the NB comment with a much smaller change? Add a guaranteed-enforce annotation to P2900 - something like pre enforce(cond) that can't be configured away - and leave everything else as is.

P4009 bundles a targeted fix for the NB concern with a wholesale redesign. The bundling is the problem.

u/enterprise_assertions_guy 67 points 10 hr. ago

I work at a large financial institution. We have exactly the kind of custom assertion macros the paper describes. Our entire codebase uses OUR_PRECONDITION(cond) which expands to different checking behavior based on build type, component level, and runtime configuration.

pre(our::precondition(cond)) is genuinely how we'd want to use a standardized contracts facility. The ability to plug our existing assertion infrastructure into language-level contracts without replacing it is appealing.

For shops that already have assertion frameworks like BDE or Abseil CHECK macros, this is a natural fit.

Edit: To be clear, I'm not saying the syntax is pretty. I'm saying the model - language keyword wrapping a library call - maps cleanly to what enterprise codebases actually look like.

u/daily_template_wizard 44 points 9 hr. ago

But that's the thing - you already have custom macros. This proposal makes the standard syntax longer for everyone else so that shops with existing infra can plug in marginally more cleanly. That's optimizing for the minority case.

u/contracts_watcher 123 points 9 hr. ago 🏆

The BSLS-prefix is completely fictional. The naming style used in it is the work of the author's imagination. Any resemblance to actual naming schemes is entirely coincidental.

Yes Ville, we believe you. Completely fictional. Absolutely no resemblance to any particular financial institution's coding standards whatsoever. None at all.

u/constification_matters 58 points 6 hr. ago

The constification punt is getting less attention than it deserves.

P2900 makes preconditions const-evaluable - contracts can be checked at compile time. This is the path toward static contract verification, which is arguably the most valuable long-term capability contracts could provide.

This paper says "let's just not do constification" and instead proposes a standalone constify() keyword in C++29. We're trading a capability we have now for a promise of a different capability three years later.

And constify() as described is a significant language feature on its own. Deferring it to C++29 means it might not land until C++32 if scope creep hits. We've seen that pattern before.

u/pre_and_post_mortem 29 points 5 hr. ago

Worth noting that constification in P2900 wasn't exactly uncontroversial either. Several committee members felt it was too restrictive. But removing it entirely and promising a replacement is a hard sell when the replacement doesn't exist yet.

u/former_boost_contributor_42 45 points 5 hr. ago

The extensibility story is actually interesting, even if the syntax makes me wince.

Right now, if you want custom assertion semantics, you're completely outside the language facility. You use macros. They don't interact with the language's contract checking at all - the compiler can't reason about them, tools can't analyze them, Boost.Contract does its thing entirely at library level.

With pre(my_custom_function(cond)), the language pre keyword knows about your custom assertion. It participates in the contract model. That's new. The paper is right that this is "the simplest and most familiar extension mechanism" - it's just a function call.

The question is whether the costs (evaluation-on-ignore, diagnostic quality loss, deferred constification, module limitations) are worth that extensibility.

u/library_design_nerd 67 points 4 hr. ago

At what cost though?

You lose diagnostic quality (source_location is not x >= 0 && x < size()). You lose the ability to skip evaluation. You gain the ability to call a function - which, as you note, we can already do with macros.

The paper's answer for diagnostics is to eventually "introduce a new facility that captures code in a nearby context in text form." That's vaporware. It doesn't exist, isn't proposed, and has no timeline.

u/former_boost_contributor_42 31 points 4 hr. ago

You're not wrong. The diagnostic quality story is the real achilles heel. source_location gives you foo.cpp:42 when what you want is x >= 0 && x < container.size(). The paper openly acknowledges this and punts it.

a new facility that simply captures code in a nearby context in text form

"Simply." Right.

u/definitely_knows_templates 44 points 3 hr. ago

I cannot wait for the blog post titled "pre(std::pre(cond)) considered harmful"

u/segfault_enjoyer 33 points 4 hr. ago

laughs in undefined behavior

you guys are getting contracts?

u/move_semantics_enjoyer 18 points 3 hr. ago

we've been negotiating the terms of the contract since 2015

u/coroutine_hater_9000 21 points 2 hr. ago

great, another paper that will take 10 years to get through EWG. by the time contracts actually ship, I'll be writing Rust in a retirement home and complaining about borrow checker ergonomics

[deleted] -3 points 3 hr. ago

[removed by moderator]

u/turbo_llama_9000 5 points 2 hr. ago

what did they say?

u/coroutine_hater_9000 11 points 2 hr. ago

some crypto link about "decentralized contract verification" or something. you know, the usual