Bug 122671 - GCC fails to compile-time valid C++26 code using both constexpr placement new (P2747R2) and constexpr exceptions (P3068R6).
Summary: GCC fails to compile-time valid C++26 code using both constexpr placement new...
Status: RESOLVED FIXED
Alias: None
Product: gcc
Classification: Unclassified
Component: libstdc++ (show other bugs)
Version: 16.0
: P3 normal
Target Milestone: 16.0
Assignee: Jakub Jelinek
URL:
Keywords: c++26, rejects-valid
Depends on:
Blocks: constexpr
  Show dependency treegraph
 
Reported: 2025-11-13 15:29 UTC by Mateusz Zych
Modified: 2025-11-19 17:11 UTC (History)
4 users (show)

See Also:
Host:
Target:
Build:
Known to work:
Known to fail:
Last reconfirmed: 2025-11-18 00:00:00


Attachments

Note You need to log in before you can comment on or make changes to this bug.
Description Mateusz Zych 2025-11-13 15:29:07 UTC
Hello!

GCC-16 does not compile the following C++26 code, which AFAIK is valid:

  #include <new>
  #include <memory>

  consteval auto func ()
  {
      struct error { };

      struct object
      {
          constexpr explicit object (int value)
          {
              if (value < 0) { throw error { }; }
          }
      };

      try
      {
          struct storage
          {
              object* ptr;

              constexpr storage ()
              : ptr { std::allocator<object> { }.allocate(1) } { }

              constexpr ~storage ()
              { std::allocator<object> { }.deallocate(ptr, 1); }
          };

          auto object_storage = storage { };

          ::new(object_storage.ptr) object { -1 };
      }
      catch (error&) { }

      return true;
  }

  static_assert(func());

Compiler Explorer link: https://godbolt.org/z/K5K86qWvM

To me this looks like a bug in GCC, which is occurring when
an object is being created at compile-time via placement new,
inside a try & catch block, and its constructor throws an exception.

Thank you, Mateusz Zych
Comment 1 Jakub Jelinek 2025-11-18 11:19:06 UTC
To me this testcase looks to be invalid, but I think most likely undesirably so.
The wg21.link/P2747R2 paper made placement operator new constexpr.
At that time constexpr exceptions weren't in C++26, so that was all that was needed.
But later on when wg21.link/P3068R5 was voted in, the P2747R2 changes look insufficient.
The problem is that when you throw from constructor during operator new, it invokes placement operator delete.  And P2747R2 didn't touch that, see
http://eel.is/c++draft/new.delete.placement
and
http://eel.is/c++draft/new.syn
If I change in preprocessed source the
--- pr122671.ii	2025-11-18 12:05:55.474736993 +0100
+++ pr122671.ii	2025-11-18 12:17:23.568904481 +0100
@@ -282,10 +282,10 @@ void* operator new[](std::size_t, void*
   noexcept
 { return __p; }
 
-inline void operator delete (void*, void*)
+constexpr void operator delete (void*, void*)
   noexcept
 { }
-inline void operator delete[](void*, void*)
+constexpr void operator delete[](void*, void*)
   noexcept
 { }
 
then it compiles fine.
So, I think a LWG issue for this should be filed and make those placement operator deletes constexpr in C++26 too.

Testcase with some formatting cleanups for the testsuite:
#include <new>
#include <memory>

consteval auto
foo ()
{
  struct E {};
  struct O
  {
    constexpr explicit O (int x)
    {
      if (x < 0) { throw E {}; }
    }
  };

  try
  {
    struct S
    {
      O *s;
      constexpr S () : s { std::allocator <O> {}.allocate (1) } {}
      constexpr ~S () { std::allocator <O> {}.deallocate (s, 1); }
    };

    auto s = S {};

    ::new (s.s) O { -1 };
  }
  catch (E &)
  {
  }
  return true;
}

static_assert (foo ());
Comment 2 Jakub Jelinek 2025-11-18 14:05:56 UTC
https://cplusplus.github.io/LWG/issue4477
Comment 3 GCC Commits 2025-11-19 08:38:52 UTC
The master branch has been updated by Jakub Jelinek <jakub@gcc.gnu.org>:

https://gcc.gnu.org/g:5294e0a0b40674365d8f9190fc1d9c456ea8c150

commit r16-5411-g5294e0a0b40674365d8f9190fc1d9c456ea8c150
Author: Jakub Jelinek <jakub@redhat.com>
Date:   Wed Nov 19 09:38:17 2025 +0100

    libstdc++: Implement proposed resolution of LWG4477 [PR122671]
    
    This patch implements the proposed resolution of
    https://cplusplus.github.io/LWG/issue4477
    The PR complains that one can't successfully throw from constructor
    in placement new in a constant expression and catch that exception
    later on.  The problem is while P2747R2 made placement ::operator new
    and ::operator new[] constexpr, when the ctor throws it invokes also
    these weird placement ::operator delete and ::operator delete[]
    which intentionally perform no action, and those weren't constexpr,
    so constant expression evaluation failed.
    
    2025-11-19  Jakub Jelinek  <jakub@redhat.com>
    
            PR libstdc++/122671
            * libsupc++/new (::operator delete, ::operator delete[]): Implement
            proposed LWG4477 resolution.  Use _GLIBCXX_PLACEMENT_CONSTEXPR for
            placement operator deletes.
    
            * g++.dg/cpp26/constexpr-eh17.C: New test.
Comment 4 Jakub Jelinek 2025-11-19 08:43:46 UTC
Implemented for GCC 16.
Comment 5 Mateusz Zych 2025-11-19 17:11:07 UTC
Hello Jakub!

You're right! I haven't realized that the issue was with the P2747R2.
Agreed - since the operator placement delete "intentionally performs no action",
marking it constexpr should be non-controversial.

Thank you for submitting the LWG4477 issue and
committing the required change into GCC's libstdc++.
Hopefully your proposed resolution will be accepted into C++26.

Lastly, I confirm that your change works.
I tested the GCC trunk by compiling a code which throws an exception
during invocation of the std::vector<T, Alloc>::emplace_back().

Thank you, Mateusz Zych