· via Hacker News – Front Page (hnrss.org)
C++26 hardening turns out-of-bounds standard library calls into contract violations
C++26 lets hardened standard library implementations report out-of-bounds and other precondition violations as terminating contract violations, with GCC, Clang and MSVC each exposing a switch.

What C++26 changes
Anyone who has used std::vector knows the split between v[i] and v.at(i): the first performs no bounds check and triggers undefined behaviour when the index is out of range, while the second throws std::out_of_range. C++26 adds a third possible outcome. The standard now defines the notion of a "hardened implementation", and for an out-of-range index passed to vector::operator[], a hardened implementation produces a contract violation rather than undefined behaviour, according to a C++ Stories article that recently reached the front page of Hacker News. Whether a given library implementation is hardened, and how the mode is switched on, is left implementation-defined.
Terminating, not throwing
The mechanism is deliberately not a copy of at(). As the article explains, hardening detects a programming error and stops execution; it does not offer a recoverable error path. Exceptions are awkward to switch off in practice, and at() and operator[] differ in interface and performance expectations anyway.
Hardening is expressed through the Contracts facility that was also accepted into C++26. Ordinary contract assertions can run with ignore, observe, enforce or quick-enforce semantics, but hardened library preconditions are more restrictive: they must use a terminating semantic, so a program cannot observe a failed check and then fall through into the very undefined behaviour hardening was meant to prevent. That guarantee comes from proposal P3878. Implementations do not have to use the actual pre, post or contract_assert language syntax to perform the checks, and the checks apply in constant expressions as well as at runtime.
Which operations are covered
The bulk of the feature is proposal P3471, with P3697 extending coverage to types such as basic_stacktrace, shared_ptr<T[N]>, view_interface, counted_iterator and common_iterator. A precondition becomes a hardened precondition when three conditions hold: violating it creates a memory-safety problem such as an out-of-bounds access or a read of uninitialized memory, the call site has all the data needed to perform the check, and the check runs in constant time with relatively little overhead.
The resulting coverage, which cppreference summarises in its table of functions with hardened preconditions, spans sequence containers (array, vector, inplace_vector, deque, list, forward_list) for operator[], front(), back(), pop_front() and pop_back(); views such as span, mdspan and view_interface; iterator adaptors; basic_string and basic_string_view, including remove_prefix() and remove_suffix(); bitset, optional and expected; shared_ptr<T[N]>; and valarray.
How to turn it on
Until vendors finish the C++26 work, the article points to the existing per-vendor switches, which C++26 effectively pulls into one common, well-defined ruleset:
- GCC and libstdc++: _GLIBCXX_ASSERTIONS, also enabled automatically by the broader -fhardened option.
- Clang and libc++: _LIBCPP_HARDENING_MODE, with NONE, FAST, EXTENSIVE and DEBUG levels.
- MSVC STL: _MSVC_STL_HARDENING=1, plus per-type macros such as _MSVC_STL_HARDENING_VECTOR and _MSVC_STL_HARDENING_OPTIONAL.
A demonstration in the article shows GCC 16.1 catching a bad index out of the box: in an unoptimized build, libstdc++ currently enables _GLIBCXX_ASSERTIONS by default, so writing far past the end of a three-element vector yields an assertion failure and a SIGSEGV. Compile with -O2, though, and the assertions are switched off again, leaving a plain segfault. The article cautions that as of August 2026 vendors are still completing the feature, so these switches do not yet necessarily represent full implementations of P3471, P3697 and P3878.
Why it matters
Out-of-bounds container access is one of the most common sources of memory corruption in C++ codebases, and today's mitigations are scattered across vendor-specific macro sets. C++26 folds those ad-hoc experiments into a single specified model with a terminating semantic, giving predictable, diagnosable failure instead of silent undefined behaviour — valuable both for debugging and for shrinking the attack surface of shipped software.
It does not make C++ fully memory-safe: raw pointer arithmetic and unchecked operations outside the covered list remain as dangerous as before, and the checks carry a runtime cost, kept small by the constant-time requirement. But for a language that still runs vast amounts of critical infrastructure, converting a whole class of library-level bugs into immediate, well-defined failures is a practical and long-awaited improvement.
- #cpp
- #cpp26
- #memory-safety
- #standard-library
- #compilers