This document makes one narrow claim: that the mechanism P2900 defines for contract assertions is the wrong mechanism for checks whose purpose is to detect or mitigate core-language undefined behavior, as proposed by P3100. The claim does not depend on any hostile reading of contracts. It follows from what the contracts proposal says about itself: P3846R1 concedes that guaranteed enforcement is a different need from what P2900 provides, and that a portable in-code guarantee that a check is performed is not available in C++26. A check for undefined behavior that is not guaranteed to run is not a check.
Some proposals want to treat checks for undefined behavior as a form of contract assertion. In particular, P3100 and related papers that propose implicit contract assertions for core-language undefined behavior.
That is a mistake. Undefined behavior checks and contracts are not the same kind of thing.
A contract assertion, as specified in P2900, is optional with respect to the meaning of a correct program. It may be ignored, observed, or enforced depending on how you build things, and different translation units may get different semantics.
An undefined behavior check that is supposed to mitigate undefined behavior has to actually be done. If it might not run, you don't have a UB check; you have a hope.
So contracts are inappropriate for undefined behavior checks. There isn't some useful sense in which they are appropriate for that.
P3846R1, written by a lot of the designers and supporters of P2900, is pretty clear about the mismatch: guaranteed enforcement is a different need from what P2900 gives you, and portable in-code guarantees are not part of C++26.
People will say that optional and non-ignorable checks are intentionally
different needs; that mixed TU semantics are just how C++ works and no worse
than C assert; that labels might come later; that two
compiler implementations are enough; that a global violation handler is a
selling point. Those are addressed below. None of that makes contracts
appropriate for UB checks.
If the goal is to detect or mitigate undefined behavior, the check has to happen. It is not enough that the source looks like a check, or that some build of some translation unit might enforce it.
Contracts don't give you that.
As specified in P2900, the evaluation semantic is chosen outside the source.
A given assertion may be compiled as ignore.
For inline functions and templates in headers, different TUs may disagree
on the semantic. That issue was described in
P3835R0.
Concretely: an inline function in a header carries an implicit UB check.
Translation unit A is built with a checked semantic; translation unit B
includes the same header built with ignore. The program links.
In B’s calls the check is gone, and the undefined behavior it was meant
to stop is back, with nothing at the call site to indicate it.
Treating a UB check as "just another contract" imports exactly those properties: the check is redundant to program meaning, it can be ignored, and mixed-mode builds are allowed.
Those properties are fine for optional correctness annotations. They are wrong for undefined behavior mitigation.
A UB check that can be compiled away is not a substitute for defined behavior or for a check that you know will run.
P3846R1 Concern 1 describes the complaint this way:
A central concern is that P2900 provides no method to guarantee in code that a particular assertion, or all assertions in a given ‘component of a program’, will always be checked.
It then separates ordinary (optional) assertions from non-ignorable checks:
By contrast, non-ignorable checks, as sought by [P3835R0],
[US 25-052], [FI-071], and [RO 2-056], serve a fundamentally different need:
providing a guarantee that certain conditions are always verified because the
risk of continuing after a violation is unacceptable. Such guarantees can be
expressed today with ordinary control flow (e.g., if statements).
Standardising contract assertions doesn’t hinder the continued use of
this approach.
P3846R1 then lists what you need, with P2900 as adopted, to get guaranteed enforcement:
That said, the features added by P2900 can offer advantages even for non-ignorable checks — such as the ability to place checks on declarations and to route violations through a global, link-time replaceable handler. P2900 provides the following options to achieve this:The most direct way to achieve non-ignorable contract assertions with P2900 is to control how that contract assertion is built and to choose a checked semantic when building it. For non-inline functions, that is the choice made for the translation unit in which the function is defined. For inline functions, as in the example from [P3835R0], control requires having a consistent build configuration across all translation units that compile the function. Alternatively, in some situations, force-inlining the function or giving it internal linkage is a viable strategy to reduce the decision to a single translation unit. Where users control the build configuration of all relevant translation units, they can reliably enforce a chosen semantic.
- Use
preand configure the build to always compile it with a checked semantic.- Duplicate the check, using both
preon the declaration andifin the definition.- Use a vendor extension such as the attribute implemented in Clang
pre [[clang::contract_semantic("quick_enforce")]]- Continue to use
ifstatements until post-C++26 extensions become available.
Read plainly, this is the proposal’s own answer to “how do I guarantee this check runs,” and not one of the four answers is “write it as a contract assertion and rely on the standard.” Three of the four require build-wide discipline or a vendor extension; the fourth is to not use the feature. That is the case against using the feature for this purpose, made by its designers.
For an in-source guarantee, P3846R1 says labels are required -- and are not in C++26 (P3846R1 cites [P3400R1]; that work continues as P3400R4):
[P3400R1] adds labels, giving users the ability to control the evaluation-semantic in code. This is required to make optional checks more scalable and — as a side effect — makes non-ignorable checks directly expressible. This proposal is being actively pursued and has received productive feedback, but it is not yet ready to adopt and has not yet reached consensus, in any WG21 subgroup, to move forward. Inclusion in C++26 is, therefore, not an option.
So the designers acknowledge that P2900 does not give you a portable
in-code guarantee that a check is done. You need all-TU build discipline,
a vendor extension, or an ordinary if.
That is not a description of a mechanism that is suitable for undefined behavior checks. That is a description of why contracts are the wrong tool for that job.
People say that optional assertions and non-ignorable checks are different goals by design, and that this is not a defect in contracts.
For undefined behavior checks, that is not complementarity. It is disqualification.
P3100 proposes to implement runtime checks for core-language undefined behavior as implicit contract assertions, using the same evaluation semantics as P2900. Undefined behavior mitigation is not asking for optional documentation of intent. It needs checks that are known to be done.
Calling that a "different need" does not rescue the design. It just says that contracts, as standardized, do not meet the need that UB checks have. A different need is precisely why it must not share the ignorable substrate, not a reason it may.
Telling people to keep writing if statements, or to wait for a
labels proposal that "has not yet reached consensus in any WG21 subgroup,"
is admitting that contracts are not the mechanism for checks that must run.
The line that "the problem is that they are not there" (P3500R1) does not
help. A check that is "there" in the source can still be compiled as
ignore in another translation unit. For undefined behavior,
that means the UB is still there. That is exactly the wrong outcome.
P3846R1 Concern 2 argues that mixed translation-unit semantics are
inevitable, that P2900 is better than C assert (which made mixed
mode IFNDR), and that the worst case -- a check that was supposed to run but
was ignored -- "is no worse than if contract assertions did not exist" and
"by design cannot introduce a new bug into an existing program."
That worst-case story is about redundant contract assertions. If you leave them out, a correct program still means the same thing. Undefined behavior checks are not redundant in that sense. If the check does not run, the undefined behavior remains.
ignore,
you do not have mitigation. You have source that looks checked and a
program that is not.ignore are normal. See the example in P3835R0.assert works the same way
misses the point. C assert was never proposed as the standard
mechanism for redefining or checking core-language undefined behavior.
Equating UB checks with contracts repeats that mistake in the standard
itself.P3846R1 says that requiring uniform semantics across an entire program would result in a feature "unusable at scale," and that requiring linker QoI for mixed mode would "greatly hinder adoption." That may say something about optional assertions. It also shows why that mechanism must not be reused for undefined behavior checks that have to be known to be done.
P3846R1 Concerns 14 and 15 argue for shipping a minimal core now and adding labels or always-enforce forms later.
That does not make contracts appropriate for undefined behavior checks in the meantime.
P3846R1 says labels are "required" to make non-ignorable checks directly expressible, are "not yet ready to adopt," and have not reached consensus to move forward in any subgroup.
Labels are, in effect, an attempt to make the P2900 model usable for checks that must run. The need for that repair is itself the point: the model being repaired is the wrong basis for undefined behavior checks, and the repair is not in C++26.
Undefined behavior checks cannot wait on an unfinished follow-on while being specified as if they were ordinary contracts. Either a check is known to be done, or it is not a UB check.
Putting P3100-style implicit UB checks on contracts now is not "constexpr started small." It is classifying must-run checks as ignorable assertions and promising to fix that later.
"We might add real enforcement later" is not a reason to standardize UB checks as contracts now.
There is also not enough usage experience to justify using the P2900 model for undefined behavior checks.
Building language-level UB mitigation on a facility whose deployment model (build-time evaluation semantics; mixed translation units; header / inline / template interactions) still cannot express "this check is known to be done" is premature.
P3846R1 Concern 17 might reply that P2900 is implemented in two major compilers and that macros have decades of prior art.
And the “two compilers” claim overstates it: as of mid-2026, GCC ships contracts only as an experimental option and Clang lists P2900R14 as unimplemented, so the deployed experience is with partial, opt-out prototypes of optional assertions, not with a facility fielded as the systemic answer to undefined behavior.
libc++ experiments that still need Clang-specific attributes to approximate component-level control (as both P3846R1 and P3835R0 note) show the gap; they do not close it.
The subject of whether or not it is reasonable to have a single contract handler for an entire program is still an open issue. It is open enough that local / per-class handlers are still being talked about as a post-C++26 extension (P3400, as cited by P3846R1 Concern 9).
P3846R1 argues that a global handler is a main reason to standardize contracts for optional library assertions under application control. That is not a reason to send large classes of core-language undefined behavior through one program-wide callback.
ignore.But in any case, having a single handler in a program for all undefined behavior is even more problematic. Routing UB through the contracts handler mechanism picks up both the open questions around a single global handler and the deeper problem that the check itself might not run.
P3846R1 highlights that contract assertions are redundant to correctness, independently checkable, and have evaluation semantics chosen outside the source. P3100 relies on those properties for implicit checks.
Those properties are why contracts are inappropriate for undefined behavior checks.
ignore.
That is unacceptable for a check that was supposed to stand in for UB.Equating the two is the mistake. Undefined behavior checks must be known to be done. Contracts, by design, are not that.
Nor is it an answer to say that a future profile or label will select which
checks are enforced. Selection presupposes the substrate. If the substrate
permits ignore, then what a profile selects is a preference, not
a guarantee, unless the guarantee is provided by a mechanism outside the
assertion model, at which point the assertion model is not doing the work and
need not be the specification vehicle. A mechanism that can only promise
“checked, where the build cooperates” answers a different
question than the one undefined behavior poses.
Contracts are inappropriate for undefined behavior checks.
Undefined behavior checks must be known to be done. Contract assertions, as specified in P2900, need not be done: they may be ignored, and different translation units may disagree on the semantic.
P3846R1 acknowledges that guaranteed enforcement is a different need from what P2900 provides, and that portable in-code guarantees are not available in C++26.
Treating UB checks as equivalent to contracts imports the wrong semantics.
Complementarity, mixed mode like C assert, ignore as harmless,
labels later, compiler prototypes, a global handler -- none of that changes
the basic point.
The test is simple and worth stating for the record: if a proposed check for
a core-language undefined behavior can, under any conforming build, be
compiled as ignore, it is not a mechanism for undefined behavior
and should not be specified as one.
Proposals such as P3100 that implement undefined behavior mitigation as implicit contract assertions should not proceed on that basis.