---
title: "Deallocation Functions with Throwing Exception Specification Are Ill-formed"
document: P3424R2
date: 2026-06-11
audience: CWG
reply-to:
  - "Alisdair Meredith <ameredith1@bloomberg.net>"
---

## Abstract

Throwing from an overloaded `delete` operator is undefined behavior, yet `delete` operators have a non-throwing exception specification by default, leading to a deterministic call to `terminate` before any undefined behavior can occur. This paper suggests we can do better than “undefined behavior” for the remaining cases.

## Revision History

### June 2026 (Brno meeting)

— Dropped all references to entirely removing or deprecating exception specifications on deallocation functions — Updated reference to current working draft (no changes) — Added Annex C wording — Core wording review

— Dropped new note, — Dropped changes to 14.5 [[except.spec]](https://wg21.link/except.spec) — Retained old note referring to 14.5 [[except.spec]](https://wg21.link/except.spec)

### December 2025 (post-Kona)

— EWG feedback from Sofia, 2025; sent to Core for C++29 — Rebased onto current working draft — Removed the deprecation wording, per EWG review — Move some of the new wording around to flow better in the Standard text — Noted that this would resolve [CWG2042]

### December 2024 (post-Wrocław)

— Initial draft of this paper.

### 1

Unless a user provides an explicit `noexcept(false)` exception specification, all deallocation functions have a non-throwing exception specification:

### 14.5 [[except.spec]](https://wg21.link/except.spec) Exception specifications

9 A deallocation function (6.8.6.5.3 [[basic.stc.dynamic.deallocation]](https://wg21.link/basic.stc.dynamic.deallocation)) with no explicit *noexcept-specifier* has a non-throwing exception specification.

This then raises the question of what purpose a potentially throwing exception specification would serve. That question is answered by:

### 6.8.6.5.3 [[basic.stc.dynamic.deallocation]](https://wg21.link/basic.stc.dynamic.deallocation) Deallocation functions

4 If a deallocation function terminates by throwing an exception, the behavior is undefined. The value of …

Whatever else the user intended by allowing an exception to propagate from their deallocation function, the standard is very clear that once you leave that function you proceed straight to undefined behavior, with no window for well-defined behavior where the exception would have turned into a call to `std::terminate` if the default exception specification had been used.

#### 1.1

In preparing this paper, it was observed that the following test code will produce a `false` result for the `noexcept` operator, as-if the implicitly declared deallocation function had a potentially-throwing exception specification:

```cpp
struct T { ~T() noexcept(false); };
T * p = nullptr;
static_assert(noexcept(delete(p)));
                                      // this static_assert fails
```

What is happening is that the destructor for the type `T` is invoked *before* calling the deallocation function, and it is the exception specification on the destructor that is part of the whole expression causing the `noexcept` operator to return `false`. It is impossible to directly test the implicitly declared exception specification for most delete functions in this way.

However, we can extract a `noexcept` test on a deallocation function by testing a *destroying delete* function, which is expected to perform both the destruction of the supplied object and the recovery of any memory associated with that object — typically to support an extended allocation into the region of memory contiguously following said object.

:::wording-remove

<del>#include</del> <new> struct T { ~T() noexcept(false); static void operator delete(T*, std::destroying_delete_t); }; T * p = nullptr; static_assert(noexcept(delete(p)));

:::

#### 1.2

Unfortunately, the destroying delete test reveals implementation divergence.

Clang trunk and the EDG compiler follow the Standard specification.

MSVC triggers the `static_assert` because it checks the destructor in this case, even though it should not. If we provide a non-throwing destructor, the test passes, indicating that the implicitly non-throwing exception specification for the destroying delete function is implemented. Also, executing a test program shows that the destructor is never actually called at runtime — this is entirely an artefact of the `noexcept` operator.

Testing against the current trunk for gcc, we see the `static_assert` fires because it does not implement the implicitly non-throwing exception specification for destroying delete. We can then demonstrate the anticipated undefined behavior by throwing an exception from the destroying delete function and catching it — demonstrating that the function is called correctly, and truly lacks the implicit exception specification

### 2

As the only effect of adding a potentially-throwing exception specification to a deallocation function is to allow undefined behavior, we recommend that construct should be disallowed. However, this risks expose the gcc bug of not supplying the implicitly nonthrowing exception to any implementation of a destroying delete function — depending on whether their interpretation would be to make all destroying delete functions without an explicit exception specification ill-formed, or whether the bug would continue to propagate UB. Our hope is that gcc resolve this bug before it becomes an issue, but the timeline is tight if we were to consider this change for C++26.

### 3

This paper would resolve [CWG2042] filed in November 2014. That issue raises concerns that the current wording is contradictory so needs to be addressed somehow, and this proposal removes that contradiction by removing the possibility to reach the contradictory undefined behavior.

It might be relevant that the issue was originally filed when dynamic exception specifications were the expected use case.

### 4

#### 4.1

Concerns were raised that we are not motivated to (eventually) remove exception specifications on deallocation functions, so we should not deprecated them either. Otherwise there was strong consensus to forward the paper to Core. As we are past the deadline for C++26, this should be one of the first papers to land for C++29.

#### 4.2

Further simplified the paper by removing unnecessary notes and unnecessary changes to normative wording that introduced subtle errors.

Proof read and cleaned up the offered Annex C wording that now appears in this paper.

### 5

All wording is relative to [N5046], the latest working draft at the time of writing.

#### 6.8.6.5.3 [[basic.stc.dynamic.deallocation]](https://wg21.link/basic.stc.dynamic.deallocation) Deallocation functions

:::wording-add

3 Each deallocation function shall return void. <ins>A deallocation function shall not have a potentially throwing</ins> <ins>exception specification (14.5 [except.spec]).</ins>

:::

If the function is a destroying operator delete declared in class type `C`, the type of its first parameter shall be `C*`; otherwise, the type of its first parameter shall be `void*`. A deallocation function may have more than one parameter. A *usual* *deallocation* *function* is a deallocation function whose parameters after the first are

— optionally, a parameter of type `std::destroying_delete_t`, then — optionally, a parameter of type `std::size_t`,1 then — optionally, a parameter of type `std::align_val_t`.

A destroying operator delete shall be a usual deallocation function. A deallocation function may be an instance of a function template. Neither the first parameter nor the return type shall depend on a template parameter. A deallocation function template shall have two or more function parameters. A template instance is never a usual deallocation function, regardless of its signature.

:::wording-remove

4 <del>If a deallocation function terminates by throwing an exception, the behavior is undefined.</del> The value of the first argument supplied to a deallocation function may be a null pointer value; if so, and if the deallocation function is one supplied in the standard library, the call has no effect.

:::

#### 5.1

#### 5.2

:::wording-add

<ins>1 Subclause C.1 lists the differences between C++ and ISO C++ 2026, by the chapters of this document.</ins>

:::

:::wording-add

<ins>5.2.1</ins> <ins>C.1.2 Clause 6: basics [diff.cpp26.basic]</ins>

:::

##### 6.8.6.5.3 [[basic.stc.dynamic.deallocation]](https://wg21.link/basic.stc.dynamic.deallocation) Deallocation functions

## 1 Affected subclause: 6.7.5.5.3 [basic.stc.dynamic.deallocation]

:::wording-add

<ins>Change: Potentially throwing exception specifications on deallocation functions are ill-formed.</ins>

:::

:::wording-add

<ins>Rationale: Removes potential undefined behavior.</ins>

:::

:::wording-add

<ins>Effect on original feature: Valid C++ 2026 code that declares a deallocation function with a potentially</ins> <ins>throwing exception specification becomes ill-formed.</ins>

:::

:::wording-add

<ins>[Example 1:</ins>

:::

:::wording-add

<ins>struct T {</ins> <ins>void operator delete(void *) noexcept(false);</ins> <ins>// ill-formed; previously well-formed</ins> <ins>};</ins>

:::

:::wording-add

<ins>—end example]</ins>

:::

### 5.3

Close the following issues as resolved by this paper:

— [CWG2042]

1The global `operator` `delete(void*,` `std::size_t)` precludes use of an allocation function `void` `operator` `new(std::size_t,` `std::size_t)` as a placement allocation function (C.5.3 [[diff.cpp11.basic]](https://wg21.link/diff.cpp11.basic)).

### 6

Thanks to Michael Park for the pandoc-based framework used to transform this document’s source from Markdown.

Thanks to Hana Dusíková for calling attention to the interaction with destroying delete.

### 7

[CWG2042] Richard Smith. 2014-11-13. Exceptions and deallocation functions.

[https://wg21.link/cwg2042](https://wg21.link/cwg2042)

[N5046] Thomas Köppe. 2026-05-12. Working Draft, Programming Languages — C++.

[https://wg21.link/n5046](https://wg21.link/n5046)
