February 5, 2017·4 min read·engineeringcppstandardscontracts

The contracts proposal separates interface predicates from checking policy

P0542R0 supplies wording for contract annotations. Its build levels and violation handling reveal the design choices behind putting preconditions and postconditions on declarations.

A precondition in a comment can tell a caller what a function requires, but the compiler cannot directly use it as an executable predicate. The February 5 paper P0542R0 proposes attributes for those requirements and for conditions expected on return. It supplies wording for an existing contracts design, P0380R1, rather than introducing the effort from nothing.

The authors are Gabriel Dos Reis, J. Daniel Garcia, John Lakos, Alisdair Meredith, Nathan Myers and Bjarne Stroustrup. Their proposal puts conditions on function declarations while keeping the policy for evaluating them a translation setting. That division is the central design issue. A contract can remain part of an interface even when a particular build does not execute its checks.

I favor requirements that callers can read beside an interface. C++ already makes resource lifetime part of the language, and a visible precondition could make another important obligation explicit. The difficult part is specifying what a violated condition means, especially when checking cost and continuation policy vary across builds.

Preconditions and postconditions attach to functions

The proposed expects attribute expresses a predicate that should hold on entry. ensures expresses a predicate expected on exit, and can introduce a name for the return value. An assert attribute expresses a condition at a point in the function body. These spellings are proposed contract syntax, not facilities supported by standard C++ today.

int positive_identity(int value)
    [[expects: value > 0]]
    [[ensures result: result == value]]
{
    return value;
}

The result name belongs to the postcondition's expression. It is not a new local variable declared in the function body. The predicate relates the returned value to the parameter without changing the computation performed by the body.

The colon is part of the proposed attribute syntax. Its rationale points out a practical consequence for assert, whose traditional function-like macro expands only when followed by parentheses. The proposed assert: spelling avoids that macro invocation. This syntax decision lets the contract notation coexist with a familiar header macro.

The wording also changes how trailing function attributes attach. The authors want these annotations to apply to the function itself, rather than its type. Their proposed function-pointer rules consequently forbid putting contracts on the pointer type, while requiring checks when a contracted function is called through a pointer.

Checking levels express expected cost

Contracts can have default, audit or axiom levels. The default level is intended for checks inexpensive relative to the function. Audit checks may have a substantial cost, while axioms are formal comments not evaluated at runtime. Those categories give authors a way to express why a predicate might remain unchecked in an ordinary build.

Translation build levels are off, default and audit. Off checks no contracts, default checks default-level predicates, and audit checks both default and audit predicates. The mechanism for choosing a level is implementation-defined. The proposal does not permit program code to set or query the translation unit's build level.

This is different from a compile-time assertion. The N4618 working draft, which the paper uses as its wording baseline, describes static_assert in terms of a constant expression whose false value makes the program ill-formed. Proposed runtime contracts can instead depend on function arguments and object state observed during execution.

The proposal requires name lookup to succeed independently of build mode. Disabling evaluation does not let a contract contain unresolved names that only an audit build would discover. This keeps the declaration's meaning from depending on which checks happen to execute.

Predicates must also avoid observable modification of objects. A check that updates program state would make correct behavior depend on whether evaluation was enabled. The wording declares such a program ill-formed without requiring a diagnostic. That is a programmer obligation, not a guarantee that every accidental side effect will receive a compiler error.

Repeated declarations must agree

The paper permits a redeclaration to repeat the whole contract or omit it entirely. A partial or different contract is not another independently valid view of the same function. The proposed equivalence allows parameter names to change while retaining the same expression structure.

This is stricter than asking whether two arbitrary Boolean expressions are logically equivalent. The authors reject making implementations solve that general problem. They choose ODR identity with a parameter-renaming allowance, which gives separate declarations a defined form of agreement.

The same concern reaches overriding functions. The proposed rule requires an override to have the base declaration's contract, with omission inheriting it. This prevents a derived declaration from silently presenting a different contract under the same virtual interface. It also makes contract design part of maintaining a class interface, not merely annotating individual bodies.

Violation handling remains an active design question

A detected violation invokes a handler with a proposed std::contract_violation object. The class provides source-location information and the text of the failed condition. The paper leaves details of populating that object and selecting the handler to implementation policy, and explicitly lists unresolved questions about the handler.

Continuation is a separate translation mode. With continuation off, the proposed rule aborts after the handler completes. With it on, execution continues after the handler. The paper motivates continuation as a way to log violations in an existing codebase before enforcing them. Continuing does not make the failed precondition true, so this mode has consequences beyond whether an error message appears.

The authors also identify an unresolved interaction when the handler throws and continuation is off. That admission matters when evaluating the wording. A proposal can describe the normal check path while still needing a precise account of exceptional control flow. Treating it as finished language behavior would conceal that open question.

I regard those policy choices as the part that needs the closest scrutiny. A declaration-level predicate is useful only when its users understand the meaning of an unchecked or violated condition. P0542R0 makes the intended checking levels concrete and records where handler behavior still needs a decision. That gives the committee a specific model to evaluate before contracts become something applications can rely on as a language facility.