C++26: Trivial infinite loops are no longer undefined behaviour

C++26 changes how the language treats “trivial” infinite loops like `while (true);`, which were previously undefined behavior and could be optimized away in surprising ways, sometimes even causing control flow to fall into unrelated code. Commenters argue over whether this long‑standing rule ever made sense, pointing to real‑world use cases in embedded and kernel code where such loops are used to halt on error or wait for interrupts. The new standard’s choice to model these loops as calls to `std::this_thread::yield()` is seen by some as a pragmatic way to satisfy “forward progress” requirements, and by others as an unnecessary source of hidden system calls and complexity in a systems programming language already strained by undefined behavior.

Scope of the change and surprising behavior

  • Many commenters are shocked that trivial infinite loops were UB for “almost 1/6 of a century,” causing things like:
    • Functions “falling through” into subsequent functions when the compiler removes the loop and epilogue.
    • Non-void functions without returns just continuing into other code.
  • Some consider this a major violation of intuitive debugging expectations (“remove code until the problem disappears” fails if a minimal loop can do anything).

Why infinite loops were UB (optimization / forward progress)

  • Several posts link this to “forward progress guarantees”: compilers can assume threads eventually make progress (return, I/O, volatile, atomics).
  • UB on infinite loops enabled:
    • Loop merging or reordering without proving termination.
    • Transformations like hoisting stores above pure computations.
  • Some are unconvinced these optimizations are worth the semantic weirdness, calling it more about benchmark contests than real user benefit.

Embedded and systems use cases

  • Strong disagreement over “there’s never a good reason” for trivial infinite loops.
  • Embedded and bare-metal developers cite:
    • Halt-on-error handlers.
    • Main “super loops” on interrupt-driven systems.
    • Staying awake instead of expensive sleep/wake transitions.
  • Others argue you should use dedicated CPU halt/sleep instructions or error-indicating loops (e.g., blink codes), not empty spins.

New C++26 behavior (std::this_thread::yield)

  • The new rule defines a very narrow case: a trivially empty infinite loop with constant condition.
  • Criticism focuses on:
    • Replacing it with an implicit this_thread::yield() call, i.e., an invisible system call in code that appears side-effect-free.
    • Risk in freestanding / embedded / sandboxed environments or real-time settings.
    • Inconsistency: while(true); vs while(true) continue; vs other spellings having different semantics.
  • Some prefer this to UB, but still call it “horrible” and would rather see:
    • A diagnostic/warning.
    • A standard library “halt here” primitive.
    • Or the C11-style rule (infinite constant-condition loops are just defined to not terminate).

Broader UB and C++ direction concerns

  • Long subthread on UB as a tool for optimization versus a source of miscompilations and incomprehensible behavior.
  • Comparisons with C and Rust:
    • C’s special-casing of constant-expression loops seen as saner.
    • Rust’s loop {} and LLVM history are mentioned as prior pressure to fix this.
  • Several comments lament growing hidden behavior and complexity in modern C++, and express fear that freestanding / embedded use keeps getting harder.