| // RUN: %clang_cc1 -fsyntax-only -verify -fcxx-exceptions -Wno-unevaluated-expression -std=c++20 %s |
| |
| namespace std { |
| struct destroying_delete_t { |
| explicit destroying_delete_t() = default; |
| }; |
| |
| inline constexpr destroying_delete_t destroying_delete{}; |
| } |
| |
| struct Explicit { |
| ~Explicit() noexcept(false) {} |
| |
| void operator delete(Explicit*, std::destroying_delete_t) noexcept { |
| } |
| }; |
| |
| Explicit *qn = nullptr; |
| // This assertion used to fail, see GH118660 |
| static_assert(noexcept(delete(qn))); |
| |
| struct ThrowingDestroyingDelete { |
| ~ThrowingDestroyingDelete() noexcept(false) {} |
| |
| void operator delete(ThrowingDestroyingDelete*, std::destroying_delete_t) noexcept(false) { |
| } |
| }; |
| |
| ThrowingDestroyingDelete *pn = nullptr; |
| // noexcept should return false here because the destroying delete itself is a |
| // potentially throwing function. |
| static_assert(!noexcept(delete(pn))); |
| |
| |
| struct A { |
| virtual ~A(); // implicitly noexcept |
| }; |
| struct B : A { |
| void operator delete(B *p, std::destroying_delete_t) { throw "oh no"; } // expected-warning {{'operator delete' has a non-throwing exception specification but can still throw}} \ |
| expected-note {{deallocator has a implicit non-throwing exception specification}} |
| }; |
| A *p = new B; |
| |
| // Because the destructor for A is virtual, it is the only thing we consider |
| // when determining whether the delete expression can throw or not, despite the |
| // fact that the selected operator delete is a destroying delete. For virtual |
| // destructors, it's the dynamic type that matters. |
| static_assert(noexcept(delete p)); |