The C++26 draft just became the first C++ draft to fail its ISO ballot, and contracts are the likely reason. Our view: contracts are worth keeping, and the objection that matters to anyone who ships software is not the syntax. It is that the same check can be built to do nothing. We compiled one postcondition four ways this morning. Two of the four builds exited 0 on a bug the check was written to catch, and one of those never said a word.
What happened
The Draft International Standard ballot closed on 23 October. Mateusz Pusz's write-up has the numbers: 12 of 20 participating members voted yes, where 14 were needed, and 9 of 22 voted no, against a limit of 25%. Either count alone would have sunk it. Both objections raised at the last plenary concerned contracts, and Pusz expects the nine No votes do too. The formal comments are due by mid-January 2027.
The top Hacker News comment put it plainly: "ISO for the very first time did not approve a proposed C++ standard" (weinzierl).
What a contract is
A contract is a condition written on the function itself. pre states what must hold when the function is called, post what must hold when it returns, and contract_assert checks a condition mid-body. The compiler, not a test file, carries the check.
The part under fire is how a check runs. cppreference lists four evaluation semantics: ignore, observe, enforce and quick-enforce. Which one applies is "implementation-defined", and it "can differ for different evaluations of the same contract assertion" (cppreference).
What we ran
One function, one bug, four builds. charge should charge an order once, and its postcondition says so. The bug charges twice when the total is over $42.00:
#include <cstdio>
int charges = 0;
void charge(int cents) pre(cents > 0) post(charges == 1) {
charges += 1;
if (cents > 4200) charges += 1; // the bug: a split total charges twice
}
int main() {
charge(4899);
std::printf("reached end, charges = %d\n", charges);
}
We built it with x86-64 GCC 16.1 on Compiler Explorer, -std=c++26 -fcontracts, changing only -fcontract-evaluation-semantic (run it yourself).
| Semantic | Exit code | What the program printed |
|---|---|---|
ignore | 0 | reached end, charges = 2 |
observe | 0 | the violation, with file, line and charges == 1, then reached end, charges = 2 |
enforce | 134 | the violation, then terminate called, SIGABRT |
quick_enforce | 134 | terminate called, SIGABRT, and no violation message |
Show the observe output
contract violation in function void charge(int) at /app/example.cpp:5: charges == 1 [assertion_kind: post, semantic: observe, mode: predicate_false, terminating: no] reached end, charges = 2
Same source, same bug. The ignore build behaves as if the contract were never written. The observe build finds the bug, says so on stderr, and exits 0, so a CI step that reads the exit code goes green. quick_enforce stops the bad run but does not say why.
Why this matters past C++
The thread split along the same line. qalmakka called the design "half-baked" and worried about "a feature that's not consistent among compilers, targets" (comment). rurban defended it: a check that is "either static or runtime, which the compiler can decide" beats hand-written asserts that "are either unnecessary or costly" (comment).
Both are right, and the flexibility is the point of the design: you want checks on in a test build and maybe off in a hot loop. The cost is that "the contracts passed" stops being a complete sentence. It needs a second half: under which semantic.
Test suites already have this problem under other names. A retried test that passes on its third try is an observe build. A healed selector is an observe build that rewrote the condition. A check skipped in CI because it was slow is an ignore build. Each one turns a verdict into "the run finished", and the exit code cannot tell the difference.
We made a related point about proofs earlier today: Hacker News asked "proved against what?". Contracts add a quieter question: checked under what?
What to do with it
If you write C++ and your toolchain ships contracts, build your test configuration with enforce, and fail CI on any violation line in an observe build, not just on the exit code. Record the semantic in the build log.
For any other check you rely on, ask the same thing. When a run goes green, can you tell whether the check ran, ran and passed, or ran, failed and was told to continue? If the answer is "the exit code", two of our four builds would have shipped the double charge.
Sources
- 01train-it (Mateusz Pusz): C++26 contracts, the ballot resulttrain-it.eu
- 02Hacker News: discussion threadnews.ycombinator.com
- 03Hacker News: weinzierl, the short versionnews.ycombinator.com
- 04Hacker News: qalmakka, on consistency across compilersnews.ycombinator.com
- 05Hacker News: rurban, in favour of this designnews.ycombinator.com
- 06cppreference: Contract assertionsen.cppreference.com
- 07Compiler Explorer: the postcondition we ran, GCC 16.1godbolt.org
// The agent clicked "Pay". Did the right things actually happen?
Example from the README
Your agent says it works. Reticle checks the network, state and console inside your running app, and names the line to fix.
85 of 88 seeded bugs caught in the public benchmark