C++ Tourniquet - Stricter Contracts

Document #: P4392R0 [Latest] [Status]
Date: 2026-10-14
Project: Programming Language C++
Audience: CWG
SG23 Safety and Security
Reply-to: Jarrad J. Waterloo
<>

1 Abstract

The, mostly likely, biggest impediment to any safety proposal that impacts the language is its being backwards compatible with existing code. However, for new code blocks that should not be an impediment. Consequently, new language features can and should be made as safe as possible. It would be beneficial to the C++ community if the standard put a moratorium on adding new language features where a programmer can write new code blocks, statements and expressions that have the same amount of undefined behavior as does the rest of the language. In other words, new language features should be made as safe as possible i.e. new language features should have less undefined behavior as the rest of C++. This request herein may be called the C++ Tourniquet or No MORE UB.

2 Motivation

Two new major language features was added to C++26. Reflection already exceeds the safety of existing code because it is mostly consteval. This means it benefits from not exhibiting “behavior explicitly specified as undefined”. [N5054]

3.66 [defns.undefined] [N5054]

undefined behavior

behavior for which this document imposes no requirements

[Note 1 to entry: … Evaluation of a constant expression (7.7.2) never exhibits behavior explicitly specified as undefined in Clause 4 through Clause 15. — end note]

Contracts unfortunately as a new language feature suffers from the same degree of undefined behavior as the rest of the language. Attempts to make it free from undefined behavior, i.e. strict, failed. It failed for two seemingly rational reasons.

  1. Making contracts free of undefined behavior by first adding conveyor [P3285R0] functions to the language would take too long. We are ~15 years into adding constexpr functions into the language and they are still not complete. Consequently, the 88% [Favor88Percent] of the C++ community that are in favor of contracts don’t want to put contracts off for ~15 more years.
  2. Making contracts free of undefined behavior by subsetting the language just in pre, post and contract_assert clauses would make contracts unusable.

Neither of which is entirely accurate. In order to understand why that is the case, we first need to review the history of these prior attempts.

Contracts always had an uphill battle when it came to safety. [P1995R1] 43%-56% of programmers weighing in on the contract use cases thought that contracts being free of undefined behavior was either a “must have” or a “nice to have”. Looking solely at “must have” the percentages were 30%-40%, which was more than enough to be a sustained concern throughout the C++26 release cycle.

Contracts — Use Cases [P1995R1]

…

crit.noundef

As a Critical Software Developer

In order to Avoid undefined behavior

I want to Have contract violation at run-time always have well-defined behavior

Must Have: 9, Nice to Have: 4, Not Important: 17

…

crit.noassume

As a Critical Software Developer

In order to Avoid unexpected or undefined behavior

I want to Ensure checks will never be __assume’d/__builtin_assume’d by the compiler as if they were facts injected into the program (otherwise, if such an assumption ever failed, I would be running a different program that is not equivalent to the one I wrote; assumptions can expand the set of possible executions by injecting facts not otherwise knowable to the compiler)

Must Have: 12, Nice to Have: 5, Not Important: 13

Throughout the C++26 release cycle there was sustained objections concerning contracts on the grounds of too much undefined behavior being permitted in the pre, post and contract_assert contract assertions. During the years 2022-2024, there were at least 13 votes on this very issue. [P2899R0] Despite the objections, 11 of the 13 votes resulted in “No consensus” and “Consensus against” doing anything about undefined behavior.

What is truly interesting are the two votes that favored less undefined behavior! The last vote kicked the bucket down the road to C++29 or later and all but ensured that contracts will have the same level of undefined behavior as the rest of the language. There were 25 people voting. However an earlier vote had 58 people voting on “contracts should expose less undefined behavior than regular C++ code does”.

Contracts for C++ — Rationale [P2899R0]

…

3.6.1 Undefined Behavior During Contract Checking

…

EWG, Tokyo, 2024-03-20, Poll 8

P2900r6: contracts should expose less undefined behavior than regular C++ code does

SF F N A SA
17 12 12 12 5

…

SG21, Wrocław, 2024-11-22, Poll 5

We support the direction of P3100R1 and encourage the authors to come back with a fully specified proposal.

SF F N A SA
19 6 0 0 0

Result: Consensus

That vote on “contracts should expose less undefined behavior than regular C++ code does” is similar in intent as another poll that resulted in a “Consensus against, but P2900 would be in danger of failure in plenary”.

EWG, Wrocław, 2024-11-19, Poll

As suggested in P3362R0 / P2680 / P3285, the contracts proposal in P2900’s Minimal Viable Product shall be changed to incorporate stricter contracts in addition to regular contracts.

SF F N A SA
10 6 3 14 16

Result: Consensus against, but P2900 would be in danger of failure in plenary

