Document number: P4428R0
Audience: SG21, EWG


Ville Voutilainen
2026-10-23

Contract concerns and where they are addressed

Abstract

Several concerns have been raised about the C++26 contracts facility, P2900, most compactly in P3173R0, "P2900R6 May Be Minimal, but It Is Not Viable": that it doesn't prioritize safety, that the evaluation of a contract assertion can have undefined behavior, that it doesn't support virtual functions and function pointers, and that it isn't applied to the standard library. A further concern, raised separately, is that P2900 leaves too much implementation-defined. This paper is an overview: for each concern, which paper addresses it, and how. Each of those papers is backed by an implementation, in a GCC branch that can be tried on Compiler Explorer.

The papers

Overview

ConcernAddressed byHow
Safety isn't prioritized (P3173R0, section 2) P4423R0; P4425R0 Safety is a property a contract can have, chosen per contract, and verified: a strict contract's evaluation is proven free of undefined behavior; a symbolic one has nothing to evaluate. Ordinary P2900 contracts don't change.
Undefined behavior in a contract's evaluation (P3173R0, section 3) P4423R0 A strict predicate, and every function it calls, is checked against mandatory rules: its references and this are object addresses, it changes nothing, its arithmetic can't overflow, it establishes its callees' preconditions. Violating one is an error.
Dynamic dispatch (P3173R0, section 4) P4427R0 A control object's inherited() decides whether an overrider inherits the contract, and base_contract<Base>() lets an override's own contract weaken or strengthen its base's, so the model in the working draft, wholesale inheritance and the Design-by-Contract model can all be expressed.
Indirect calls (P3173R0, section 4) P4426R0 A contract on a function pointer declaration is checked on every call through it, with no change to the pointer's representation or to the ABI.
No application to the standard library (P3173R0, section 5) P4324; P4423R0 The library's hardened preconditions are implemented with P4324 contracts, in non-strict form; as strict contracts, 478 of its 509 hardened assertions, in 204 functions across 44 headers, with 60 declared preconditions.
Too much implementation-defined behavior P4324 What P2900 leaves to the implementation, a control object does, in portable code: choosing the semantic at run time, repeating an evaluation, choosing where the check is made, what a violation does.

Safety, prioritized

P3173R0 recalls SG21's 2020 poll to "progress contract checking to enforce software safety first", and finds that P2900 doesn't deliver on it. P4423R0 does, without changing what P2900 specifies: a contract becomes a strict contract by naming a control object that says so, and for a strict contract the compiler verifies, rather than trusts, that evaluating it is safe. In November 2024, EWG polled against making the MVP itself stricter; the strict contracts of P4423R0 are additive, so they don't ask EWG to revisit that.

Symbolic contracts go further for the contracts they're used for: a symbolic predicate has no body, so it has no side effects and no undefined behavior by construction, and its truth is established by the compiler from the postconditions of earlier calls.

Undefined behavior in contract evaluation

P3173R0's central objection is that a check meant to guard against bugs can itself be elided, or exploited, through undefined behavior in its own evaluation; it refers to P2680 for the remedy. P4423R0 implements that remedy. A strict predicate, a conveyor predicate, may call only conveyor functions, and both are checked at compile time against rules that rule out undefined behavior: every reference and this is the address of an object, nothing is modified, arithmetic doesn't overflow and doesn't divide by zero, every call establishes its callee's preconditions. A violation is an error, not a warning.

A strict contract is not limited to toy predicates: the standard library's own assertions, listed below, are expressed that way.

Dynamic dispatch and indirect calls

For virtual functions, the debate has been between a Design-by-Contract model, where an overrider's contract relates to the overridden function's, and the model in the working draft, where it doesn't. Each has been called unsuitable by someone. "Configurable contracts for virtual functions" doesn't pick one. A control object declares inherited(), and an override that declares no contract of its own inherits the contract when it says so, separately for the caller's side and the definition's. An override's own contract can refer to its base's with base_contract<Base>(): base_contract<Base>() || extra is a weaker precondition, base_contract<Base>() && extra a stronger postcondition, which is Design-by-Contract subcontracting. The model in the working draft, wholesale inheritance and Design by Contract can then each be expressed, contract by contract.

For function pointers, "Contracts for (member) function pointers" attaches a contract to a function pointer's declaration and checks it on a call through that pointer:

void f(int a, int b);
void (*p)(int a, int b) pre(a <= b) = f;

Nothing is carried along when the pointer is copied, so neither the pointer's representation nor the ABI changes; a copy declared with a different contract, or none, can get a quality-of-implementation warning.

The standard library

P3173R0 asks for the facility to be applied to the standard library. In the implementation, P4324 contracts, in non-strict form, implement the library's hardened preconditions: each is a contract assertion whose control object enforces it. The same assertions can also be strict contracts: 478 of the library's 509 are, in 204 functions across 44 headers, with 60 declared preconditions; the remaining 31 are assertions whose predicates can't be strict. The library's saturation arithmetic functions are conveyor functions, so strict predicates can use them. Details are in P4423R0.

Implementation-defined behavior

Another concern raised about P2900 is that it leaves too much implementation-defined: which evaluation semantic applies, usually chosen by a compiler flag for the whole program; whether an assertion is evaluated more than once; whether the check is made on the caller's side or the callee's; what a violation does beyond calling the violation handler. Each is a door left open for an implementation to go through.

P4324 doesn't leave those doors open. It lets the program go through them, portably, in the control object it names. A control object's type answers the compile-time questions (whether the assertion is ignored, whether it is constified, on which side it is checked), and its operator(), handed an assertion_context, decides what an evaluation does: whether to evaluate the predicate, how many times, and what to do when it fails. So what P2900 makes implementation-defined is, under P4324, just code.

Semantics chosen at run time are a control object whose operator() consults the program's own state to decide whether, and how, to evaluate the predicate; P4324 gives an example.

Repeated evaluation is a loop in a control object's operator(), calling ctx.check() as many times as it chooses. The number of evaluations can be a compile-time constant, https://godbolt.org/z/Kv6xd7Ka1, or a run-time variable, so the program decides while it runs, https://godbolt.org/z/vjodhxP5n.

These are ordinary user code, and behave the same with every implementation of P4324. None of them needs a compiler flag, and they can be used side by side in the same program, each by the contracts that name it.

Not covered here

Implementation experience

All of the above is implemented in a GCC branch, and can be tried on Compiler Explorer with the "x86-64 gcc (P4324 contracts, and profiles)" compiler, using -std=c++26 -fcontracts -fcontract-control-objects. The papers listed above each give their own examples.