December 10, 2025·5 min read·engineeringcppcontractsstandards

P3911 proposes contracts that build settings cannot disable

P3911R0 proposes unconditional contract assertions. The disagreement concerns who controls failure behavior when a library depends on a check surviving every build.

A library author can write a precondition that looks like a check without controlling whether the executable checks it. That separation motivates P3911R0, the Romanian national body's proposal for unconditional contract assertions. Its preferred spelling adds an exclamation mark to the assertion. The intended guarantee belongs to the source declaration and survives changes to build policy.

The WG21 paper index dates P3911R0 December 10, 2025, in the December mailing. This is a proposed change to the contracts design. The practical question is whether a library can express a failure policy that its consumers cannot silently weaken.

I have spent a decade with C++, and its accumulated language eras make local reasoning difficult enough. An assertion whose enforcement requires knowledge of the consuming build adds another dependency to understanding a function. The attraction here is a declaration that tells the reader more about the executable's behavior.

Checking and stopping are different guarantees

The baseline P2900R14 design defines four evaluation semantics. Ignore performs no check. Observe checks and invokes the violation handler, but permits execution to continue if that handler returns normally. Enforce invokes the handler and terminates if it returns. Quick-enforce terminates on a violation without invoking that handler. Selection of evaluation semantics is implementation-defined.

Consequently, requesting a check and requiring termination express different requirements. Removing ignore alone would leave observe available. A library that must stop before proceeding with invalid state needs a stronger constraint than the presence of predicate evaluation. This distinction matters when interpreting the proposal's title, which emphasizes non-ignorability.

P3911's proposed wording selects quick-enforce for unconditional assertions. It covers pre!, post!, and contract_assert!. This illustrative declaration uses the proposed precondition spelling and requires the proposed language extension:

int positive_divisor(int divisor)
    pre! (divisor > 0)
{
    return divisor;
}

The proposed assertion requires a positive argument and specifies termination on a detected violation. A logging handler would not run through the quick-enforce path. That choice deserves attention from anyone expecting a common violation-reporting facility to record every failed assertion. The guarantee includes a particular response to failure, with consequences for diagnostics.

The example deliberately returns its argument. No arithmetic operation is needed to demonstrate the declaration's policy. This keeps the discussion about enforcement separate from questions about the behavior of a later operation on an invalid value.

Why language contracts differ from assertion macros

The companion contracts rationale explains why a language facility is useful even when assertion macros already exist. A declaration can expose requirements without requiring a reader or tool to inspect the function body. Common syntax also gives analysis tools something consistent to process across libraries.

The rationale describes another cost of macro-based assertions. Preprocessing can remove the predicate text, allowing disabled assertions to deteriorate without ordinary compilation detecting it. A language facility can retain information that a text substitution removes. Standardizing how a violation reaches a handler also reduces the need for each library to define its own reporting interface.

These benefits make the enforcement dispute consequential. A project may want to replace a private assertion facility to gain common syntax and tooling. Such a replacement is incomplete if the existing facility promises termination in every build and the new declaration leaves that decision elsewhere. Migration requires comparing behavior as well as comparing the conditions written between parentheses.

The rationale also records disagreement over permitting repeated predicate evaluation. That history is another reason to avoid treating language assertions as a spelling change for macros. Predicate design and failure behavior both need explicit consideration when deciding whether two facilities provide the same contract.

Hardening exposed the policy conflict

An October paper, P3878R0, argues against coupling standard-library hardening to the contracts machinery. Its authors identify a case where a checked but non-terminating assertion can leave a hardened precondition violation with undefined behavior after the handler returns. For their intended hardening guarantee, checking without stopping is insufficient.

The paper also identifies an implementation problem. A standard-library implementation may support several compilers, while the means of controlling contract evaluation belong to each implementation. The authors report no known common mechanism for requiring the needed semantics across those compilers. They also require library hardening to operate independently of the policy for other contracts in the same translation unit.

This is a concrete example of competing policy owners. The application controls its build, while a library implementation wants to promise a particular boundary on failure. The language has to provide enough control for that promise, or the library must retain another mechanism. Merely documenting an application-wide setting transfers part of the library's guarantee to every consumer.

I find that argument persuasive because the maintenance cost falls on readers as well as build maintainers. A reviewer should be able to identify which component owns a terminating check. A private macro can express that policy, but it also requires the reviewer to learn another library's conventions.

The alternatives change different parts of the design

P3911 also discusses removing ignore, adopting a fuller properties framework, or moving contracts to another shipping vehicle. These alternatives differ in scope. Removing an evaluation option changes the choices available to all assertions. A dedicated spelling permits individual declarations to request a guarantee. Deferring the facility changes when any standard language mechanism becomes available.

The earlier P3400R0 properties proposal explores more extensive control through compile-time assertion-control objects. It includes restricting allowed semantics and providing library-specific violation handling. Its example combines an enforce-or-quick-enforce restriction with a handler that can delegate to the program's handler. That addresses customization beyond the single unconditional spelling.

Such control has a cost in concepts that implementers and readers must understand. P3911's narrow spelling is easier to explain, but selecting quick-enforce fixes the handling choice along with the enforcement requirement. A properties mechanism can represent more policies and requires a larger design. The useful comparison concerns which guarantees a declaration must express and how much machinery is justified to express them.

My preference is for source-level control over checks that a library considers mandatory. The declaration should make its failure policy readable without reconstructing the consumer's build settings. P3911 provides a specific proposal to evaluate against that requirement, including the diagnostic consequences of choosing immediate termination without the violation handler.