If we are all against unsafety, then why are we so divided and against freeing contracts from as much undefined behavior as possible. To the credit of the majority for contracts, the comments of the safety minority was investigated in papers such as Exploring strict contract predicates [P3499R0] and Contract assertions versus static analysis and 'safety' [P3376R0].

Exploring strict contract predicates [P3499R0]

EWG Poll, Wrocław, 2024-11-19

As suggested in P3362R0 / P2680 / P3285, the contracts proposal in P2900’s Minimal Viable Product shall be changed to incorporate stricter contracts in addition to regular contracts.

SF F N A SA
10 6 3 14 16

Result: Consensus against, but P2900 would be in danger of failure in plenary

While strict contracts as described above seem specifiable and implementable, it is worth highlighting that they are severely limited. Remember that in such strict predicates:

  • Any operation on values other than of built-in arithmetic or enumeration type is ill-formed;
  • Dereferencing any pointers is ill-formed;
  • Using any references to objects is ill-formed;
  • Calling any existing member function on any object is ill-formed.

Contract assertions versus static analysis and ‘safety’ [P3376R0]

1. The perception of safety

bool has_name(string const& s)  // reference may be dangling
{
  return !s.empty();        // may be accessing a non-object
}

It was concluded unanimously, SF/F, after that investigation to kick the undefined behavior ball down the road. However, that was premature. It would seem that the severe limitations was predicated on the unrealistic expectations of safety by both the majority of those for contracts and the minority opposed to contracts. This is highlighted by the two uncommented apositives, as described and in such.

Exploring strict contract predicates [P3499R0]

While strict contracts as described above seem specifiable and implementable, it is worth highlighting that they are severely limited. Remember that in such strict predicates:

  • Any operation on values other than of built-in arithmetic or enumeration type is ill-formed;
  • Dereferencing any pointers is ill-formed;
  • Using any references to objects is ill-formed;
  • Calling any existing member function on any object is ill-formed.

The freedom from undefined behavior that the majority/minority is wanting is different from what the C++ community is asking for.

The C++ community position comes as an alloy of 4 different variations

  1. No UB aspirational: Evaluation of a constant expression which can still have undefined behavior. It is just a proto erroneous behavior where the results varied from compiler to compiler.
  2. While current C++ code has an open set of undefined behavior, new code should start with a closed set of undefined behavior that defaults to no undefined behavior. Adding undefined behavior requires a proposal with examples so that programmmers know what they are responsible for.
  3. No UB [f]actual: Similar to evaluation of a constant expression but for non constant expressions. Pretty much freedom from compile time undefined behavior. If a compiler can detect undefined behavior, than it is honor bound to diagnose it.
  4. New code [constructs] should have as little undefined behavior as possible. Alternatively, it “should expose less undefined behavior than regular C++ code does”.

The difference between the C++ leadership and the C++ community is literally a case of “Perfect is the enemy of good”.

Perfect is the enemy of good [PerfectEnemyGood]

“Perfect is the enemy of good” is an aphorism that means insistence on perfection often prevents implementation of good improvements. The phrase suggests that achieving absolute perfection may be impossible, and that the pursuit of perfection should not prevent someone from recognizing or completing work that is imperfect yet still valuable.

The “NO undefined behavior” does not allow one to make contracts stricter because that strict code may be called by less strict code and call less strict code. Since the UB can originate in less strict code, it can cause problems in strict code. This restricts improving any code in isolation and gradually as everything, throughout the core language, must be elevated in safety at once.

The “no WRITING undefined behavior” position is ok with undefined behavior originating in less strict code. It is OK to call less strict code and to be called by less strict code. What programmers is getting here is that if there is a problem, it is not in the strict code!

What does this “no WRITING undefined behavior” position actually mean by example? Consider an earlier example.

Contract assertions versus static analysis and ‘safety’ [P3376R0]

1. The perception of safety

bool has_name(string const& s)  // reference may be dangling
{
  return !s.empty();        // may be accessing a non-object
}

The “no WRITING undefined behavior” position does not consider this to be a problem. While it is true that has_name has a use after free. The real problem, the creation of the dangling reference occurs outside of the contract assertion and it is in that other less strict code where the fix must reside. The difference between the compile time dangling reference/alias creation and the runtime use after free parallels the compile time unitialized memory creation with the runtime reading from uninitialized memory.

  More Compile Time More Run Time
Uninitialized Forced initialization
Zero Initialization
Uninitialized Read
Dangling Dangling Alias Creation Use After Free
bool has_name(string const& s)  // reference may be uninitialized
{
  return !s.empty();        // may be accessing a uninitialized-object
}

By permitting strict code to call less strict code and to be called by less strict code, at least 3 of the 4 severe limitations are resolved. The result is that new code can “expose less undefined behavior than regular C++ code does.

