The adopted contracts design separates checking from continuation
Rapperswil advances contracts for C++20 with separate controls for checking and continuation after a violation. The adopted wording also constrains side effects, redeclarations and postconditions.
A contract can state an interface requirement, select whether it is checked, and define what happens after a failed check. Those are different decisions in the contracts design approved at Rapperswil. Ville Voutilainen's June 9 Evolution report records the approval while acknowledging remaining questions about preconditions and postconditions on virtual functions. The feature is work toward C++20, not an interface already required of every deployed C++ compiler.
The detailed design is P0542R5, dated June 8. It introduces preconditions, postconditions and assertions, with build levels and a separate violation-continuation mode. Understanding those controls is necessary because writing a condition beside a function does not alone establish whether every build evaluates it or stops when it fails.
I value C++ when its interfaces make obligations explicit enough to reason about locally. Contracts could help describe those obligations, but their utility depends on the exact evaluation rules. An annotation that appears to validate an input needs a clear distinction between stating a requirement and enforcing a runtime boundary.
Preconditions and postconditions describe different moments
The design uses expects for a function's entry requirement and ensures for its return requirement. An assertion inside the body uses assert. Each takes a predicate contextually converted to bool. A postcondition can introduce a name for the result so that it can relate the returned value to the function's requirements.
A small example in the adopted design's syntax is:
// Contracts design adopted for the C++20 working draft.
int nonnegative(int value)
[[expects: value >= 0]]
[[ensures result: result >= 0]]
{
return value;
}
The example deliberately avoids modifying the parameter used in the contract. It also uses the design's own attribute syntax, without implying that an ordinary C++17 compiler must implement it. The precondition says what the caller must supply. The postcondition says what should hold when the function returns normally.
P0542R5 places the precondition check immediately before evaluation of the body and the postcondition check immediately before returning control to the caller. The latter occurs after local variables and temporaries have ended their lifetimes. Exiting by exception is not a normal return for this purpose. A postcondition therefore cannot be understood as a check on every possible exit path.
Multiple preconditions are evaluated in their lexical order when checked. The same applies separately to multiple postconditions. This permits a sequence that first establishes a pointer is non-null and then checks a property through it. The order within each category matters even if preconditions and postconditions are interleaved in the declaration.
Build levels control which predicates are checked
The design has default, audit and axiom contract levels. Omitting a level selects default. The accompanying explanation associates default checks with relatively inexpensive predicates and audit checks with more costly ones. Axioms are intended as formal comments without ordinary runtime evaluation.
Build levels are a different setting. A default build checks default contracts. An audit build checks default and audit contracts. An off build does not select those runtime checks. The mechanism that selects the build level is implementation-defined, and the wording does not provide a programmatic facility for querying or changing it within a translation unit.
That means a source-level contract level is not a local request that the compiler must always execute the expression. It classifies the requirement for the selected build policy. A developer reading only the declaration cannot infer the complete checking behavior without also knowing that policy.
The wording gives unchecked contracts consequences too. It leaves evaluation unspecified for a contract that the current build level would not check, and says that behavior is undefined if it would evaluate false. This makes the distinction from ordinary input validation substantial. Turning checking off does not turn the requirement into permission for callers to violate it.
Continuation is independent of selecting a check
When a checked predicate evaluates false, the design invokes a violation handler. A separate continuation mode determines what follows a completed handler call. With continuation off, execution terminates through std::terminate. With continuation on, execution continues. The default continuation mode is off.
The paper describes continuation as a way to instrument an existing codebase and record violations before enforcing them. That is a particular adoption strategy. It does not establish that proceeding after every failed precondition leaves the function body with usable arguments. The body can still depend on the requirement it declared.
The handler receives a contract_violation describing the failure. The proposed class exposes a line number, file and function names, a comment related to the predicate, and the assertion level. Several details remain implementation-defined, including establishing the handler and populating its argument.
Location rules also distinguish categories. A precondition's reported location is implementation-defined, with an encouragement to report the caller. Postcondition reports point to the function definition, and an assertion points to its statement. A handler therefore receives useful context without a guarantee that every precondition report identifies the exact call site.
Predicates cannot perform ordinary application work
P0542R5 restricts side effects in predicates. The permitted modifications concern non-volatile objects whose lifetimes begin and end during predicate evaluation in functions invoked by the predicate. Other side effects have undefined behavior. The rationale explicitly includes reading a volatile object among the problematic operations.
An expression that increments an application counter or writes a diagnostic stream is consequently unsuitable as a contract predicate under this design. The problem is not simply that the operation might be skipped in another build. The wording independently restricts the predicate's observable effects.
Local computation inside a helper can still be allowed. A helper may use and update its own temporary variables while calculating a result, provided it remains within the stated lifetime and visibility constraints. The restriction therefore concerns observable application state, rather than requiring every helper to consist of one expression with no internal assignments.
I consider that restriction essential to reading a contract as a requirement. If evaluating it changes the application's behavior, changing the checking policy changes more than diagnostic coverage. Keeping predicates within the proposed side-effect boundary lets the requirement remain meaningful across the intended build modes.
Repeated declarations must agree
The first declaration supplies all of a function's contract conditions. Later declarations can omit them or repeat the same list. Repeating a different subset is not a way to add a new local requirement. The wording compares order, levels and predicates, with allowances for renaming parameters and result identifiers.
This creates another cross-translation-unit obligation. Repeated contracts need to agree even where no diagnostic is required. The requirement follows the design's relationship to the one-definition rule, not a promise that a compiler processing one source file can always detect disagreement with another file.
Name lookup is also independent of checking policy. The paper chooses to require contract expressions to pass lookup regardless of whether their level is selected for runtime evaluation. An audit-only predicate cannot therefore refer to an undeclared helper merely because a default build will not check it. That rule keeps the declaration meaningful across build levels while leaving runtime checking configurable.
Mixed build levels across translation units are conditionally supported. This qualification makes build configuration part of the implementation agreement for a program, alongside the matching declarations themselves. Source-level agreement about a predicate does not settle every permitted combination of compiled objects.
The paper also keeps contracts out of the function type itself. A function pointer cannot carry an independent set of contract conditions in its type. Calling a contracted function through a pointer still checks the function's own conditions as specified. The pointer syntax does not supply a mechanism for overriding those conditions.
Postconditions impose a further constraint on parameters. If a postcondition odr-uses a parameter value and the body modifies that value directly or indirectly, the design gives undefined behavior. Modifying an object through a pointer parameter is distinguished from modifying the pointer parameter itself. This makes it necessary to identify which value the postcondition actually uses.
Virtual interfaces remain a stated area of concern
The adopted wording requires an overriding function that supplies conditions to use the same conditions as the overridden function. An override without conditions inherits a list under the specified rules. Multiple overridden functions must also have matching lists, with additional requirements when predicates use *this.
Voutilainen's report explicitly notes open questions about virtual-function contracts despite the approval. That is the appropriate limit on the result. Rapperswil has advanced a concrete design with detailed evaluation and consistency rules. Its build policy, continuation behavior and inheritance obligations still need to be read as separate parts of the interface contract.