- Published on
C++26 Contracts — the feature that passed 114 to 12, and the one implementation that shipped it: GCC 16.1
- Authors

- Name
- Youngju Kim
- @fjvbn20031
- Introduction — C++26 Passed, But Not Unanimously
- What Contracts Are — the 3-Minute Version
- The Core Design — Source Code Doesn't Decide Whether a Check Runs
- The Opposing Argument — One Inline Function in a Header
- The Rebuttal — Functional Safety and Language Safety Are Different Things
- What Got Fixed at Kona, and What Didn't Make It In
- The Compiler Reality — One: GCC 16.1
- So, When Should You Use It — and When Shouldn't You
- Closing
- References
Introduction — C++26 Passed, But Not Unanimously
On Saturday, March 28, 2026, in Croydon, London, the ISO C++ committee finished the technical work on C++26. After working through the last of the 411 National Body (NB) comments that came out of the summer CD (Committee Draft) international ballot, the committee finalized the document to be sent to the final approval vote (DIS).
But the final plenary vote was not unanimous.
114 in favor, 12 against, 3 abstentions
In his Croydon trip report, Herb Sutter wrote that this lack of unanimity was "mainly due to continuing concerns about contracts." There's a point of comparison. When contracts were added to the C++26 working draft in February 2025 in Hagenberg, the vote was 100 in favor, 14 against, 12 abstentions. Over the year, the "against" count barely moved, from 14 to 12, but the abstentions dropped from 12 to 3. Sutter's reading: an unusually low abstention count means most experts now feel confident in their own technical judgment, whichever side they land on.
It's rare for a standard feature to still draw a double-digit "against" count this late in the process. Let's lay out what happened, and whether you can actually use this feature today.
One note before we start: this post uses Sutter's trip report strictly as the source for the vote counts. He is on the pro-contracts side (and a co-author of P3911R2), so the interpretation and framing there are his own. The arguments themselves are drawn directly from the original papers each side wrote.
What Contracts Are — the 3-Minute Version
A contract assertion is language syntax for writing a function's preconditions, postconditions, and mid-body assertions. There are three forms.
// Precondition: what the caller must uphold
int divide(int a, int b)
pre (b != 0);
// Postcondition: what the function guarantees. The return value can be named
int abs_value(int x)
post (r: r >= 0);
// An assertion inside the body
void process(std::span<int> data) {
contract_assert(!data.empty());
// ...
}
The difference from C's assert macro is clear once you line them up. assert is a preprocessor macro, so it cannot attach to a function declaration. A precondition written that way can only live in the body, invisible to a caller who only sees the header. Contracts attach to the declaration, so they become part of the interface, tools can read them, and postconditions can refer to the return value by name.
Nobody disputes any of this. The argument starts at the next point.
The Core Design — Source Code Doesn't Decide Whether a Check Runs
The C++26 draft standard's [basic.contract.eval] defines four evaluation semantics.
ignore— no effectobserve— check it, call the handler on failure, and continue execution if the handler returnsenforce— check it, call the handler on failure, and terminate execution if the handler returnsquick-enforce— check it, and terminate immediately on failure, with no handler call
observe, enforce, and quick-enforce are the checking semantics; enforce and quick-enforce are the terminating semantics. And the following sentence is what defines the character of this feature.
It is implementation-defined which evaluation semantic is used for any given evaluation of a contract assertion.
Whether a given contract assertion actually gets checked is implementation-defined. It's decided not by what's written in the source, but by the compiler and build flags. The very next note goes further still.
The evaluation semantics can differ for different evaluations of the same contract assertion, including evaluations during constant evaluation.
Even the same contract assertion can get a different semantic on different evaluations. The standard offers, as "Recommended practice," an option to translate everything as ignore and an option to translate everything as enforce, and says the default should be enforce — but that is a recommendation, not a requirement.
There's one more layer to this.
The evaluation A of a contract assertion using a checking semantic determines the value of the predicate. It is unspecified whether the predicate is evaluated.
Even under a checking semantic, whether the predicate is actually evaluated is unspecified. The standard's own example shows this directly.
struct S {
mutable int g = 5;
} s;
void f()
pre ((s.g++, false)); // #1
void g() {
f(); // even if #1 uses a checking semantic, the increment of s.g may not happen
}
If your predicate has a side effect, that side effect may or may not happen. An implementation is allowed to perform an alternative evaluation that produces the same value without the side effect.
The pro-contracts paper P3846R0 defines contracts by three properties — they are redundant to program correctness, independently checkable, and their evaluation semantic is determined independently of the source code. That third property is the intended design. It is not a bug.
The Opposing Argument — One Inline Function in a Header
The title of the opposing paper isn't subtle: P3835R0, "Contracts make C++ less safe -- full stop", by John Spicer (EDG), Ville Voutilainen, José Daniel García Sánchez, September 3, 2025. The whole argument rests on one code example.
// z.h — a third-party library header
inline void f(int x) {
contract_assert(x > 0);
}
// l1.cpp — library implementation. Compiled with quick_enforce
#include "z.h"
void g() {
f(0); // expects the contract violation to be caught
}
// c1.cpp — client code. Compiled with ignore
#include "z.h"
extern void g();
int main() {
f(1);
g();
}
The library was built so that its own code is always checked with quick_enforce. The client builds with ignore. Because f is an inline function, only one definition survives under the ODR. So which semantic ends up actually applying after linking — whether the check the library author intended survives at all — depends on whether the compiler inlined the call inside l1.cpp, and which translation unit's copy the linker picked. In the paper's own words, there is no way for the programmer to know which semantic applies to which call.
This can happen wherever a header contains an inline function or template definition with a contract assertion in it — which is to say, in every header-only library. The paper states that modules don't solve this problem either. Its conclusion:
Contracts reduce the safety of C++ and should be removed until a version that is strictly an improvement in safety is provided.
There's more in the same direction: P3829R0, "Contracts do not belong in the language" (David Chisnall, Spicer, Gabriel Dos Reis, Voutilainen, García), and P3573R0, "Contract concerns," which carries the names of Bjarne Stroustrup and David Vandevoorde. This was not a lightweight objection.
National body comments piled on too. The appendix to P3846R0, which tallies contract-related NB comments, counts 17 of them, from seven national bodies — Sweden, Spain, the US, France, Romania, Czechia, and Finland. Spain's ES-046 is summarized this way: critical checks can be removed, opening a security vulnerability that enables a new class of supply-chain attack. Several comments explicitly demanded removal from C++26.
If "supply-chain attack" sounds like an exaggeration, it's worth looking at the example P3829R0 gives. As P3846R0 summarizes it: a function is compiled with quick_enforce in one translation unit and ignore in another. When optimizing the quick_enforce side, GCC assumes the contract check is enforced and removes the caller-side null check. But at link time, the ignore version can end up being the one selected. Then there's no contract check and no null check that was removed on the assumption the contract check would catch it. Not one check gone — two. P3829R0 frames this as a design flaw in P2900 and argues that fixing it would require either (1) making mixed-mode an ODR violation, (2) a major change to compiler intermediate representations, or (3) giving up a core optimization.
There's a rebuttal to this from the pro-contracts side, and it's worth reporting fairly. P3846R0 states that EWG reviewed the mixed-mode concern at Hagenberg and declined to change the ODR for contracts, and that the GCC interprocedural-optimization problem in question was judged on the reflector to be "a compiler bug unrelated to contracts," and a reproduction case was reported to GCC. In other words, the pro-contracts side treats this as an implementation bug, not a language design flaw.
The Rebuttal — Functional Safety and Language Safety Are Different Things
The pro-contracts response is P3846R0, "C++26 Contract Assertions, Reasserted" (October 6, 2025). It's signed by 22 people led by Timur Doumler, Joshua Berne, and Gašper Ažman, and among the signatories are GCC's Jason Merrill and Jan Sando, libc++'s Louis Dionne, and Eric Fiselier, who implemented the Clang support. The paper answers 17 "concerns" one by one.
The core rebuttal starts by splitting a term in two.
- Functional safety — the likelihood that a program performs its intended function without causing harm
- Language safety — the absence of undefined behavior, or the ability to prove that absence
Contracts, the paper argues, are built for the former, not the latter. A checked assertion catches a bug; an ignored one documents intent with no runtime effect. The ability to configure the semantic externally isn't a flaw — it's a precondition for wide adoption.
Here's how the paper answers the cross-TU semantic mismatch from P3835R0's example.
Mixing translation units compiled with different build flags is an inevitable consequence of the C++ compilation model. (...) With C assert, mixed mode makes programs IFNDR, and with P2900, the behaviour is limited to one of the semantics incorporated into the program, which is a significant improvement. (...) The worst case is that an assertion will go unchecked, which by design cannot introduce a new bug into an existing program.
In other words, the worst case is a check going unchecked, and by design that cannot introduce a new bug into an existing program. The paper also states plainly that requiring a uniform semantic across the whole program would make it "a feature you cannot use at scale." A header's f might be used in a performance-critical loop, where nothing but ignore is affordable, while less-hardened caller code upstream might warrant quick_enforce — an assertion feature that can't support this variability, the argument goes, isn't useful.
The paper also notes that a proposal to change pre and post semantics so they're always checked was already voted on in Tokyo and rejected by strong consensus. Instead, it points to labels (P3400R1) as the path to expressing "must be checked," positioned as a post-C++26 extension.
Lay the two sides' claims side by side and the factual disagreement is actually narrow. Neither side disputes the behavior itself. Both agree checks can be silently dropped, and both agree there's no way to force it from the source. What they disagree on is whether that makes it "not a safety feature" or "actively unsafe."
What Got Fixed at Kona, and What Didn't Make It In
At the November 2025 Kona meeting, the committee spent Monday and Tuesday on contracts feedback. The outcome was this.
Keep contracts, but fix two bugs — reached by strong consensus. One of the adopted fixes was P3878R1, "Standard library hardening should not use the 'observe' semantic," authored by Voutilainen, Jonathan Wakely, Spicer, and Stephan T. Lavavej. This was a bug-fix paper written by people on the opposing side, and it was adopted — which is itself evidence that the opposition was technical, not political.
There's a control case worth noting. At the same meeting, trivial relocatability (P2786) had a decisive bug found that couldn't be fixed in time, and it was removed from C++26. So it isn't that the committee lacked the will to cut a feature — contracts survived on their own merits. In the same week, one feature got cut and the other kept.
And there's what didn't get in. At Kona, the committee agreed to pursue something close to the first solution in P3911R0, "[basic.contract.eval] Make Contracts Reliably Non-Ignorable," through the March meeting. In response to a Romanian NB comment (RO 2-056), the proposal was to add syntax to say, in the source code itself, "this assertion must be enforced." The proposed form looked like this.
int divide(int a, int b)
pre! (b != 0); // proposed syntax: always enforced. Did not make it into C++26
The paper reached R2 (January 14, 2026), and Andrei Alexandrescu and Herb Sutter joined as authors. But the current draft standard's grammar does not have this.
precondition-specifier:
pre attribute-specifier-seq_opt ( conditional-expression )
postcondition-specifier:
post attribute-specifier-seq_opt ( result-name-introducer_opt conditional-expression )
There is no pre! in the [dcl.contract.func] grammar, and the word "non-ignorable" appears nowhere in [basic.contract]. C++26's __cpp_contracts value stands unchanged from P2900R14's 202502L. Sutter's own Croydon report also states that "the feature was neither added nor removed."
To sum this up: C++26 gives you no way to write, in source code, that a given contract check must always run. The opposing side's core argument shipped unresolved with the standard, and that is the substance of the 12 dissenting votes.
One more gap worth flagging: C++26 contracts also cannot be attached to virtual functions. That capability, P3097R3 "Contracts for C++: virtual functions," landed in the C++29 draft instead (__cpp_contracts >= 202606L per the GCC status page), and GCC has not implemented it yet. For a feature whose whole pitch is documenting preconditions in an interface, leaving out virtual functions is a fairly large hole.
The Compiler Reality — One: GCC 16.1
This is the part that matters most in practice. On cppreference's C++26 compiler support table, the Contracts (P2900R14) row shows 16 in the GCC column, and the Clang, MSVC, Apple Clang, EDG eccp, and Intel C++ columns are all empty.
As of July 2026, GCC 16 is the only released compiler that implements C++26 contracts. GCC 16.1 shipped April 30, 2026, a month after the standard was finalized.
Here's how you use it.
# -std=c++26 alone does not turn contracts on. You need -fcontracts
g++ -std=c++26 -fcontracts main.cpp
# choosing an evaluation semantic (default is enforce)
g++ -std=c++26 -fcontracts -fcontract-evaluation-semantic=quick_enforce main.cpp
Cross-referencing the GCC 16 manual with the option definitions in the source gives this.
| Flag | Values | Default |
|---|---|---|
-fcontracts | enables the contracts feature | off |
-fcontract-evaluation-semantic= | ignore / observe / enforce / quick_enforce | enforce |
-fcontracts-client-check= | none / pre / all — insert caller-side checks | none |
-fcontracts-definition-check= | on / off — callee-side checks | on |
-fcontracts-conservative-ipa | restricts interprocedural optimization | on |
-fcontract-checks-outlined | splits checks into a separate function | off |
One small trap: the manual text writes the values as ignored and observed, but the strings the compiler actually accepts are ignore, observe, enforce, and quick_enforce (per the enum definition in GCC's source, gcc/c-family/c.opt, default Init(3) = enforce). Copy-paste from the manual and you'll get an "unrecognized contract evaluation semantic" error.
You override the violation handler by defining this function.
#include <contracts>
void handle_contract_violation(const std::contracts::contract_violation& v) {
// logging, etc.
}
The flag worth paying attention to in that table is -fcontracts-conservative-ipa. In GCC's own documented words, this flag "prevents interprocedural analysis from taking action when the body of an inline function is visible in some translation unit, since the conditions for the evaluation of contracts might vary between translation units and such action could be incorrect." And it's on by default.
This is exactly the null-check-elision scenario from P3829R0 above, made concrete. The property the opposing side flagged — that semantics can differ across TUs, so optimizing on the assumption they don't is dangerous — is embodied in GCC as a flag that conservatively restricts interprocedural optimization by default. The same idea shows up in the description of -fcontract-disable-optimized-checks, which talks about "optimizations that could unintentionally elide a check step."
Which side's evidence this counts as depends on how you read it. The opposing side reads it as "the language is offloading this burden onto the compiler." The pro-contracts side reads it as "this is a manageable implementation problem, and it's being managed." What's not in dispute is that this isn't an abstract philosophical debate — it's a thing that exists as a compiler flag turned on by default.
For what it's worth, the same GCC 16 also implements reflection (P2996R13), which needs its own separate -freflection flag. And P2795R5 (erroneous behavior), which removes the undefined behavior of reading an uninitialized value, also landed in GCC 16 — unlike contracts, that one was uncontroversial, and you get the benefit just by recompiling.
So, When Should You Use It — and When Shouldn't You
Start with when not to.
- Do not use it to validate a trust boundary. This is the most important one. User input, data coming off the network, permission checks — none of that belongs in a contract. Build with
ignoreand it vanishes; if it's in an inline function in a header, it can vanish regardless of your intent. Validate trust boundaries withifand explicit error handling. Even P3846 states that "a mechanism for non-ignorable checks already exists — for instance,ifstatements." That's not the opposing side talking. That's a sentence from the pro-contracts paper. - Do not use a predicate with side effects. Whether it evaluates at all is unspecified.
- Do not use it in code that needs to be portable. As of July 2026, this is a GCC-16-only feature. Neither Clang nor MSVC has it in a release.
- You can't use it on virtual function interfaces yet. That has to wait for C++29.
- Inside a predicate, outer variables become
constandthisbecomes aconstpointer (const-ification). A check that calls a non-const operation simply won't compile.
Where it earns its keep.
- Documenting preconditions in the interface in an internal codebase. Being able to see the contract just by reading the header is something
assertnever gave you, and that benefit survives even a build withignore. - The classic pattern of
enforcein debug/test builds andignorein release. This is doing whatassertalready did, with better syntax, and this is realistically where most early adoption will land. - In an internal service where the build is locked to a single compiler (GCC), compiling every TU with the same semantic. This is exactly what P3846 calls the "natural initial strategy" — a single semantic per TU. If you never mix modes, P3835R0's problem never arises in the first place.
In other words, contracts work fine when you control the entire build. The trouble starts the moment you pull in a third-party header-only library, and the moment a library author wants to say "my check must always be on." C++26 gives them no syntax to write that down.
Closing
To sum up: C++26 contracts survived the final vote on March 28, 2026, at 114 in favor, 12 against, 3 abstentions — the endpoint of 20 years of discussion, 5 years of work by SG21, and one prior removal back in C++20. The feature itself is real: preconditions and postconditions attached to a declaration do something assert never could, and the four evaluation semantics reflect a genuinely wide range of real-world needs.
At the same time, the opposing argument is also real. Source code cannot determine whether a check runs, and inside an inline function in a header, a programmer cannot know which semantic will actually apply. This is not a bug — it's the intended design — which is exactly why it's not the kind of thing that gets "fixed." The syntax for forcing a check from source (P3911) was pursued through March but did not make it into C++26, and labels (P3400) remain a post-C++26 item.
So the right posture toward this feature is clear. Contracts are a bug-detection tool, not a safety feature. Even the pro-contracts side says as much — built for functional safety, not language safety. Miss that distinction, read it as "C++26 makes things safe via contracts," and you'll get hurt exactly the way the opposing side warned: by putting a check that can be silently skipped on a trust boundary.
And as of July 2026, if you want to try it at all, GCC 16.1 is your only option. Sutter expects C++26 adoption to move fast, but on contracts specifically, the only thing you can do right now is experiment with GCC 16. Putting this into portable production code is a conversation for after Clang and MSVC catch up.
References
- C++26 is done! — Trip report: March 2026 ISO C++ standards meeting (London Croydon, UK) — Herb Sutter — source for the vote counts (114/12/3, 100/14/12) and the 411 NB comments
- Trip report: November 2025 ISO C++ standards meeting (Kona, USA) — Herb Sutter — the decision to keep contracts, P3878R1, the removal of trivial relocatability
- P3846R0 — C++26 Contract Assertions, Reasserted (Doumler, Berne, Ažman, and 19 others) — the pro-contracts side's response to 17 concerns, and the NB comment appendix
- P3835R0 — Contracts make C++ less safe -- full stop (Spicer, Voutilainen, Garcia) — the inline-function example
- P3829R0 — Contracts do not belong in the language
- P3911R2 — Make Contracts Reliably Non-Ignorable (Neațu, Alexandrescu, Teodorescu, Nichita, Sutter) — the
pre!proposal that did not make it into C++26 - [basic.contract.eval] — the draft standard's evaluation semantics rules
- [dcl.contract.func] — contract specifier grammar
- C++ Standards Support in GCC — Contracts P2900R14 → GCC 16,
__cpp_contracts >= 202502L - GCC 16 Release Series — Changes and GCC C++ Dialect Options — the contract-related flags
- GCC Releases — GCC 16.1 released April 30, 2026
- Compiler support for C++26 — cppreference — no support outside GCC 16