Exploring strict contract predicates [P3499R0]

While strict contracts as described above seem specifiable and implementable, it is worth highlighting that they are severely limited. Remember that in such strict predicates:

  • Any operation on values other than of built-in arithmetic or enumeration type is ill-formed;
  • Dereferencing any pointers is ill-formed;
  • Using any references to objects is ill-formed;
  • Calling any existing member function on any object is ill-formed.

For the last limitation, one can hardly consider “dereferencing any pointers is ill-formed” as severe since references are used way more than pointers and pointer comparisons are still permitted for dealing with null pointer dereference on top of what compilers are already doing with respect to protected memory.

Armed with this revised requirement of less undefined behavior, what could be done to improve contracts assertions? Before considering that, let’s have a quick glance at all of the assertions in the hardened library.

Standard Library Hardening [P3471R4]

idx < size() is true
empty() is false
Count <= size() is true
count <= size() is true
Offset <= size() && (Count == dynamic_extent || Count <= size() - Offset) is true
offset <= size() && (count == dynamic_extent || count <= size() - offset) is true
count == extent is true
(last - first) == extent is true
ranges::size(r) == extent is true
il.size() == extent is true
s.size() == extent is true
pos < size() is true
empty() is false
n <= size() is true
a.empty() is false
n < a.size() is true
static_extent(r) == dynamic_extent || static_extent(r) == other.extent(r) is true
n < size() is true
has_value() is true
has_value() is false

Minor additions to C++26 standard library hardening [P3697R0]

i >= 0
i < N
!empty() is true
length > 0 is true
n < length is true
i.length > 0 is true
x.length > 0 and y.length > 0 are true
n >= 0
n <= length is true
-n <= length is true
x.v_.valueless_by_exception() is false
holds_alternative<I>(v_) is true
x.v_.valueless_by_exception() and y.v_.valueless_by_exception() are each false
skip <= skip + max_depth is true
frame_no < size()

2.1 Uninitialized memory

2.1.1 Forced initialization

We could have required that all objects are initialized. Any opting out could come in future release but only if truly needed. Forced initialization is not a severe limitation of any of assertions in the standard library hardening. Even if someone wanted to write these assertions multiline for debugging purposes, even that does not require uninitialized memory because each variable would be initialized by the temporary of each sub expression.

2.1.2 Zero initialization

Zero initialization is a tolerable 2nd best choice for which there is already precedence in the standard. It is not as deliberate as forced initialization and zero can be a poor default. It is still better than the status quo of allowing uninitialized memory.

2.1.3 Full erroneous behavior

Erroneous behavior will never support globals as that requires whole program analysis, instead of local analysis. The intent is that one day both automatic and dynamically allocated objects will benefit from erroneous behavior but for C++26 it was rolled out only for automatic storage duration objects. Since backwards compatibility is not a limiter for new code constructs, erroneous behavior could have been applied to dynamically allocated objects in contract assertions but wasn’t.

2.1.4 The status quo - erroneous behavior

Instead of doing what was best for contracts and programmers, the majority/minority implicitly chose to do the worse and keep it at the same undefined behavior level as the rest of the language when there was 3 better choices. If we start with the most constrained, which is not at all severely restrictive, then the most constrained can be relaxed over time. However, the opposite is not true. It is much harder to constrain, after the fact because existing code has already came to depend upon the less constrained.

2.2 Dangling of the stack

As stated earlier, dangling is similar to uninitialized memory from a compile time versus runtime perspective. Two things could have been done to addressing dangling of the stack.

  1. Lifetime extend all temporaries in contract assertions. Don’t wait 12 years to fix contracts as was done for the range based for loop.
  2. Since compiler vendors have yet to agree on this one and likely will, than make the runtime aspects of dangling of the stack erroneous behavior.

It should be noted that contract assertions effectively return bool and as such can’t return a dangling alias.

2.3 Invalidation

Without unique mutable aliasing, invalidation is a runtime issue. It is less of an issue for contract assertions due to constification.

2.4 Dangling across thread like boundaries

This is inheritly runtime and consequently out of scope. However, this is mostly solved by banning aliases across thread boundaries unless they are provably safe. This makes it mostly a library, type based solution [P3874R1] rather than a contract solution. As a simple contract assertion only solution, it might be best just to ban creating thread like objects such as jthread and thread in the contract assertion. This makes sense as contract assertions should be fast and likely comparing 2 existing objects passed in as arguments. Even if someone is desperate to create a thread in a contract assertion, one could do this in any less strict function call as strict code can call less strict code and be called by less strict code.

2.5 Out of bounds reads and writes i.e. range access

While this is predominantly runtime, it should be noted that none of the hardened library assertions even use an indexer; core or otherwise. So emitting core runtime index check will always have zero impact on these most common assertions. Also when one performs a range access check, one is comparing a passed in index against a size/length not the indexer.

