Last Update:
C++26: Standard Library Hardening Experiments
Table of Contents
“Hardening” seems to be a very popular term in the C++ World in 2026. In this article we’ll explore what this word means and see some core examples. Can a hardened library make C++ fully safe? Let’s find out.
The Core Idea
When you learned about std::vector you may remember that you can access an element at the i-th position using at least two expressions:
std::vector<int> v { 1, 2, 3, 4 };
v[i] = 10; // for some i
v.at(j) = 11; // for some j
The main difference between those two is that [] is unchecked (and can generate undefined behaviour if you try to access an element which is not there), while .at() may throw std::out_of_range (so it’s a well defined behaviour).
C++26 Changes
In C++26, the Standard introduces the notion of a hardened implementation. Whether a standard-library implementation is hardened, and how that mode is enabled, is implementation-defined.
For std::vector<T, Allocator>::operator[](size_type pos):
| C++ Standard | Condition |
|---|---|
| until C++26 | If pos < size() is false, the behavior is undefined. |
| since C++26 | If pos < size() is false: If the implementation is hardened, a contract violation occurs, If the implementation is not hardened, the behavior is undefined. |
In other words, if you switch this “hardened” mode you’ll get some well specified error/violation rather than just an undefined behaviour.
Let’s untangle the wording and common questions:
- For
.at()you may get an exception… so why do we need a new alternative? That’s fair question. In shortat()and[]has different interfaces and performance/error-handling approaches. What’s more important you cannot turn exceptions off easily (you can, andstd::terminatewill be called, but that’s not very flexible). - So Hardening does not change
operator[]intoat()- it detects a programming error and terminates instead of allowing memory-unsafe undefined behaviour. - “A contract violation occurs” - this is the key thing here. The “hardening” feature is expressed in terms of Contracts that also were accepted into C++26.
- C++26 specifies hardened preconditions using the new Contracts model: violating one in a hardened implementation causes a contract violation evaluated with a terminating semantic. However, a library implementation does not necessarily implement these checks using the actual
pre,post, orcontract_assertlanguage syntax. - So what is this contract violation? Ordinary C++26 Contracts may use ignore, observe, enforce, or quick-enforce semantics. Hardened Standard Library preconditions are more restrictive: in a hardened implementation they must use a terminating semantic, so execution cannot continue after a failed check. It’s implementation dependent on how to switch between those modes. Read more here: Contract assertions (since C++26) - cppreference.com
- Does it work in runtime? Yes, actually it can run in constant expressions, but, more importantly, it runs at runtime.
- How does this relate to things like
GLIBCXX_ASSERTIONS,_ITERATOR_DEBUG_LEVELand others? C++26 tries to bring those vendor specific checkers and create a common, well defined, set of rules.
The Main question:
How to enable this thing?
- GCC / libstdc++:
_GLIBCXX_ASSERTIONSenables lightweight Standard Library precondition checks. GCC’s broader-fhardenedoption enables it automatically together with other security options. - Clang / libc++: use
_LIBCPP_HARDENING_MODE, withNONE,FAST,EXTENSIVE, andDEBUGmodes. - MSVC STL:
_MSVC_STL_HARDENING=1enables hardening globally. Individual types can be controlled with macros such as_MSVC_STL_HARDENING_VECTORand_MSVC_STL_HARDENING_OPTIONAL.
Note: At the time of writing (August 2026), compiler and library vendors are still completing the C++26 feature. The options below are the current vendor hardening mechanisms and do not necessarily represent complete implementations of P3471/P3697/P3878
Core documents and proposals
We have the following papers that make the whole feature, as of C++26:
- P3471 - main Standard library hardening
- P3697 - Minor additions to C++26 standard library hardening - basic_stacktrace, shared_ptr<T[N]>, view_interface (front, back), counted_iterator, common_iterator
- P3878 - Standard library hardening should use a terminating semantic. Ensures that a hardened-precondition violation cannot simply be observed and then continue into the UB that hardening was intended to prevent.
Hardening Modes — libc++ documentation
To specify hardening in the Standard, this proposal introduces the notion of a hardened precondition. A hardened precondition is a precondition that results in a contract violation in a hardened implementation. Adding hardening to the library largely consists of turning some of the existing preconditions into hardened preconditions in the specification.
What conditions are candidates to get the hardened implementation?
- Violating the precondition results in a memory safety issue (an out-of-bounds access or an access to uninitialized memory);
- The call site has all the necessary data to perform the check;
- The check can be done in constant time and imposes relatively little overhead.
C++26 hardened conditions
Here’s a summary of what conditions/member functions are checked:
| Category | Classes / types | Hardened operations |
|---|---|---|
| Sequence containers | array, vector, inplace_vector, deque, list, forward_list |
operator[], front(), back(), pop_front(), pop_back() |
| Container views | span, mdspan, view_interface |
construction, operator[], front(), back(), first(), last(), subspan() |
| Iterator adaptors | common_iterator, counted_iterator |
construction, operator*, operator->, operator[], operator++, arithmetic, comparisons, iter_move, iter_swap |
| Strings | basic_string, basic_string_view |
operator[], front(), back(), pop_back(), remove_prefix(), remove_suffix() |
| General utilities | bitset, optional, expected |
operator[], operator*, operator->, error() |
| Stacktrace | basic_stacktrace |
current(), operator[] |
| Smart pointers | shared_ptr<T[N]> |
operator[] |
| Numeric arrays | valarray |
operator[] |
At cppreference.com there’s a cool table that summarizes all conditions and standard library types. See “Functions with hardened preconditions” at https://en.cppreference.com/cpp/standard_library
A Basic Example
Let’s start with a basic “hello world” example. We see the default compiler behaviour, and then how does it change with the hardening options.
#include <vector>
#include <iostream>
int main() {
std::vector<int> v { 1, 2, 3 };
int a = 10;
std::cin >> a;
v[a] = a;
std::cout << "hello world!";
}
Running on GCC 16.1 with just -std=c++26 and passing 100000 as input:
Program returned: 139
Program stderr
/cefs/38/383ad2f84cbd57a52fd68bbe_consolidated/compilers_c++_x86_gcc_16.1.0/include/c++/16.1.0/bits/stl_vector.h:1253: constexpr std::vector<_Tp, _Alloc>::reference std::vector<_Tp, _Alloc>::operator[](size_type) [with _Tp = int; _Alloc = std::allocator<int>; reference = int&; size_type = long unsigned int]: Assertion '__n < this->size()' failed.
Program terminated with signal: SIGSEGV
Hmm… is it already hardened by default?
With GCC 16.1 we don’t even have to explicitly enable hardening in an unoptimized build. Current libstdc++ enables _GLIBCXX_ASSERTIONS by default when compiling without optimization. Once optimization is enabled, these assertions are disabled by default. So compile with -O2 and we get:
Program returned: 139
Program stderr
Program terminated with signal: SIGSEGV
See here @Compiler Explorer
In other words without optimizations, you could already have some runtime checks enabled by default.
On the other hand, to enable hardened mode in optimized GCC build we need to specify: -std=c++26 -O2 -D_GLIBCXX_ASSERTIONS
Program returned: 139
Program stderr
/cefs/38/383ad2f84cbd57a52fd68bbe_consolidated/compilers_c++_x86_gcc_16.1.0/include/c++/16.1.0/bits/stl_vector.h:1253: constexpr std::vector<_Tp, _Alloc>::reference std::vector<_Tp, _Alloc>::operator[](size_type) [with _Tp = int; _Alloc = std::allocator<int>; reference = int&; size_type = long unsigned int]: Assertion '__n < this->size()' failed.
Program terminated with signal: SIGSEGV
We can also use -fhardened that adds even more safety checks, for example:
-D_FORTIFY_SOURCE=3
-D_GLIBCXX_ASSERTIONS
-ftrivial-auto-var-init=zero
-fPIE -pie
-Wl,-z,relro,-z,now
-fstack-protector-strong
-fstack-clash-protection
-fcf-protection=full
On Clang Trunk I’m getting the following:
compiled with: -std=c++26 -stdlib=libc++ -D_LIBCPP_HARDENING_MODE=_LIBCPP_HARDENING_MODE_DEBUG
Program stderr
vector.h:414: libc++ Hardening assertion __n < size() failed: vector[] index out of bounds
Program terminated with signal: SIGSEGV
Note: libc++ offers NONE, FAST, EXTENSIVE, and DEBUG hardening modes. I’m using DEBUG here because it prints a useful diagnostic; libc++ recommends FAST for most production applications.
Real-World Bugs Beyond std::vector
Would you like to see more?
In the extended version of the article @Patreon, we describe the following bugs that were found with enabling the hardening mode: Calling `std::deque::back()` on an Empty Container and Dereferencing an Empty `std::optional`
See all Premium benefits here.
How much does it cost at runtime?
Would you like to see more?
In the extended version of the article @Patreon, we discuss some basic assembler outputs, plus real-life experiments (done by some large companies).
See all Premium benefits here.
Summary
In the text we looked at the important C++26 feature “Standard Library hardening”. We started with the classic example of std::vector::operator[], where an out-of-bounds index used to mean UB. In a hardened implementation, selected Standard Library preconditions are checked and violations use terminating semantics instead.
We also saw that hardening is broader than bounds checking. It covers cases such as:
- accessing
front()orback()on an empty container, - dereferencing a disengaged
std::optional, - using
std::expectedin the wrong state, - invalid operations on
span,string_view, iterators,shared_ptr<T[N]>, and other library types.
We also looked at the three main papers behind the C++26 feature: P3471, P3697, and P3878. Together they define which preconditions are hardened and, importantly, require hardened violations to use terminating semantics rather than allowing execution to continue.
The implementation side is still very much in progress. The Standard deliberately leaves the mechanism for enabling a hardened implementation to vendors, and the major libraries currently expose different approaches:
- libstdc++ uses existing mechanisms such as
_GLIBCXX_ASSERTIONS, also enabled as part of GCC’s broader-fhardenedoption; - libc++ provides several hardening modes such as
FAST,EXTENSIVE, andDEBUG; - MSVC STL uses
_MSVC_STL_HARDENINGtogether with more fine-grained per-library-type switches.
Those implementations also do not necessarily use the actual C++26 pre, post, or contract_assert syntax internally. Compiler and Standard Library vendors are still completing and aligning their Contracts and hardening implementations.
So C++26 hardening does not suddenly make C++ memory safe, nor does it replace sanitizers, static analysis, good API design, or careful validation. What it does provide is a standardized baseline for turning several common and dangerous Standard Library precondition violations from silent undefined behaviour into detectable, terminating failures.
Extra links
I've prepared a valuable bonus for you!
Learn all major features of recent C++ Standards on my Reference Cards!
Check it out here:
