Representative interview topic

C++26 Contracts: How do you safely ship preconditions, postconditions, and contract_assert?

CodingHard
Offer.cc Editorial TeamPublished Updated

Question

How do you explain pre, post, and contract_assert and migrate existing assert usage while compiler support is incomplete?

Prompt and scope

You own an order library and want C++26 Contracts to express API preconditions, postconditions, and function-body invariants. Explain the semantic boundaries of pre, post, and contract_assert, evaluation modes, violation handling, and a migration path from existing assert usage that does not drop validation of untrusted input. Include a release strategy while compiler support is incomplete.

What the interviewer is testing

  • Separating contract documentation, runtime diagnostics, and business input validation.
  • Explaining why predicates may be elided or evaluated more than once, so they must be side-effect free.
  • Designing a violation handler, telemetry, and fault isolation without assuming every violation throws.
  • Using a compiler capability matrix and gradual rollout to manage C++26 support differences.

Clarifying questions

  1. Are callers trusted library code or direct network-request handlers?
  2. Should production observe a violation, terminate the process, or isolate the request and continue?
  3. Which compiler, standard-library version, and ABI release cadence are in scope?

30-second answer framework

Start with responsibility: pre states what the caller must satisfy, post states what the callee promises on return, and contract_assert states a local function-body contract. Then explain that predicates must have no side effects because an implementation may elide or repeat evaluation; violations go through a handler and deployment policy. Finally, keep explicit validation for untrusted input and use feature detection, a compiler matrix, and canaries for migration.

Deep-dive answer

1. Establish the contract layers

Preconditions are the caller's responsibility; postconditions are the callee's responsibility. They express composable API constraints such as positive capacity or an ordering guarantee. A function-body contract_assert expresses a local invariant or algorithm-stage assumption. All three should be readable in review and kept separate from recovery policy.

2. Keep predicates side-effect free

A predicate should only read state and compute a Boolean. Do not increment counters, mutate caches, release resources, or depend on one-shot randomness inside it. C++26 contract semantics allow different evaluation modes: some may elide evaluation and others may evaluate more than once. Side effects would make correct-program behavior depend on build configuration.

cpp
int withdraw(Account& a, int amount)
  pre (amount > 0)
  pre (amount <= a.balance())
  post (a.balance() == old_balance - amount);
{
  contract_assert(a.is_open());
  return a.debit(amount);
}

old_balance in the snippet highlights a design issue; C++26 does not provide a general postcondition capture, so do not assume an old(...) syntax. If the old value is needed, save it explicitly in the function and confirm that the save does not change business semantics, or wait for a later standard extension.

3. Choose evaluation and violation handling

Define behavior for modes such as observe and enforce. Development and tests can collect diagnostics; a critical service can fail fast under enforcement; a request boundary can convert a failure into an observable, isolated result. Do not promise that a violation always throws: behavior depends on implementation and build configuration. The handler should record contract location, request correlation ID, and version, while avoiding recursive entry into the same contract path.

4. Keep input validation at the boundary

Network fields, user amounts, and permissions are untrusted. Validate them explicitly and return an expected business error before calling an internally contracted function. Contracts can catch programmer mistakes among trusted callers, but they do not replace authentication, authorization, rate limiting, or format checks and must not be the only data-cleaning defense.

5. Plan migration and release

Build a capability matrix by feature macro and compiler version, testing syntax, handlers, debug information, and optimized builds separately. Enable observe mode in tests and canaries, compare violation rate and overhead, then increase enforcement gradually. Traditional assert has macro expansion, NDEBUG, and side-effect assumptions that cannot be mechanically equated; audit each use, preserve failure semantics, and use an adapter when uniform reporting is required.

High-quality sample answer

I treat Contracts as executable design constraints, not as input validation or an exception system. pre constrains the caller, post constrains the callee, and contract_assert constrains a function-body invariant. Every predicate stays read-only because an implementation may elide or repeat evaluation, so I prohibit I/O, counter mutation, and resource release. Violations go through one handler that emits structured diagnostics; build policy then chooses observation, enforcement, or fail-fast behavior, and the code does not assume an exception. The request boundary still performs authentication, authorization, and untrusted-data checks. For migration I create a compiler capability matrix, start with tests and canaries, and raise enforcement gradually; I audit NDEBUG and macro side effects instead of replacing assert mechanically. This gives stable internal checks without making online recovery depend on non-uniform implementation behavior.

Common mistakes

  • Treating pre as a complete security check for every input.
  • Logging, incrementing metrics, or mutating objects inside predicates.
  • Claiming a violation must throw or must terminate, regardless of mode.
  • Replacing every assert with contract_assert and missing NDEBUG, macro arguments, or side effects.
  • Describing C++26 postconditions with a nonexistent general old(...) syntax.

Follow-ups and responses

What if the interviewer asks, “Why not use exceptions everywhere?”

Contracts state caller and callee responsibility and can participate in review and build policy; exceptions define control flow and recovery. They complement each other and are not interchangeable.

What if repeated predicate evaluation is too expensive?

Keep predicates read-only and cheap, then measure each build mode. Put expensive diagnostics on an explicit path instead of hiding side effects in a contract.

What if they ask whether virtual functions can carry contracts directly?

C++26 has a boundary here: the WG21 roadmap lists virtual-function support as a later extension. Check the target compiler and standard version rather than presenting a future proposal as existing C++26 behavior.

Public sources

Related questions

Related interview tool

Use Screenshot for a coding prompt

Capture the problem, then work through the constraints, solution, code, edge cases, and complexity in order.

View the tool