Article
How C++26 reduces undefined behavior: diagnostics and runtime checks
C++26 makes deletion through incomplete types and certain temporary-bound reference returns ill-formed, and introduces erroneous behavior for some uninitialized reads. Understanding compiler diagnostics separately from library hardening reveals what existing code gains and which conditions still matter.
Share
Koharu's reading tip
Look beyond the C++26 label: distinguish errors diagnosed by the compiler from checks controlled by your standard library configuration.

When C++26 promises less undefined behavior, a practical question follows: how many problems can rebuilding existing code actually reveal? The Sandor Dargo article featured by ISO C++ Blog provides a useful starting point.
Compile-time diagnostics and runtime checks operate in different places. Uninitialized values introduce another distinction: the classification of the incorrect behavior itself changes.
Understanding these boundaries helps identify what switching to C++26 can uncover, and what still needs attention in code or build configuration.
Type and lifetime rules move certain errors into compile-time diagnostics
Consider applying delete to a pointer where only a class forward declaration is visible. The definition needed to determine its destructor and class-specific deallocation behavior is unavailable there. P3144R2 makes deletion through such an incomplete class type ill-formed in C++26.
Previously, behavior was undefined if the completed class had a non-trivial destructor or a class-specific deallocation function; otherwise, this pattern was permitted. The exception disappears. Move deletion to a location where the class definition is visible. When hiding an implementation, defining the deletion function in the implementation file is one possible approach.
Another error visible from types is binding a returned reference to a temporary. P2748R5 addresses this initialization in a return statement. A function returning const double& can create a temporary double even when its return expression names a static int: the conversion matters, despite the original integer having a long lifetime.
Returning a value is a possible redesign. This rule targets a particular reference binding, rather than detecting every dangling reference. Also, ill-formed code requires a diagnostic; an implementation may subsequently accept it as an extension. Rejection under every compiler configuration is not guaranteed.
Uninitialized integers become more diagnosable, but still need initialization
Other mistakes depend on execution paths rather than just combinations of types. Reading a local variable before assigning its value is one example.
P2795R5 gives ordinary uninitialized automatic objects such as int an erroneous value, with reads classified as erroneous behavior. This remains incorrect behavior, for which diagnostics are recommended. The execution rules also permit termination; neither a warning nor continued execution is guaranteed.
Storage duration and attributes change the result. Under the value classification rules, these function-scope examples differ:
| Declaration and subsequent read | C++26 treatment |
|---|---|
Read int count; before assignment |
Erroneous behavior |
Read int count [[indeterminate]]; before assignment |
Undefined behavior |
Read an integer allocated with new int before assignment |
Undefined behavior |
Read int count{}; |
Read an initialized zero |
This comparison deliberately uses int; certain operations on unsigned char and std::byte have separate rules. Even if an implementation appears to supply zero, uninitialized reads are not correct program logic. Express the required initial value in the code.
Out-of-range access depends on library hardening configuration
A container size and an index may only become known at runtime. P3471R4 on standard library hardening introduces a framework for checking designated operation preconditions. Accessing index 3 in a three-element std::vector violates index < size(); dereferencing a disengaged std::optional is another covered operation.
A hardened implementation treats such a failure as a contract violation. With a non-hardened implementation, violating the precondition remains undefined behavior. Undefined behavior also follows if a non-terminating contract-violation handler returns. Checking an invalid access does not make it capable of producing a valid result.
Activation is implementation-specific. For example, libc++ Hardening Modes documents the compiler option -D_LIBCPP_HARDENING_MODE=_LIBCPP_HARDENING_MODE_FAST. This is a setting for builds using libc++; ask the vendor about its default. A consumer-side macro does not change the configuration of already compiled library components.
Checking coverage trades off against runtime cost. libc++ positions fast mode for production and debug mode for non-production use. Measure the selected mode against your workload rather than assuming one universal overhead figure.
Match C++26 mode against individual feature support
These distinctions determine how to evaluate an upgrade. As checked on October 10, 2026, the GCC status page and Clang status page list:
| Language feature | GCC listing | Clang listing |
|---|---|---|
| Restrict temporary-bound reference returns (P2748R5) | 14 | 19 |
| Reject incomplete-type deletion (P3144R2) | 15 | 19 |
| Erroneous behavior for uninitialized reads (P2795R5) | 16 | Not supported |
These are individual feature entries, not claims of complete C++26 support. Match your compiler and selected language mode against the status tables, then examine diagnostics for the affected deletion and reference-return patterns. Check library hardening separately.
How much can a rebuild reveal? As much as the implemented language rules and enabled runtime checks cover. Turn those facilities into practical improvements by fixing diagnosed type and lifetime mistakes, explicitly initializing values, and establishing the library checking configuration. That is how a standards change becomes an opportunity to find defects in an actual build.
Source
- Title: C++26: Reducing undefined behaviour -- Sandor Dargo
- URL: https://isocpp.org//blog/2026/10/cpp26-reducing-undefined-behaviour-sandor-dargo
Share
Related Articles
These articles share nearby categories or tags, so you can keep reading along the same thread.




