C++26: Reducing undefined behaviour | Sandor Dargo's Blog<br>Sandor Dargo's Blog<br>On C++, software development and books
HOME TAGS ARCHIVES BOOKS SPEAKING DAILY C++ WORKSHOPS HI... SUBSCRIBE
Blog 2026 07 29 C++26: Reducing undefined behaviour Post<br>Cancel
C++26: Reducing undefined behaviour<br>Sandor Dargo Jul 29 2026-07-29T00:00:00+02:00<br>7 min
Nowadays, safety is in the focus of C++. Whether it’s at committee meetings, conference talks, or hallway discussions, the topic keeps coming up. C++26 made some big steps forward in this area, and one recurring theme is the reduction of cases leading to undefined behaviour.<br>With the words of one of the presented proposals, “undefined behaviour has all but unbounded potential for bad program behaviour, including security leaks, data corruption, deadlocks, and so on”. The simple act of violating a precondition can silently turn a perfectly correct-looking program into one that exhibits any of these.<br>In today’s article, we look at how and where C++26 addresses this. A couple of these changes we have already covered on this blog, but they are worth reiterating in this context. And there is one important change we haven’t talked about yet. Let’s start with that.<br>P3144R2: Deleting a Pointer to an Incomplete Type Should be Ill-formed<br>Before C++26, deleting a pointer to an incomplete type was undefined behaviour. The only exception was when the complete class type happened to have a trivial destructor and no class-specific deallocation function. In all other cases, the compiler simply could not know what to do.<br>Consider this example:<br>// widget.h<br>struct Widget; // forward declaration only
void deleteWidget(Widget* p) {<br>delete p; // What destructor should be called?<br>// Is there a custom operator delete?
// widget.cpp<br>struct Widget {<br>~Widget() { /* non-trivial cleanup */ }<br>static void operator delete(void* p) { /* custom deallocation */ }<br>};
At the point of the delete expression, the compiler doesn’t know whether Widget has a non-trivial destructor or a custom operator delete - the former is quite often the case, while the latter is relatively rare. It cannot determine at translation time whether the program has well-defined behaviour or undefined behaviour - that determination depends upon information typically available only to the linker.<br>P3144R2 makes deleting a pointer to an incomplete class type ill-formed. Full stop. It’s now a compilation error.<br>The road to ill-formed<br>Interestingly, this wasn’t the initial direction. Several alternatives were considered which are presented in the proposal. Let’s enumerate a few of them:<br>Deprecation first : The original plan was to deprecate such deletes and only make them ill-formed in a later standard. The fear was that going straight to ill-formed would face adoption resistance.Require the compiler to do the right thing : Force the implementation to generate correct destructor calls and deallocation even for incomplete types. A nice idea, but nobody knew how to implement it efficiently.Don’t call the destructor : Define the behaviour as ending the object’s lifetime without running its destructor. This was combined with the concept of erroneous behaviour (borrowed from P2795R5) and deprecation.Ill-formed unconditionally : Including for types with trivial destructors. This would break some existing code that currently works fine, thanks to Hyrum’s Law.After thorough analysis, the originally preferred solution was the deprecation path combined with erroneous behaviour. The idea was to preserve well-defined behaviour in all cases but mark the pattern as deprecated. The erroneous behaviour classification would give compilers better diagnostics, both at compile time and at runtime.<br>But it turned out that the committee was more courageous. The courage is based on the fact that all major compilers already warn on such deletes without even trying to determine whether the complete type would have a trivial destructor. In other words, in practice, developers were already being told not to do this. So the committee went for the simplest and most impactful choice: make it ill-formed.<br>If you have existing code that deletes through an incomplete type, you’ll need to make the type complete at the point of deletion. This is the right thing to do anyway - it ensures the destructor actually runs.<br>P2795R5: Erroneous Behaviour for Uninitialized Reads<br>We covered this one in detail previously, but it fits perfectly into the theme of reducing undefined behaviour, so let’s recap.<br>Reading an uninitialized local variable used to be undefined behaviour. Starting from C++26, it becomes erroneous behaviour - a new category that means the behaviour is well-defined but incorrect. The compiler is recommended to diagnose it.<br>void foo() {<br>int d; // d has an erroneous value<br>bar(d); // erroneous behaviour, not UB anymore
Instead of the program silently doing anything (as UB permits), an uninitialized object is now initialized to an implementation-specific value. Reading...