February 17, 2025·6 min read·engineeringcppcontractshardeningstandards

C++26 adopts contracts and hardened library preconditions

Hagenberg puts contracts into the C++26 draft and gives selected standard library preconditions a checking requirement. The two changes have different enforcement guarantees.

An assertion language is useful only if its guarantees are clear at the point where a programmer depends on it. The February C++ meeting adopted both a configurable contracts facility and standard library hardening. Those changes share a violation-handling framework, but they do not make every assertion compulsory or every library operation checked. Their different requirements explain the practical significance of adopting them together.

Herb Sutter's February 17 trip report records the Hagenberg adoption of P2900 contracts and P3471 library hardening. The meeting ended on February 15. This post uses the report's publication date so the account rests on a source public as of its date. Adoption here means inclusion in the C++26 working draft, with the standard still under development.

The contracts paper adopted is P2900R14, dated February 13. It adds preconditions, postconditions, and contract_assert statements. The report identifies postponement of contracts on virtual functions as a change made at the meeting. Reflection remained in wording review for a later vote, so it should not be included in a list of features already adopted here.

Contracts describe requirements in source

A contract assertion expresses a condition that the programmer expects correct execution to satisfy. Preconditions describe requirements at a call boundary. Postconditions describe requirements after the function's result is established. An assertion statement places a check within executable code. These forms let a declaration carry information that an assertion macro inside a definition cannot expose in the same place.

That placement matters to readers and tools. A precondition on a declaration can describe a requirement without requiring the reader to find the function body. It does not remove the obligation to make the predicate itself a valid C++ expression. Checking an invalid pointer through a predicate can still have undefined behavior, because contract syntax does not supply a separate memory-safe expression language.

The facility has four evaluation semantics. Ignore does not evaluate the predicate. Observe evaluates it and permits continuation after violation handling. Enforce and quick-enforce prevent continuation past a detected violation. A configurable facility can therefore support different deployment policies, but a source-level precondition alone does not promise that every build enforces it.

I see the value in making requirements visible at interfaces. C++ already has enough implicit obligations in ordinary calls. A standard syntax can improve how those obligations are expressed, provided its evaluation policy remains part of the explanation. The distinction between an asserted requirement and an unconditional control-flow check remains necessary.

Library hardening uses checking semantics

Konstantin Varlamov and Louis Dionne's P3471R4, dated February 14, gives selected library requirements a separate status as hardened preconditions. In a hardened implementation, violating one produces a contract violation. The implementation must evaluate the relevant assertions with a checking semantic before other observable side effects of the operation.

That rules out ignore for these checks. It does not mandate that every violation must follow exactly one termination path. The wording allows the contract handling framework, including non-terminating semantics, but specifies undefined behavior if such a handler returns after the violated hardened precondition. Reporting a violation does not make it legitimate to continue into an operation whose required state is absent.

This is a more precise account than calling all hardened accesses safe indexing. The library detects a specified precondition failure using the required checking framework. It does not supply a valid element for an invalid index. The program still needs a policy that responds appropriately to the diagnosed violation.

In a non-hardened implementation, violation of these preconditions remains undefined behavior under the adopted wording. Whether the implementation is hardened is implementation-defined. The proposal leaves the activation mechanism to toolchains, so adopting the standard wording does not establish one universal compiler switch that users can already assume.

Bounds checks target existing requirements

The initial hardening scope includes element access on sequence containers and views, with operations such as front, back, and indexing. It also includes modifiers that require a nonempty sequence and accessors on optional and expected that depend on the active state. The proposal hardens existing requirements instead of adding a new algorithmic condition to every successful call.

For span::operator[], the relevant condition is that the index is less than the span's size. A span supplies the size needed for that check through its existing interface. The implementation can check the condition without making the access operation responsible for validating the original ownership and lifetime of every byte behind the view.

That limit is useful to state. A bounds condition describes a position within a range. It does not, by itself, establish that the range still refers to live storage. Hardening selected preconditions improves detection at that boundary while leaving other validity requirements of the surrounding program in place.

The same reasoning applies to accessing an empty optional. The operation has a state requirement before the contained value can be used. A hardened precondition can detect that the requirement is false. It does not invent a contained object or turn the call into the alternative error-handling API an application might have chosen.

The omissions follow implementation constraints

P3471 explains why checking every library precondition is an explicit non-goal. Some requirements concern semantic properties that cannot be evaluated with a small runtime test. Others would require representation changes or unacceptable overhead. The proposal targets checks that can be deployed in ordinary production builds rather than treating a comprehensive debug mode as sufficient.

Iterator-based erase operations illustrate the problem. For some contiguous containers, an implementation can derive useful checks from a pointer and storage bounds. For non-contiguous containers, checking ownership of an arbitrary iterator can require different metadata and representation choices. The proposal leaves those cases to dedicated work instead of claiming the same inexpensive implementation everywhere.

Algorithms create another difficulty. A valid range is a broad precondition, and a generic pair of iterators need not expose enough information to prove that both belong to the same container. Requirements about overlapping input and output can be similarly difficult to check for arbitrary iterator categories. The initial adopted scope reflects those limits.

The authors also intend hardening to avoid requiring an ABI change. They leave mixing hardened and unhardened translation units to implementation choices rather than imposing one model. That helps explain why a standardized behavioral requirement can coexist with toolchain-specific activation and configuration details.

Detection does not replace ordinary error handling

The adopted model treats a failed library precondition as a contract violation. That is different from a normal operation returning an error result that the caller is expected to handle. The call has already violated a requirement for its valid use. A program that accepts arbitrary external indices still needs an application-level decision about what to do with an index outside its accepted range.

This distinction follows directly from the hardening wording's treatment of a returning handler. Checking is required in the hardened implementation, but continuing after the specified violation is not assigned a useful element-access result. A diagnostic mechanism cannot supply the missing object. Likewise, choosing observation while introducing checks into a large system is a deployment technique described by the authors, not a redefinition of which calls meet the library's requirements.

For interface design, the question is whether the invalid condition is expected input or a violated programming obligation. Contracts and hardened preconditions describe the latter. The surrounding program decides which expected inputs it accepts before making the call.

Adoption gives the library a shared vocabulary

Existing implementations already have hardening or debug modes, as P3471 documents. The standards change identifies common preconditions and a shared handling framework. A user can then ask which documented hardened implementation they are building against instead of assuming every vendor's assertion mode covers the same operations.

The proposal's portability gain concerns the specified behavior, not a promise that all build systems become interchangeable. Toolchains still choose how to request hardening and how their compiled components interact. Those settings need to accompany claims about a deployed program's behavior.

I value RAII because its cleanup rules let resource management follow scope. Library hardening offers a different kind of improvement for existing C++ code, by checking selected requirements at familiar operations. The strongest practical result of Hagenberg is that contracts and the library now have coordinated wording for that work. A hardened access must check its designated condition, and a general contract assertion retains the evaluation flexibility specified by the contracts facility.