C++ Module 18 – C++26 previews: reflection, contracts and safety
C++26 contracts: pre, post and contract_assert
Write C++26 preconditions, postconditions and contract_assert checks, choose how a build evaluates them and replace the violation handler, run on GCC 16.
What you will learn
- Write preconditions, postconditions and contract assertions
- Choose an evaluation semantic when building a program
- Install a custom contract-violation handler
- Predict what each evaluation semantic does when a contract is violated
Before you start
What you’ll learn · Before you start
What you will learn
- Write preconditions, postconditions and contract assertions
- Choose an evaluation semantic when building a program
- Install a custom contract-violation handler
- Predict what each evaluation semantic does when a contract is violated
Before you start
On this page
In C++23, percent(5, 4) quietly returns 125, a percentage above 100, unless an assert that release builds remove
happens to catch it. Contracts let a C++26 function state its assumptions in its declaration. A precondition,
pre(…), must hold when the function is called; a postcondition, post(r : …), must hold for the result r when
it returns; and the statement contract_assert(…) checks a condition inside a body, like assert but without the
macro. When a check fails, a contract violation occurs, and its consequence is chosen when the program is built,
not in the code: the checks can be ignored, observed (reported, then execution goes on) or enforced (reported, then
the program stops). With GCC 16 contracts need only -std=c++26; Apple clang 21 and upstream Clang do not implement
them yet.
Preview: GCC 16 only
Status: in the final C++26 draft. GCC 16.2 compiled every contract example with -std=c++26 alone; on macOS,
linking GCC’s default violation handler also takes -lstdc++exp. Apple clang 21 does not accept contracts.
Three kinds of contract assertion check a call at three points
Follow one call of percent from top to bottom:
When each kind of contract assertion is checked, and what a violation does
Text description of the diagram
The diagram follows one call from top to bottom.
- The caller writes percent(5, 4).
- Inside the call, first the preconditions are checked on entry: pre(whole > 0) and pre(part >= 0 && part <= whole).
- Then the body runs, and each contract_assert is checked where it is written.
- Last, the postconditions are checked on a normal return, here post(r : r >= 0 && r <= 100). They are not checked when an exception leaves the function.
- The result returns to the caller.
A dashed arrow labelled "when a check fails" leads from the box "Inside the call" to a double-bordered box at the bottom: the evaluation semantic chosen when building decides what happens. With ignore the checks never run; with observe the violation handler runs and then the call goes on; with enforce the handler runs and then the program stops; with quick_enforce the program stops without calling the handler.
Preconditions and postconditions are written after the function’s parameter list, before its body, and a function
may have as many of each as it needs; they are checked in the order written. A postcondition can name the result,
here r, and refers to the object the function returns. It is checked only when the function returns normally,
never when an exception leaves it. contract_assert is a statement and can stand wherever a statement can, which
makes it the replacement for assert inside a body, with one difference that matters: it does not depend on whether
a macro such as NDEBUG happens to be defined.
Because preconditions and postconditions sit in the declaration, they are part of the function’s interface: they
appear in the header, a reader sees them next to the parameter list, and tools can read them. GCC checks them inside
the called function by default, and its -fcontracts-client-check=pre (or =all) option adds the checks at call
sites as well.
All three check conditions that a correct program never violates. A contract is not input validation: if bad input can reach a function in normal use, such as a quantity typed by a user, check it with ordinary code and report an error. A contract says that the caller, or this function’s own code, has a bug if the condition fails.
Checkpoint
Check: Pre, post and contract_assert
The build decides what a violation does
// Two preconditions, a postcondition and an assertion on one small function.
#include <cstdio>
#include <print>
int percent(const int part, const int whole)
pre(whole > 0)
pre(part >= 0 && part <= whole)
post(r : r >= 0 && r <= 100)
{
const int result = part * 100 / whole;
contract_assert(result * whole <= part * 100); // integer division rounds down, never up
return result;
}
int main() {
std::setvbuf(stdout, nullptr, _IONBF, 0); // unbuffered, so output and violation messages appear in order
std::println("percent(3, 4) = {}", percent(3, 4));
std::println("percent(5, 4) = {}", percent(5, 4)); // part > whole breaks the second precondition
std::println("done");
} from session import run
# One program, built with each of GCC 16's four evaluation semantics, then once without the flag. On macOS, GCC 16.2
# keeps its default violation handler in libstdc++exp, so the builds that call a handler link it with -lstdc++exp.
run("""
g++-16 -std=c++26 -fcontract-evaluation-semantic=ignore percent.cpp -o percent
./percent
g++-16 -std=c++26 -fcontract-evaluation-semantic=observe percent.cpp -o percent -lstdc++exp
./percent
g++-16 -std=c++26 -fcontract-evaluation-semantic=enforce percent.cpp -o percent -lstdc++exp
./percent
g++-16 -std=c++26 -fcontract-evaluation-semantic=quick_enforce percent.cpp -o percent
./percent
g++-16 -std=c++26 percent.cpp -o percent -lstdc++exp
./percent
""") Output
$ g++-16 -std=c++26 -fcontract-evaluation-semantic=ignore percent.cpp -o percent $ ./percent percent(3, 4) = 75 percent(5, 4) = 125 done $ g++-16 -std=c++26 -fcontract-evaluation-semantic=observe percent.cpp -o percent -lstdc++exp $ ./percent percent(3, 4) = 75 contract violation in function int percent(int, int) at percent.cpp:7: part >= 0 && part <= whole [assertion_kind: pre, semantic: observe, mode: predicate_false, terminating: no] contract violation in function int percent(int, int) at percent.cpp:8: r >= 0 && r <= 100 [assertion_kind: post, semantic: observe, mode: predicate_false, terminating: no] percent(5, 4) = 125 done $ g++-16 -std=c++26 -fcontract-evaluation-semantic=enforce percent.cpp -o percent -lstdc++exp $ ./percent percent(3, 4) = 75 contract violation in function int percent(int, int) at percent.cpp:7: part >= 0 && part <= whole [assertion_kind: pre, semantic: enforce, mode: predicate_false, terminating: yes] terminate called without an active exception [stopped by signal SIGABRT] $ g++-16 -std=c++26 -fcontract-evaluation-semantic=quick_enforce percent.cpp -o percent $ ./percent percent(3, 4) = 75 terminate called without an active exception [stopped by signal SIGABRT] $ g++-16 -std=c++26 percent.cpp -o percent -lstdc++exp $ ./percent percent(3, 4) = 75 contract violation in function int percent(int, int) at percent.cpp:7: part >= 0 && part <= whole [assertion_kind: pre, semantic: enforce, mode: predicate_false, terminating: yes] terminate called without an active exception [stopped by signal SIGABRT]
Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 semantics_session.py
GCC 16 chooses the evaluation semantic with -fcontract-evaluation-semantic=, and enforce is the default
when the flag is missing, as the final draft recommends: the last build sets no semantic, and its report says
semantic: enforce. The first four runs differ only in that flag:
ignore: the checks never run, and the program goes on as if no contract were written.observe: the checks run; on a violation the handler is called, and the program goes on when it returns.enforce: the checks run; on a violation the handler is called, and the program stops when it returns.quick_enforce: the checks run; on a violation the program stops at once, without calling the handler.
Step through the observe build, then what the other three builds did:
The drawing shows the whole scene; its 9 steps are listed below.
Every step as text
- percent(3, 4). The build used -fcontract-evaluation-semantic=observe. main calls percent(3, 4): both preconditions hold, the body computes 75, the contract_assert holds and so does the postcondition.
- Returns 75. Every check passed, so 75 comes back and main prints percent(3, 4) = 75. A correct call pays for the checks and nothing more.
- percent(5, 4). The second call breaks the second precondition: 5 is more than 4. That is a contract violation, reported at line 7 of percent.cpp, the line of the precondition, not of the call.
- The handler reports it. Under observe the check calls the violation handler. GCC's default handler prints two lines: the function, the file and line and the condition, then the kind (pre), the semantic and terminating: no.
- The call goes on. The handler returns, and under observe execution continues. The body computes 5 * 100 / 4 = 125, and the contract_assert, 125 * 4 <= 5 * 100, holds.
- The postcondition fails. post(r : r >= 0 && r <= 100) is checked on the normal return and fails for 125, so the handler is called a second time, for line 8.
- 125 reaches main. The handler returns again, percent returns 125, and main prints percent(5, 4) = 125 and done. Observing told you about the bug without preventing its effect.
- Under enforce. Built with enforce instead, the run is the same up to the first report. When the handler returns, the program stops through std::terminate, with SIGABRT, before 125 exists.
- quick_enforce and ignore. quick_enforce stops at the same check without calling the handler, so no report is printed. ignore runs no check at all, and percent(5, 4) quietly returns 125.
With ignore, percent(5, 4) quietly returns 125, a percentage above 100. With observe, GCC’s default handler
reports the broken precondition, the call goes on, and the postcondition r <= 100 is reported as broken too, before
125 comes back: observing tells you about bugs without preventing their effects. With enforce, the first violation
stops the program, here through std::terminate: The function C++ calls when exception handling cannot go on, for example when no handler matches a throw or an exception tries to leave a noexcept function. By default it calls std::abort, which ends the program with SIGABRT. More in “C++ exceptions: throw, catch and stack unwinding”, before the bad value exists. With
quick_enforce, the program stops at the same point but skips the handler and its message, which keeps the checks
small in the compiled code.
The default handler’s two lines name the function, the file and line, the condition, the kind of assertion and the
semantic. The line is the line of the precondition, not the line of the call that broke it, which matters when a
function has many callers.
Checkpoint
Check: The four evaluation semantics
Practise: contracts and semantics
Eight questions follow on where the checks go and what each semantic does.
Practice that adapts to you
Practise this lesson
8 questions about 4 ideas of this lesson. Each answer is explained at once, and what you get right is kept in this browser.
Replacing the violation handler, and where GCC 16.2 differs from the final draft Pro: opens with a Pro, Premium or Ultimate pass
The default handler only prints. A program can install its own, to log violations in its own format or, in tests, turn them into exceptions. This part does both and records where GCC 16.2 departs from the final draft.
In this part:
- A handler that logs one line and flushes, and the link step on macOS
- Four ways GCC 16.2 departs from the final draft
- A throwing handler for tests, and why noexcept stops it
7 runnable examples (C++, Python) · about 5 minutes
Opening this part with your pass…
This part opens with any MySmartCoPilot pass.
- Pro pass · 1 week₹79$2.99
- 30 days₹179$5.99
- 1 year₹1,699$45
One payment; a pass never renews. The price shown is the total you pay: no fees or charges are added at checkout.
Passes can’t be bought right now, so this part can’t be opened at the moment. The free part above stands on its own. If you have a pass, enter your licence key.
It also opens every other in-depth part in Learn and every Pro tool. Sales are final, except where the law gives you a right to withdraw, for example in the EU and the UK (refund policy).
Rules that catch people out, a compile-time check, and an exercise with a prediction Pro: opens with a Pro, Premium or Ultimate pass
Four rules of C++26 contracts surprise people who come from assert. Each is reproduced below with GCC 16.2’s
message, and the exercise then asks you to predict the effect of contracts under two semantics before you run them.
In this part:
- Four rules, from const parameters to constant evaluation, reproduced
- Choosing a semantic for each build
- Your turn: contracts for a bounded stack, with a prediction first
7 runnable examples (C header, C++, Python) · about 8 minutes
Opening this part with your pass…
This part opens with any MySmartCoPilot pass.
- Pro pass · 1 week₹79$2.99
- 30 days₹179$5.99
- 1 year₹1,699$45
One payment; a pass never renews. The price shown is the total you pay: no fees or charges are added at checkout.
Passes can’t be bought right now, so this part can’t be opened at the moment. The free part above stands on its own. If you have a pass, enter your licence key.
It also opens every other in-depth part in Learn and every Pro tool. Sales are final, except where the law gives you a right to withdraw, for example in the EU and the UK (refund policy).
Interview questions
-
Fresher to mid · Warm-up
Name the four evaluation semantics of C++26 contracts, and say which call the violation handler and which stop the program.
ignore does not check the contract at all, although the condition must still compile. observe checks it and calls the violation handler on a failure, then lets the program go on. enforce checks it, calls the handler, and stops the program when the handler returns. quick_enforce checks it and stops the program at once, without calling the handler. So observe and enforce call the handler, and enforce and quick_enforce stop the program. The semantic is chosen when building, not in the code: GCC 16 uses -fcontract-evaluation-semantic=, with enforce as the default, which is also what the final draft recommends.
-
Senior to staff
You want to add preconditions to a function that 400 call sites use, and some callers may already break them. How do you introduce the contracts without crashing production, and how do you decide when to switch to enforce?
-
Mid to senior
A function debits a wallet: debit(account_id, amount_paise). Which conditions belong in preconditions, which in a contract_assert, and which in an exception or an error result? Give one example of each and justify the choice.
-
Mid to senior
Your builds enforce contracts, and enforce stops the program on a violation. How do you write unit tests that prove a precondition actually fires?
Key takeaways
pre(…)andpost(r : …)attach checks to a function’s declaration;contract_assert(…)checks inside a body.- The evaluation semantic is a build choice:
ignore,observe,enforceorquick_enforce, withenforcethe default in GCC 16. observereports violations and lets the program go on, so the bug’s effects still happen.- Contracts state what a correct program never violates; validate user input with ordinary code.
- GCC 16 accepts contracts with
-std=c++26; Apple clang 21 does not.
References
- P2900R14: Contracts for C++ (ISO/IEC JTC1/SC22/WG21)
- Working Draft, Programming Languages: C++ (N5050) (ISO/IEC JTC1/SC22/WG21)
- Options Controlling C++ Dialect (GCC 16.2 manual) (GNU Project)
- GCC 16 Release Series, Changes, New Features, and Fixes (GNU Project)
- C++ Support in Clang (LLVM Project)
- P3097R3: Contracts for C++: virtual functions (ISO/IEC JTC1/SC22/WG21)
Report a problem with this lesson
Kept only in this browser. Your Learn progress