C++26 Eliminates Undefined Behavior for Trivial Infinite Loops
C++26 adopts proposal P2809R3, resolving a critical divergence from C11 by making trivial infinite loops well-defined behavior. This change addresses compiler optimizations that previously stripped empty loops under the forward progress guarantee, which often led to unsafe fall-throughs in bare-metal halt-on-error handlers.
Under the C++11 forward progress guarantee (`[intro.progress]`), the implementation assumes that any thread will eventually terminate, call a library I/O function, access a volatile glvalue, or perform a synchronization or atomic operation. If a thread runs an infinite loop that performs none of these actions—such as `while (true);`—the execution has undefined behavior (UB).
Because the standard allowed compilers to assume execution never gets stuck in such loops, optimizing compilers (notably Clang) have historically optimized these loops away entirely. This results in the execution falling through to whatever code the linker placed immediately after the loop in memory. While this is mathematically consistent with the abstract machine's constraints, it breaks common bare-metal and embedded systems patterns where infinite loops act as "halt-on-error" traps:
```cpp if (hardware_init_failed()) { log_error("fatal: hardware init failed"); while (true); // Halt execution } ```
In this scenario, a compiler optimizing away the loop allows execution to fall through into whatever instruction happens to follow, causing the hardware to execute in a corrupt state. C11 avoided this issue by specifying that loops with a constant controlling expression may not be assumed to terminate. C++11 did not adopt this exception, leading to a long-standing divergence where `while (1);` was well-defined in C but UB in C++.
The C++26 Specification: P2809R3
C++26 addresses this divergence through P2809R3. Rather than adopting C’s broader exception—which could inhibit optimization passes on a wider class of loops—C++26 targets a strictly defined class: the trivial infinite loop.
To qualify as a trivial infinite loop, a construct must meet two conditions: 1. Trivially Empty Iteration Statement: The loop body must be structurally empty (either a null statement `;` or an empty compound statement `{}`). Any statement within the body, even a side-effect-free expression statement like `"a string";`, disqualifies it. 2. Constant True Controlling Expression: The controlling expression must be a constant expression that evaluates to `true`. For `for` loops with an omitted condition, `true` is implicit.
| Code | Trivial Infinite Loop? | Rationale | | :--- | :--- | :--- | | `while (true);` | Yes | Trivially empty, constant `true` | | `for (;;);` | Yes | Trivially empty, implicit `true` is a constant expression | | `do {} while (true);` | Yes | Trivially empty, constant `true` | | `constexpr bool go = true; while (go);` | Yes | `go` is a constant expression | | `while (true) { "a string"; }` | No | Body is not structurally empty | | `while (true) if (done) break;` | No | Body is not structurally empty | | `while (true) if constexpr (false) break;` | No | Syntactically not a trivially empty iteration statement | | `bool done = false; while (!done);` | No | Condition is not a constant expression |
Implementation Mechanics and Freestanding Caveats
When these conditions are met, the compiler replaces the loop body with a call to `std::this_thread::yield()`. This satisfies the forward progress guarantee by transforming the loop into an active participant in cooperative scheduling, ensuring the compiler can no longer optimize it away or assume execution passes it. Additionally, the forward progress guarantee itself is updated to allow a thread to "continue execution of a trivial infinite loop."
For bare-metal and kernel development on freestanding implementations, the replacement with `std::this_thread::yield()` is implementation-defined. This prevents the standard from forcing cooperative yield mechanisms on systems where no OS or thread scheduler exists, preserving the literal "halt" behavior.
P2809R3 was also accepted as a Defect Report (DR). Consequently, compiler implementers are permitted to apply these rules retroactively to older language standards (such as C++20 mode), which is why recent compiler toolchains may already preserve trivial infinite loops even when compiling under older standards.