https://gcc.gnu.org/g:1c2e59eccb78aadf3f4e88edc0f6a1dd84c01d07
commit r17-2384-g1c2e59eccb78aadf3f4e88edc0f6a1dd84c01d07 Author: Jakub Jelinek <[email protected]> Date: Tue Jul 14 08:00:30 2026 +0200 c++: Default deallocating functions to noexcept for C++11 https://eel.is/c++draft/except.spec#9 says A deallocation function with no explicit noexcept-specifier has a non-throwing exception specification. and something like that is there back to C++11. We only imply noexcept for destructors though (in deduce_noexcept_on_destructor). The following patch does that for operator delete/operator delete[] both for :: namespace ones and for class member functions. On Mon, Jul 13, 2026 at 04:34:12PM -0400, Jason Merrill wrote: > Does this affect mangling? I've tried to compile struct A {}; void operator delete (void *, A) {} struct B {}; void operator delete (void *, B) {} struct C { static void operator delete (void *); }; void C::operator delete (void *) {} struct D {}; void operator delete (void *, D) noexcept (true) {} template <bool B> struct E { template <typename T> static void operator delete (void *, T) noexcept (B) {} }; auto f1 () { return &E <true>::operator delete<int>; } auto f2 () { return &E <false>::operator delete<int>; } with g++ 15, vanilla trunk and patched trunk and everything is identical except .ident, mangled names _ZdlPv1A _ZdlPv1B _ZN1CdlEPv _ZdlPv1D _Z2f1v _Z2f2v _ZN1EILb1EEdlIiEEvPvT_ _ZN1EILb0EEdlIiEEvPvT_ If I add template <typename T> void bar () {} void baz () { bar <decltype (C::operator delete)> (); } to that testcase, then there is a difference, _Z3barIFvPvEEvv vs. _Z3barIDoFvPvEEvv. Isn't that desirable though? Note, clang++ mangles it the same as the patched g++, it is unlikely anybody does this in real-world code and they do actually want to test in that case whether it is explicitly or implicitly noexcept or not. Like when one uses reflection and tests is_noexcept, or uses noexcept on the destroying delete. 2026-07-14 Jakub Jelinek <[email protected]> * decl.cc (grokfndecl): If raises is NULL_TREE for C++11 deallocation function, use noexcept_true_spec instead. * g++.dg/cpp0x/dealloc1.C: New test. * g++.dg/cpp0x/dealloc2.C: New test. * g++.dg/cpp2a/destroying-delete7.C: New test. * g++.dg/reflect/is_noexcept5.C: New test. * g++.dg/cpp1z/aligned-new3.C (operator delete): Don't expect a warning. Reviewed-by: Jason Merrill <[email protected]> Diff: --- gcc/cp/decl.cc | 8 ++++++++ gcc/testsuite/g++.dg/cpp0x/dealloc1.C | 8 ++++++++ gcc/testsuite/g++.dg/cpp0x/dealloc2.C | 8 ++++++++ gcc/testsuite/g++.dg/cpp1z/aligned-new3.C | 2 +- gcc/testsuite/g++.dg/cpp2a/destroying-delete7.C | 10 ++++++++++ gcc/testsuite/g++.dg/reflect/is_noexcept5.C | 25 +++++++++++++++++++++++++ 6 files changed, 60 insertions(+), 1 deletion(-) diff --git a/gcc/cp/decl.cc b/gcc/cp/decl.cc index 340bbe71b9df..d10290752e7b 100644 --- a/gcc/cp/decl.cc +++ b/gcc/cp/decl.cc @@ -12166,6 +12166,14 @@ grokfndecl (tree ctype, return NULL_TREE; } + /* [except.spec]/9 - A deallocation function with no explicit noexcept-specifier + has a non-throwing exception specification. */ + if (raises == NULL_TREE + && cxx_dialect >= cxx11 + && IDENTIFIER_NEWDEL_OP_P (declarator) + && !IDENTIFIER_NEW_OP_P (declarator)) + raises = noexcept_true_spec; + type = build_cp_fntype_variant (type, rqual, raises, late_return_type_p); decl = build_lang_decl_loc (location, FUNCTION_DECL, declarator, type); diff --git a/gcc/testsuite/g++.dg/cpp0x/dealloc1.C b/gcc/testsuite/g++.dg/cpp0x/dealloc1.C new file mode 100644 index 000000000000..8c861d045803 --- /dev/null +++ b/gcc/testsuite/g++.dg/cpp0x/dealloc1.C @@ -0,0 +1,8 @@ +// { dg-do compile { target c++11 } } + +struct A {}; +void operator delete (void *, A); +void operator delete (void *, A) noexcept; +struct B {}; +void operator delete (void *, B) noexcept (true); +void operator delete (void *, B); diff --git a/gcc/testsuite/g++.dg/cpp0x/dealloc2.C b/gcc/testsuite/g++.dg/cpp0x/dealloc2.C new file mode 100644 index 000000000000..5a0a89257b7a --- /dev/null +++ b/gcc/testsuite/g++.dg/cpp0x/dealloc2.C @@ -0,0 +1,8 @@ +// { dg-do compile { target c++11 } } + +struct A {}; +void operator delete (void *, A); +void operator delete (void *, A) noexcept (false); // { dg-error "declaration of 'void operator delete\\\(void\\\*, A\\\) noexcept \\\(false\\\)' has a different exception specifier" } +struct B {}; +void operator delete (void *, B) noexcept (false); +void operator delete (void *, B); // { dg-error "declaration of 'void operator delete\\\(void\\\*, B\\\) noexcept' has a different exception specifier" } diff --git a/gcc/testsuite/g++.dg/cpp1z/aligned-new3.C b/gcc/testsuite/g++.dg/cpp1z/aligned-new3.C index 165c3771ab9d..d6fba98fd133 100644 --- a/gcc/testsuite/g++.dg/cpp1z/aligned-new3.C +++ b/gcc/testsuite/g++.dg/cpp1z/aligned-new3.C @@ -13,7 +13,7 @@ void* operator new (std::size_t n, std::align_val_t) } bool deleted = false; -void operator delete (void *p, std::size_t, std::align_val_t) // { dg-warning "exception specifier" } +void operator delete (void *p, std::size_t, std::align_val_t) { deleted = true; operator delete (p); diff --git a/gcc/testsuite/g++.dg/cpp2a/destroying-delete7.C b/gcc/testsuite/g++.dg/cpp2a/destroying-delete7.C new file mode 100644 index 000000000000..468cc315d11a --- /dev/null +++ b/gcc/testsuite/g++.dg/cpp2a/destroying-delete7.C @@ -0,0 +1,10 @@ +// { dg-do compile { target c++20 } } + +#include <new> + +struct T { + ~T () noexcept (false); + static void operator delete (T *, std::destroying_delete_t); +}; +T *p = nullptr; +static_assert (noexcept (delete (p))); diff --git a/gcc/testsuite/g++.dg/reflect/is_noexcept5.C b/gcc/testsuite/g++.dg/reflect/is_noexcept5.C new file mode 100644 index 000000000000..854c3291fbe5 --- /dev/null +++ b/gcc/testsuite/g++.dg/reflect/is_noexcept5.C @@ -0,0 +1,25 @@ +// { dg-do compile { target c++26 } } +// { dg-additional-options "-freflection" } +// Test std::meta::is_noexcept. + +#include <meta> +#include <new> + +struct A { + ~A (); +}; +static_assert (is_noexcept (^^A::~A)); +struct B { + ~B () noexcept (false); +}; +static_assert (!is_noexcept (^^B::~B)); +struct C { + ~C () noexcept (false); + static void operator delete (void *); +}; +static_assert (is_noexcept (^^C::operator delete)); +struct D { + ~D () noexcept (false); + static void operator delete (D *, std::destroying_delete_t); +}; +static_assert (is_noexcept (^^D::operator delete));
