Contracts are the most contested feature in C++26. The vote was 114-12-3. Stroustrup opposed the design. The committee shipped it anyway — because the alternative was shipping nothing for another three years while the safety pressure mounted.
What actually landed is useful. Here is how to use it.
Today
The three assertion kinds
P2900R14 adds three contract annotation mechanisms to C++26:
int f(int i)
pre(i >= 0) // precondition: checked before entry
post(r: r > 0) // postcondition: checked after return
{
contract_assert(i < 1000); // mid-function assertion
return i + 1;
}
pre(condition) — evaluates before the function body executes. Can reference parameters. Multiple pre specifiers are allowed; they are evaluated in order.
post(r: condition) — evaluates after the function returns. The identifier before the colon (r) names the return value. Can reference both parameters and the result.
contract_assert(condition) — replaces assert() inside function bodies. Unlike assert(), the enforcement mode is controlled by the compiler, not by NDEBUG.
All three are context-sensitive keywords (pre, post) or a full keyword (contract_assert). They appear after the function declarator, before the body:
std::string normalize(std::string_view input)
pre(!input.empty())
pre(input.size() <= max_input_size)
post(result: !result.empty())
post(result: result.size() <= input.size())
{
contract_assert(is_valid_encoding(input));
// ...
}
Lambdas work too:
auto safe_div = [](int a, int b)
pre(b != 0)
post(r: r * b == a || a % b != 0)
{
return a / b;
};
Virtual functions: contract specifiers on virtual functions are ill-formed in the C++26 MVP. The design was removed at Hagenberg (Feb 2025) because the interaction with inheritance was not yet mature. Expect this in a future standard.
First-declaration rule: contracts may only appear on the first declaration. A redeclaration must either repeat the same contracts or omit them entirely. Different contracts in different TUs is IFNDR.
The four evaluation semantics
The enforcement mode is chosen per-TU at compile time, not per-assertion in source:
| Semantic | Predicate evaluated? | Handler called? | Program terminates? | Use case |
|---|---|---|---|---|
| ignore | No | No | No | Max performance, trusted code |
| observe | Yes | Yes | No (continues) | Staging, canary, data gathering |
| enforce | Yes | Yes | Yes (after handler) | Production safety net |
| quick_enforce | Yes | No | Yes (immediate trap) | Hard real-time, fail-fast |
- ignore: zero runtime cost. The predicate must still type-check. Use this for code paths where you trust the caller and want no overhead.
- observe: the handler runs, then execution continues past the violation. This is the key mode for introducing contracts into existing codebases — you see violations in logs without risking false-positive crashes.
- enforce (default): the handler runs, then the program is contract-terminated via
std::terminate(). The handler can escape termination by throwing an exception (if not in anoexceptcontext). - quick_enforce: the program terminates immediately without calling the handler — typically via
__builtin_trap(). No diagnostic output. Minimal overhead. Use this for safety-critical “fail fast and reboot” scenarios where even the handler cost is unacceptable.
Compiler flags
GCC 16.1 (shipped April 30, 2026):
# Enable contracts with default enforcement (enforce)
g++ -std=c++26 -fcontracts main.cpp
# Observe mode (log violations, don't crash)
g++ -std=c++26 -fcontracts -fcontract-evaluation-semantic=observed main.cpp
# Quick-enforce mode (immediate trap)
g++ -std=c++26 -fcontracts -fcontract-evaluation-semantic=quick_enforce main.cpp
# Ignore all contracts
g++ -std=c++26 -fcontracts -fcontract-evaluation-semantic=ignored main.cpp
Note: GCC uses ignored and observed (past tense) in its flag values.
Clang (experimental fork): The arcosuc3m/clang-contracts fork implements P2900. Available on Compiler Explorer:
clang++ -std=c++23 -fcontracts -fcontract-evaluation-semantic=enforce -stdlib=libc++ main.cpp
MSVC: not implemented as of VS 2026 (v18.0). No public timeline.
The replaceable violation handler
When a contract is violated under observe or enforce, the implementation calls:
void handle_contract_violation(
const std::contracts::contract_violation& v);
The contract_violation object provides:
| Member | Returns | Description |
|---|---|---|
kind() | assertion_kind | pre, post, or assert |
semantic() | evaluation_semantic | Which mode was active |
location() | std::source_location | File, line, function |
comment() | string-like | Text of the predicate (impl-defined) |
detection_mode() | detection_mode | predicate_false or evaluation_exception |
evaluation_exception() | std::exception_ptr | If the predicate threw |
Replace the handler like you replace operator new — define your own version:
#include <contracts>
#include <print>
void handle_contract_violation(
const std::contracts::contract_violation& v)
{
std::println(stderr, "[CONTRACT] {} violated at {}:{} predicate: {}",
v.kind() == std::contracts::assertion_kind::pre ? "pre" :
v.kind() == std::contracts::assertion_kind::post ? "post" : "assert",
v.location().file_name(),
v.location().line(),
v.comment());
}
Under observe, the handler returns and execution continues. Under enforce, the handler returns and std::terminate() is called. Under quick_enforce, the handler is never called.
Practical patterns
Module boundaries — put contracts on public API functions:
// public_api.h
namespace mylib {
std::expected<User, Error> parse_user(std::string_view json)
pre(!json.empty())
pre(json.size() <= max_json_size)
post(r: r.has_value() || r.error() != Error::none);
void save_user(const User& u, Database& db)
pre(u.id > 0)
pre(db.is_connected());
} // namespace mylib
Serialization — validate parsed values before struct population:
template <typename T>
T deserialize(std::span<const std::byte> data)
pre(data.size() >= sizeof(uint32_t))
post(r: r.is_valid())
{
auto header = read_header(data);
contract_assert(header.version <= current_version);
contract_assert(header.payload_size <= data.size() - sizeof(header));
return decode<T>(data.subspan(sizeof(header)));
}
FFI boundaries — catch invalid arguments before they cross into C or system calls:
int safe_write(int fd, const void* buf, size_t count)
pre(fd >= 0)
pre(buf != nullptr || count == 0)
pre(count <= max_write_size)
{
return ::write(fd, buf, count);
}
Contracts as fuzz oracles — the contract IS the test oracle:
// Step 1: annotate the function
Json parse_json(std::string_view input)
pre(input.size() <= max_input)
post(r: r.is_well_formed());
// Step 2: write the fuzz harness (no oracle needed!)
extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) {
auto input = std::string_view(reinterpret_cast<const char*>(data), size);
try {
auto result = parse_json(input); // contracts check automatically
} catch (...) {}
return 0;
}
Compile with -fcontract-evaluation-semantic=enforce. Any contract violation signals a bug — the fuzzer catches it as a crash. No separate oracle, no manual property assertions. This pattern composes directly with the fuzzing workflows in testing-for-safety-2026.
Migration strategy
Contracts reward a staged rollout:
- observe in staging: compile your test suite and canary deployments with
-fcontract-evaluation-semantic=observed. Violations are logged but don’t crash. Gather data on which contracts fire. - Triage: fix real bugs found by violations. Weaken contracts that are too strict (false positives from edge cases you intentionally allow). Remove contracts that are wrong.
- enforce in production: once the violation rate is zero in staging, flip to
enforce. Now violations crash the program — which is correct, because you’ve confirmed they indicate real bugs. - quick_enforce for hot paths: after months of zero violations, switch latency-critical paths to
quick_enforceto eliminate even the handler overhead.
This is the same pattern that Google used for hardened-stdlib rollout (see hardened-stdlib): observe first, enforce after confidence. The difference is that contracts are user-written checks at your API boundaries, not library-internal checks.
Reflection today (C++26)
Can reflection see contracts?
No. P2996 (Reflection) does not currently provide the ability to introspect contract specifiers on functions. std::meta::nonstatic_data_members_of walks struct fields; there is no std::meta::contracts_of or similar facility. Contract specifiers are not part of the type system — they are properties of declarations, and P2996’s MVP focuses on type-level reflection.
This means you cannot write a reflection-driven “verify all public functions have contracts” lint today. The check must be done with external tooling (clang-tidy rules, code review).
How contracts compose with reflection patterns
Even without reflecting contracts themselves, contracts and reflection compose well at the application level:
Reflection-generated code gets contracts for free. The serialization and deserialization functions from posts 8-11 generate field-walking code at compile time. Adding a pre() or post() to the generated function’s declaration checks invariants on every reflected field automatically:
template <typename T>
std::string to_json(const T& obj)
post(r: !r.empty())
post(r: r.front() == '{')
{
// reflection-generated field walk
}
Contracts complement sanitizers. The cost hierarchy is:
| Mechanism | Runtime cost | What it catches |
|---|---|---|
Contracts (enforce) | Near-zero (branch + trap) | Violated specifications |
| Hardened stdlib (P3471) | ~0.3% | OOB, null deref, overruns |
| ASan | 2-3x slowdown | Heap/stack buffer overflow, UAF |
| UBSan | 1.2-1.5x slowdown | Signed overflow, alignment, shift |
Contracts are the cheapest runtime safety mechanism. They belong in production; sanitizers belong in CI. See sanitizers-2026 for the full matrix.
Where this is heading
C++29 directions
Virtual function contracts were removed from the C++26 MVP but remain a priority. The core question — whether a derived class can weaken a precondition or strengthen a postcondition (Liskov substitution) — needs a design that composes with multiple inheritance. Expect a proposal for C++29.
Contracts + profiles. The profiles framework (P3589, Dos Reis) envisions profile attributes as a layer on top of contracts. A [[profiles::enforce(type_safety)]] attribute would generate implicit contracts for type-safety violations. When profiles ship (targeting C++29), contracts become their enforcement substrate.
Contracts + code injection (P3294). Token injection could auto-generate contracts from annotations:
// hypothetical C++29
template <typename T>
consteval void inject_range_contracts(std::meta::info fn) {
for (auto param : std::meta::parameters_of(fn)) {
if (has_annotation<bounded>(param)) {
auto [lo, hi] = annotation_of<bounded>(param);
// inject: pre(param >= lo && param <= hi)
}
}
}
This is speculative — P3294 has no shipping compiler — but the direction is clear: reflection observes, injection generates, and contracts enforce. The three features compose into a safety stack that C++26 started and C++29 will complete.
The bigger picture
Contracts are one layer in the C++26 safety stack:
| Layer | Standard | Status |
|---|---|---|
| Contracts (P2900) | C++26 | Shipped, GCC 16.1 |
| Hardened stdlib (P3471) | C++26 | Shipped, GCC/Clang/MSVC |
| Erroneous behavior | C++26 | Shipped (no more UB for uninit reads) |
| Profiles | C++29 target | SG23 framework at Brno (June 2026) |
| Code injection (P3294) | C++29 target | No shipping compiler |
The migration path: contracts today (user-written, at API boundaries), profiles tomorrow (compiler-generated, at type-system boundaries), injection eventually (reflection-driven, at annotation boundaries). Each layer is independently useful; together they close the gap that the safety state-of-the-union documented.
References: P2900R14 Contracts for C++, P3321R0 Contracts Interaction With Tooling, GCC 16.1 contracts flags, clang-contracts fork, Timur Doumler — Contracts Explained in 5 Minutes, Andreas Mueller — Four Practical Use Cases, cppreference — Contract assertions.