[gcc r17-2384] c++: Default deallocating functions to noexcept for C++11

Jakub Jelinek via Gcc-cvs <[email protected]>
Newsgroups gmane.comp.gcc.cvs
Message-ID <[email protected]>
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));
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.