2.6 Null pointer check

Without a null/not null attribute, null pointer checks are predominantly runtime. It should be noted that none of the hardened library assertions even use a pointer directly. So emitting core pointer checks will always will have zero impact on these most common assertions. Also when one performs a pointer check, one is comparing a passed in pointer against whether it is null or not instead of dereferencing the pointer. It would seem the most important pointer check to emit is when performing contract assertions on member function which is already a prerequisite and for that reason should always be added for [[null]] pointers but never for reference, direct alias access or [[not null]] pointers.

2.7 Overflow and Underflow

Many other safe languages [IOU] [RIOU] [CSIOU] [J1IOU] [J2IOU] does not consider overflow and underflow to be a safety issue in release builds worthy of doing anything preventative against. This is in part for the fact that there are numerous ways of representing numbers each with their speed, simplicity and safety tradeoffs. Other than the status quo, there is no clear winner and as such some problems are not ready to be solved. It should be noted that what Rust does in Debug vs Release mode is essential a proto contract. [RIOU]

2.8 Other

The following are keywords and functions that are not used in the standard hardened library and consequently could be initially banned. These could be added back later but only if truly necessary. Keep in mind, any of these could still be used in less strict functions that are called by strict contracts.

3 Wording

3.66 [defns.undefined] [N5054]

undefined behavior

behavior for which this document imposes no requirements

[Note 1 to entry: … Evaluation of a constant expression (7.7.2), pre contract assertion, post contract assertion and contract_assert contract assertion never exhibits behavior explicitly specified as undefined in Clause 4 through Clause 15. — end note]

4 Impact on the standard

The C++ Tourniquet only affects new code, not any existing code. Applying the C++ Tourniquet to contracts results in stricter contracts which has up to 73% less explicit core language UB across ten basic categories identified by P3100R8 [P3100R8] all without having to enable it by some future profile. Many of these potential UB fixes are addressed via compile time subsetting, both by error and made to work, instead of with slower runtime checks as mostly proposed in P4277R0 [P4277R0]. The C++ Tourniquet is meant to also affect future code constructs where programmers write code such as pattern matching and do expressions. Their specific validation will differ from strict contracts to ensure they also provide the maximum safety appropriate for them. Since the strict contract validation applies to a conditional-expression [N5054], there may be other conditional expressions in the language that could benefit from a similar but different restriction set.

The proposed changes are relative to the current working draft N5054 [N5054].

References

[CSIOU] The checked and unchecked statements (C# reference).
https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/statements/checked-and-unchecked?redirectedfrom=MSDN
[Favor88Percent] C++26 is done! — Trip report: March 2026 ISO C++ standards meeting (London Croydon, UK).
https://herbsutter.com/2026/03/29/c26-is-done-trip-report-march-2026-iso-c-standards-meeting-london-croydon-uk/
[IOU] Integer overflow.
https://en.wikipedia.org/wiki/Integer_overflow
[J1IOU] Overflow and Underflow in Java.
https://www.baeldung.com/java-overflow-underflow
[J2IOU] How does Java handle integer underflows and overflows and how would you check for it?
https://stackoverflow.com/questions/3001836/how-does-java-handle-integer-underflows-and-overflows-and-how-would-you-check-fo
[N5054] 2026. Working Draft, Programming Languages – C++.
https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2026/n5054.pdf
[P1995R1] 2020. Contracts — Use Cases.
http://wg21.link/P1995R1
[P2899R0] 2025. Contracts for C++ — Rationale.
http://wg21.link/P2899R0
[P3100R8] 2026. A framework for systematically addressing undefined behaviour in the C++ Standard.
http://wg21.link/P3100R8
[P3285R0] 2024. Contracts: Protecting The Protector.
https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2024/p3285r0.pdf
[P3376R0] 2024. Contract assertions versus static analysis and “safety.”
http://wg21.link/P3376R0
[P3471R4] 2025. Standard library hardening.
http://wg21.link/P3471R4
[P3499R0] 2026. Exploring strict contract predicates.
https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2025/p3499r0.pdf
[P3697R0] 2025. Minor additions to C++26 standard library hardening.
http://wg21.link/P3697R0
[P3874R1] 2026. Should C++ be a memory-safe language?
http://wg21.link/P3874R1
[P4277R0] 2026. Overview and Implementation Report for P3100.
http://wg21.link/P4277R0
[PerfectEnemyGood] Perfect is the enemy of good.
https://en.wikipedia.org/wiki/Perfect_is_the_enemy_of_good
[RIOU] The Rust Programming Language - Integer Overflow.
https://doc.rust-lang.org/book/ch03-02-data-types.html#integer-overflow