Last Update: 11 October 2026
C++26 Contracts: Introduction to Preconditions and Postconditions
In the article about Standard Library hardening, we looked at checks for invalid Standard Library container access and other broken preconditions. I mentioned that those requirements are expressed in terms of “contracts”. But what about our code?
In this post, we’ll explore contracts from C++26. We’ll start with a simple cassert, move the requirement into the function itself, and see how to check results. We’ll also look at declarations and definitions, compiler options, and a few details that can surprise you.
Starting with an Assertion
Let’s start with a small example:
A header file first:
// foo.h
#include
int get_value(const std::vector& values, std::size_t index);
And the implementation:
// foo.cpp
#include "foo.h" // our function declaration...
#include
int get_value(const std::vector& values, std::size_t index) {
assert(index & values, std::size_t index)
pre(index & values, std::size_t index)
pre(index
#include
int get_value(const std::vector& values, std::size_t index)
pre(index values { 10, 20, 30 };
std::println("{}", get_value(values, 1));
// get_value(values, 10); // violates the precondition
}
The valid call prints:
20
And here’s the version for you to play with the problematic call uncommented:
See at Compiler Explorer
On GCC, I’m getting:
Program returned: 134
Program stdout
20
Program stderr
contract violation in function int get_value(const std::vector&, std::size_t) at /app/example.cpp:6: index & values, std::size_t index)
pre(index & values, std::size_t index)
{
return values[index];
}
The contract assertions are established by the function’s first declaration. A later declaration or definition may omit the contract specifiers, as above, or repeat the same sequence.
A later declaration cannot introduce a different set of preconditions or postconditions.
In practice, if a function is declared in a header, that first declaration is the natural place for its preconditions and postconditions. If you’re reading the API, it’s probably best to see all preconditions without looking inside the implementation.
What’s more, contracts are associated with the function, but they are not part of the function type. Overload resolution, for example, does not distinguish functions based on their contracts.
Contract Evaluation Semantics
C++26 defines four evaluation semantics:
| Semantic | What happens at runtime? |
|---|---|
| Ignore | The predicate is not evaluated. |
| Observe | A violation invokes the handler; execution continues if it returns normally. |
| Enforce | A violation invokes the handler; execution terminates if it returns normally. |
| Quick-enforce | A violation terminates execution without invoking the handler. |
The implementation determines which semantic applies. C++26 has no portable syntax for forcing an individual ordinary contract assertion to use one specific policy. These rules appear in the contract evaluation specification.
With the ignore semantic, no runtime check is performed. With observe, a violation is reported, but execution continues if the handler returns normally. In our example, both policies can allow the program to reach values[index] with an invalid index, resulting in undefined behavior.
Thus, adding pre(...) expresses the requirement, while the selected policy determines how checking responds.
Compiling with GCC
As of October 2026, only GCC 16 supports Contracts. We can use it with special compile flags:
g++ -std=c++26 -fcontracts \
-fcontract-evaluation-semantic=enforce example.cpp
With that policy, the invalid call invokes the violation handler. If the handler returns normally, execution terminates before the function body performs the invalid access. GCC’s default handler emits diagnostic information. See the GCC options documentation - contracts.
GCC also allows us to replace the default contract-violation handler. For example, we can log a custom message when a violation is detected.
The handler receives a std::contracts::contract_violation object, which provides information about the failed contract, including the predicate, location, and evaluation semantic.
#include
#include
void handle_contract_violation(
const std::contracts::contract_violation& violation)
{
std::println(stderr, "Contract violated: {}",
violation.comment());
}
See an example @Compiler Explorer
Notice that replacing the handler doesn’t change what happens after it returns. With observe, execution continues. With enforce, the program terminates.
Not every implementation is required to support replacing the handler, so this feature is implementation-defined.
Adding a Postcondition
So far, we’ve described what the caller must provide. We can also describe what a function promises when it returns normally.
See this example:
int clamp_value(int value, const int low, const int high)
pre(low = low && result high)
return high;
return value;
}
There are two useful pieces here:
- `pre(low
int clamp_value(int value, const int low, const int high) pre(low = low && result high) return high; return value; }
int main() { std::print("{}", clamp_value(10, 20, 30)); }
See [@Compiler Explorer](https://godbolt.org/z/c9asf75M5)
And on GCC I’m getting:
```text
contract violation in function int clamp_value(int, int, int) at /app/example.cpp:5: result >= low && result :5:28: error: a value parameter used in a postcondition must be const
5 | post(result: result >= low && result
std::size_t count_spaces(std::string_view text) {
std::size_t count = 0;
for (char ch : text) {
if (ch == ' ')
++count;
}
contract_assert(count 0) // error
{
}
An error from GCC:
error: increment of read-only location '(const int)count'
2 | pre(++count > 0) // error
Contract predicates use special constification rules. In this example, count is treated as const inside the predicate, so ++count is rejected by the compiler.
However, this does not make predicates completely free of side effects. For example, a predicate can still call a function that modifies global state. Since contract evaluation may be ignored, and side effects are not guaranteed even under checking semantics, predicates should not modify program state.
Contracts Are Not Input Validation
There’s another practical distinction: input validation.
Suppose an index comes from a configuration file. If an invalid value requires an error message, that handling belongs in ordinary control flow:
if (index >= values.size()) {
std::println("Invalid index: {}", index);
return;
}
std::println("{}", get_value(values, index));
The validation handles an expected failure at the application boundary. The precondition documents the internal helper’s requirements.
P2900 explicitly discusses this idea in:
Principle 12: Contract Assertions Are Not Flow Control: While a contract assertion provides an algorithm to validate correctness, nothing about a contract assertion guarantees any particular runtime behavior associated with that syntactic construct.
And more:
Importantly, this aspect of Contracts is why contract assertions must not be used for error handling and input validation: If a function has in-contract requirements to report certain events as errors, that handling must be done with standard C++ control statements that are not optional, never with contract assertions.
Considering Predicate Costs
Finally, one note about performance implications.
Our index comparison is cheap. A condition that verifies whether an entire collection is sorted needs to examine its elements. An expensive check may be useful during testing, but you should measure its effect on representative workloads.
Summary
C++26 contracts give us three ways to express assumptions and guarantees directly in the language:
pre(...)describes conditions expected to hold when a function is entered,post(...)describes conditions expected to hold when a function returns normally,contract_assert(...)checks properties at specific points inside a function.
Preconditions and postconditions are associated with the function through its declarations. In particular, the first declaration establishes the contract, which makes declarations in header files a natural place to document an API’s requirements.
A contract assertion does not necessarily mean that a runtime check will always be performed. The selected evaluation semantic can ignore, observe, enforce, or quick-enforce the assertion. Code therefore shouldn’t depend on contract predicates for required application logic or side effects.
Contracts also aren’t a replacement for normal input validation. Use regular control flow for errors that your program expects and needs to handle.
References
- P2900, Contracts for C++
- Contract assertions (since C++26) - cppreference.com - C++ Reference
- How C++26 Contracts Improve Correctness Without Sacrificing Performance | Citadel Securities
- How C++26 contracts work | Train IT
- Test Driving C++26 Contracts with GCC 16.1 | Fabian Koehler
I've prepared a valuable bonus for you!
Learn all major features of recent C++ Standards on my Reference Cards!
Check it out here:
Download a free copy of C++23/C++20/C++17 Ref Cards!
Similar Articles:
- C++26: Standard Library Hardening Experiments
- How to join or concat ranges, C++26
- C++ Templates: How to Iterate through std::tuple: C++26 Packs and Expansion Statements
- Views as Data Members for Custom Iterators
- How to use std::span from C++20
Tags: cpp26,





