On 9/23/26 7:34 AM, Jakub Jelinek wrote:
On Tue, Sep 22, 2026 at 04:49:06PM -0400, Jason Merrill wrote:
Though, I'd be afraid we could then evaluate side-effects in the placement
arguments multiple times.
Or pass both placement and alloc_call and use one in fn_type_unification
and the other in the actual call args?
This last sounds right.

This seems to work too (note, the other patch with convert_from_reference
passed x86_64 and i686 bootstrap/regtest).
So far lightly tested, ok for trunk if it passes full bootstrap/regtest?

OK.

2026-09-23  Jakub Jelinek  <[email protected]>

        P3769R1
        PR c++/125848
        * call.cc (build_op_delete_call_1): Implement C++26
        P3769R1 - Clarification of placement new deallocation.  Instead of
        calling instantiate_type to select placement deallocation function
        iterate over OVERLOAD if any, perform argument deduction for
        templates, ignore candidates with different parameter type lists
        or unsatisfied constraints and if only one candidate remains,
        use it, otherwise don't destruct on throw.  Add placement_args
        argument, pass it down to build_op_delete_call call and use in
        template argument deduction.
        (build_op_delete_call): Add placement_args argument and pass it
        down to build_op_delete_call_1.
        (build_coroutine_op_delete_call): Remove placement argument, pass
        down NULL_TREE and NULL to build_op_delete_call_1 as placement and
        placement_args.
        * cp-tree.h (build_op_delete_call): Add placement_args argument.
        (build_coroutine_op_delete_call): Remove placement argument.
        * init.cc (build_new_1): Pass down *placement as placement_args
        to build_op_delete_call for placement new.
        (build_vec_delete_1): Adjust build_op_delete_call callers.
        (build_delete): Likewise.  Remove {}s around single statement
        body.
        * coroutines.cc (build_coroutine_frame_delete_expr): Don't pass
        placement to build_coroutine_op_delete_call.
        * optimize.cc (build_delete_destructor_body): Adjust
        build_op_delete_call caller.

        * g++.dg/cpp26/placement-new1.C: New test.

--- a/gcc/cp/call.cc    2026-09-23 09:20:20.239210825 +0200
+++ b/gcc/cp/call.cc    2026-09-23 13:16:45.083711943 +0200
@@ -8075,6 +8075,8 @@ usual_deallocation_fn_p (tree fn)
     usual deallocation should be chosen in preference to the single argument
     version in a class context.
     PLACEMENT is the corresponding placement new call, or NULL_TREE.
+   PLACEMENT_ARGS is a pointer to vector containing the original placement
+   arguments, or NULL.
If this call to "operator delete" is being generated as part to
     deallocate memory allocated via a new-expression (as per [expr.new]
@@ -8085,7 +8087,8 @@ usual_deallocation_fn_p (tree fn)
  static tree
  build_op_delete_call_1 (enum tree_code code, tree addr, tree size,
                        bool global_p, bool coro_p, tree placement,
-                       tree alloc_fn, tsubst_flags_t complain)
+                       vec<tree, va_gc> *placement_args, tree alloc_fn,
+                       tsubst_flags_t complain)
  {
    tree fn = NULL_TREE;
    tree fns, fnname, type, t;
@@ -8127,28 +8130,66 @@ build_op_delete_call_1 (enum tree_code c
if (placement)
      {
-      /* "A declaration of a placement deallocation function matches the
-        declaration of a placement allocation function if it has the same
-        number of parameters and, after parameter transformations (8.3.5),
-        all parameter types except the first are identical."
+      /* For a placement allocation function, the matching deallocation
+        function is selected as follows:
- So we build up the function type we want and ask instantiate_type
-        to get it for us.  */
+        - Each candidate that is a function template is replaced by the
+          function template specializations (if any) generated using template
+          argument deduction with arguments as specified below.
+        - Each candidate whose parameter-type-list is not identical to that
+          of the allocation function, ignoring their respective first
+          parameters, is removed from the set of candidates.
+        - Each candidate whose associated constraints (if any) are not
+          satisfied is removed from the set of candidates.
+        - If exactly one function remains, that function is selected.
+        - Otherwise, no deallocation function is selected.  */
        t = FUNCTION_ARG_CHAIN (alloc_fn);
-      t = tree_cons (NULL_TREE, ptr_type_node, t);
-      t = build_function_type (void_type_node, t);
+      fn = NULL_TREE;
+      tree *args = NULL;
+      unsigned nargs = 0;
+      for (tree elt : lkp_range (MAYBE_BASELINK_FUNCTIONS (fns)))
+       {
+         if (TREE_CODE (elt) == TEMPLATE_DECL)
+           {
+             tree targs = make_tree_vec (DECL_NTPARMS (elt));
+             if (args == NULL)
+               {
+                 nargs = vec_safe_length (placement_args);
+                 args = XALLOCAVEC (tree, nargs);
+                 args[0] = addr;
+                 for (unsigned i = 1; i < nargs; ++i)
+                   args[i] = (*placement_args)[i];
+               }
+             elt = fn_type_unification (elt, NULL_TREE, targs, args,
+                                        nargs, NULL_TREE,
+                                        DEDUCE_CALL, LOOKUP_NORMAL,
+                                        NULL, false, false);
+             if (elt == error_mark_node)
+               /* Instantiation failed.  */
+               continue;
+           }
- fn = instantiate_type (t, fns, tf_none);
-      if (fn == error_mark_node)
-       return NULL_TREE;
+         if (!constraints_satisfied_p (elt))
+           continue;
+
+         /* See if there's a match.  */
+         if (!compparms (t, FUNCTION_ARG_CHAIN (elt)))
+           continue;
- fn = MAYBE_BASELINK_FUNCTIONS (fn);
+         if (fn != NULL_TREE)
+           {
+             fn = NULL_TREE;
+             break;
+           }
+         else
+           fn = elt;
+       }
/* "If the lookup finds the two-parameter form of a usual deallocation
         function (3.7.4.2) and that function, considered as a placement
         deallocation function, would have been selected as a match for the
         allocation function, the program is ill-formed."  */
-      if (second_parm_is_size_t (fn))
+      if (fn && second_parm_is_size_t (fn))
        {
          const char *const msg1
            = G_("exception cleanup for this placement new selects "
@@ -8372,7 +8413,7 @@ build_op_delete_call_1 (enum tree_code c
    if (excluded_destroying
        && DECL_NAMESPACE_SCOPE_P (alloc_fn))
      return build_op_delete_call (code, addr, size, true, placement,
-                                alloc_fn, complain);
+                                placement_args, alloc_fn, complain);
/* [expr.new] @@ -8406,21 +8447,24 @@ build_op_delete_call_1 (enum tree_code c tree
  build_op_delete_call (enum tree_code code, tree addr, tree size, bool 
global_p,
-                     tree placement, tree alloc_fn, tsubst_flags_t complain)
+                     tree placement, vec<tree, va_gc> *placement_args,
+                     tree alloc_fn, tsubst_flags_t complain)
  {
    return build_op_delete_call_1 (code, addr, size, global_p, /*coro_p*/false,
-                                placement, alloc_fn, complain);
+                                placement, placement_args, alloc_fn,
+                                complain);
  }
/* Arguments as per build_op_delete_call_1 (). */ tree
  build_coroutine_op_delete_call (enum tree_code code, tree addr, tree size,
-                               bool global_p, tree placement, tree alloc_fn,
+                               bool global_p, tree alloc_fn,
                                tsubst_flags_t complain)
  {
    return build_op_delete_call_1 (code, addr, size, global_p, /*coro_p*/true,
-                                placement, alloc_fn, complain);
+                                /*placement*/NULL_TREE,
+                                /*placement_args*/NULL, alloc_fn, complain);
  }
/* Issue diagnostics about a disallowed access of DECL, using DIAG_DECL
--- a/gcc/cp/cp-tree.h  2026-09-14 10:37:36.000000000 +0200
+++ b/gcc/cp/cp-tree.h  2026-09-23 12:54:11.506705628 +0200
@@ -7359,11 +7359,10 @@ extern bool aligned_allocation_fn_p             (tr
  extern tree destroying_delete_p                       (tree);
  extern bool usual_deallocation_fn_p           (tree);
  extern tree build_op_delete_call              (enum tree_code, tree, tree,
-                                                bool, tree, tree,
-                                                tsubst_flags_t);
+                                                bool, tree, vec<tree, va_gc> *,
+                                                tree, tsubst_flags_t);
  extern tree build_coroutine_op_delete_call    (enum tree_code, tree, tree,
-                                                bool, tree, tree,
-                                                tsubst_flags_t complain);
+                                                bool, tree, tsubst_flags_t);
  extern bool can_convert                               (tree, tree, 
tsubst_flags_t);
  extern bool can_convert_standard              (tree, tree, tsubst_flags_t);
  extern bool can_convert_arg                   (tree, tree, tree, int,
--- a/gcc/cp/init.cc    2026-09-14 10:37:36.867105116 +0200
+++ b/gcc/cp/init.cc    2026-09-23 12:52:49.792792825 +0200
@@ -3898,14 +3898,12 @@ build_new_1 (vec<tree, va_gc> **placemen
        /* The Standard is unclear here, but the right thing to do
         is to use the same method for finding deallocation
         functions that we use for finding allocation functions.  */
-      cleanup = (build_op_delete_call
-                (dcode,
-                 alloc_node,
-                 size,
-                 globally_qualified_p,
-                 placement_allocation_fn_p ? alloc_call : NULL_TREE,
-                 alloc_fn,
-                 complain));
+      cleanup
+       = build_op_delete_call (dcode, alloc_node, size, globally_qualified_p,
+                               placement_allocation_fn_p
+                               ? alloc_call : NULL_TREE,
+                               placement_allocation_fn_p ? *placement : NULL,
+                               alloc_fn, complain);
if (cleanup && init_expr && !processing_template_decl)
        /* Ack!  First we allocate the memory.  Then we set our sentry
@@ -4380,12 +4378,11 @@ build_vec_delete_1 (location_t loc, tree
          virtual_size = size_binop (PLUS_EXPR, virtual_size, cookie_size);
        }
- deallocate_expr = build_op_delete_call (VEC_DELETE_EXPR,
-                                             base_tbd, virtual_size,
-                                             use_global_delete & 1,
-                                             /*placement=*/NULL_TREE,
-                                             /*alloc_fn=*/NULL_TREE,
-                                             complain);
+      deallocate_expr
+       = build_op_delete_call (VEC_DELETE_EXPR, base_tbd, virtual_size,
+                               use_global_delete & 1, /*placement=*/NULL_TREE,
+                               /*placement_args=*/NULL,
+                               /*alloc_fn=*/NULL_TREE, complain);
      }
body = loop;
@@ -5583,13 +5580,11 @@ build_delete (location_t loc, tree otype
      {
        head = get_internal_target_expr (build_headof (addr));
        /* Delete the object.  */
-      do_delete = build_op_delete_call (DELETE_EXPR,
-                                       head,
-                                       cxx_sizeof_nowarn (type),
-                                       /*global_p=*/true,
-                                       /*placement=*/NULL_TREE,
-                                       /*alloc_fn=*/NULL_TREE,
-                                       complain);
+      do_delete
+       = build_op_delete_call (DELETE_EXPR, head, cxx_sizeof_nowarn (type),
+                               /*global_p=*/true, /*placement=*/NULL_TREE,
+                               /*placement_args=*/NULL,
+                               /*alloc_fn=*/NULL_TREE, complain);
        /* Otherwise, treat this like a complete object destructor
         call.  */
        auto_delete = sfk_complete_destructor;
@@ -5600,13 +5595,11 @@ build_delete (location_t loc, tree otype
    else if (!virtual_p)
      {
        /* Build the call.  */
-      do_delete = build_op_delete_call (DELETE_EXPR,
-                                       addr,
-                                       cxx_sizeof_nowarn (type),
-                                       /*global_p=*/false,
-                                       /*placement=*/NULL_TREE,
-                                       /*alloc_fn=*/NULL_TREE,
-                                       complain);
+      do_delete
+       = build_op_delete_call (DELETE_EXPR, addr, cxx_sizeof_nowarn (type),
+                               /*global_p=*/false, /*placement=*/NULL_TREE,
+                               /*placement_args=*/NULL,
+                               /*alloc_fn=*/NULL_TREE, complain);
        /* Call the complete object destructor.  */
        auto_delete = sfk_complete_destructor;
        if (do_delete != error_mark_node)
@@ -5616,15 +5609,12 @@ build_delete (location_t loc, tree otype
        }
      }
    else if (TYPE_GETS_REG_DELETE (type))
-    {
-      /* Make sure we have access to the member op delete, even though
-        we'll actually be calling it from the destructor.  */
-      build_op_delete_call (DELETE_EXPR, addr, cxx_sizeof_nowarn (type),
-                           /*global_p=*/false,
-                           /*placement=*/NULL_TREE,
-                           /*alloc_fn=*/NULL_TREE,
-                           complain);
-    }
+    /* Make sure we have access to the member op delete, even though
+       we'll actually be calling it from the destructor.  */
+    build_op_delete_call (DELETE_EXPR, addr, cxx_sizeof_nowarn (type),
+                         /*global_p=*/false, /*placement=*/NULL_TREE,
+                         /*placement_args=*/NULL, /*alloc_fn=*/NULL_TREE,
+                         complain);
if (destroying_delete)
      /* The operator delete will call the destructor.  */
--- a/gcc/cp/coroutines.cc      2026-07-02 09:55:11.563487666 +0200
+++ b/gcc/cp/coroutines.cc      2026-09-23 12:54:49.082205693 +0200
@@ -4981,8 +4981,9 @@ build_coroutine_frame_delete_expr (tree
       one with two parameters.  */
    tree del_coro_fr
      = build_coroutine_op_delete_call (DELETE_EXPR, frame_arg, frame_size,
-                                     /*global_p=*/false,  /*placement=*/NULL,
-                                     /*alloc_fn=*/NULL, tf_warning_or_error);
+                                     /*global_p=*/false,
+                                     /*alloc_fn=*/NULL_TREE,
+                                     tf_warning_or_error);
    if (!del_coro_fr || del_coro_fr == error_mark_node)
      return error_mark_node;
    return del_coro_fr;
--- a/gcc/cp/optimize.cc        2026-03-27 10:17:14.026332502 +0100
+++ b/gcc/cp/optimize.cc        2026-09-23 13:16:03.817259733 +0200
@@ -138,12 +138,11 @@ build_delete_destructor_body (tree delet
    tree virtual_size = cxx_sizeof (current_class_type);
/* Call the delete function. */
-  tree call_delete = build_op_delete_call (DELETE_EXPR, current_class_ptr,
-                                          virtual_size,
-                                          /*global_p=*/false,
-                                          /*placement=*/NULL_TREE,
-                                          /*alloc_fn=*/NULL_TREE,
-                                          tf_warning_or_error);
+  tree call_delete
+    = build_op_delete_call (DELETE_EXPR, current_class_ptr, virtual_size,
+                           /*global_p=*/false, /*placement=*/NULL_TREE,
+                           /*placement_args=*/NULL, /*alloc_fn=*/NULL_TREE,
+                           tf_warning_or_error);
tree op = get_callee_fndecl (call_delete);
    if (op && DECL_P (op) && destroying_delete_p (op))
--- a/gcc/testsuite/g++.dg/cpp26/placement-new1.C       2026-09-23 
12:47:53.816732769 +0200
+++ b/gcc/testsuite/g++.dg/cpp26/placement-new1.C       2026-09-23 
12:47:53.816732769 +0200
@@ -0,0 +1,427 @@
+// P3769R1 - Clarification of placement new deallocation
+// { dg-do run { target c++11 } }
+
+namespace std
+{
+  using size_t = decltype (sizeof 0);
+}
+
+struct A {};
+int v;
+A a;
+template <int I>
+struct B {};
+B <0> b;
+template <int I>
+struct C {};
+C <0> c;
+template <int I>
+struct D {};
+D <0> d;
+struct T { T () = default; T (int) { throw 1; } };
+union U { unsigned char c[sizeof (T)]; T t; } u;
+void *foo ();
+struct V {
+  V () = default;
+  V (int) { throw 2; }
+  void *operator new (std::size_t, A &)
+  {
+    if (v != 5)
+      __builtin_abort ();
+    ++v;
+    return foo ();
+  }
+  template <int = 0>
+  void operator delete (void *, A &)
+  {
+    if (v != 6)
+      __builtin_abort ();
+    v += 2;
+  }
+  void *operator new (std::size_t, B <0> &)
+  {
+    if (v != 8)
+      __builtin_abort ();
+    v += 4;
+    return foo ();
+  }
+  template <int I>
+  void operator delete (void *, B <I> &)
+  {
+    __builtin_abort ();
+  }
+  void operator delete (void *, B <0> &)
+  {
+    __builtin_abort ();
+  }
+  void *operator new (std::size_t, C <0> &)
+  {
+    if (v != 12)
+      __builtin_abort ();
+    v += 8;
+    return foo ();
+  }
+#if __cpp_concepts >= 202002L
+  template <int I>
+  requires (I > 0)
+  void operator delete (void *, C <I> &)
+  {
+    __builtin_abort ();
+  }
+#endif
+  void operator delete (void *, C <0> &)
+  {
+    if (v != 20)
+      __builtin_abort ();
+    v += 16;
+  }
+};
+union W { unsigned char c[sizeof (V)]; V t; } w;
+
+void *
+foo ()
+{
+  return &w.c[0];
+}
+
+void *
+operator new (std::size_t, A &)            // #1
+{
+  if (v != 0)
+    __builtin_abort ();
+  ++v;
+  return &u.c[0];
+}
+
+template <int = 0>
+void
+operator delete (void *, A &)              // #2
+{
+  if (v != 1)
+    __builtin_abort ();
+  v += 2;
+}
+
+void *
+operator new (std::size_t, B <0> &)  // #3
+{
+  if (v != 3)
+    __builtin_abort ();
+  v += 4;
+  return &u.c[0];
+}
+
+template <int I>
+void
+operator delete (void *, B <I> &)    // #4
+{
+  __builtin_abort ();
+}
+
+void
+operator delete (void *, B <0> &)    // #5
+{
+  __builtin_abort ();
+}
+
+void *
+operator new (std::size_t, C <0> &)  // #6
+{
+  if (v != 7)
+    __builtin_abort ();
+  v += 8;
+  return &u.c[0];
+}
+
+#if __cpp_concepts >= 202002L
+template <int I>
+requires (I > 0)
+void
+operator delete (void *, C <I> &)    // #7
+{
+  __builtin_abort ();
+}
+#endif
+
+void
+operator delete (void *, C <0> &)    // #8
+{
+  if (v != 15)
+    __builtin_abort ();
+  v += 16;
+}
+
+void *
+operator new (std::size_t, double, int, float)
+{
+  if (v != 36)
+    __builtin_abort ();
+  v += 32;
+  return &u.c[0];
+}
+
+void
+operator delete (void *, double, int, float, int = 0, double = 0.0, float = 
0.0)
+{
+  __builtin_abort ();
+}
+
+void *
+operator new (std::size_t, double, int, long)
+{
+  if (v != 68)
+    __builtin_abort ();
+  v += 64;
+  return &u.c[0];
+}
+
+void
+operator delete (void *, double, int, long, int = 0, double = 0.0, float = 0.0)
+{
+  __builtin_abort ();
+}
+
+void
+operator delete (void *, double, int, long)
+{
+  if (v != 132)
+    __builtin_abort ();
+  v += 128;
+}
+
+void *
+operator new (std::size_t, unsigned, B <0> &, B <0> &)
+{
+  if (v != 9)
+    __builtin_abort ();
+  ++v;
+  return &u.c[0];
+}
+
+template <typename T, typename U>
+void operator delete (void *, unsigned, T &, U &)
+{
+  __builtin_abort ();
+}
+
+template <typename T>
+void operator delete (void *, unsigned, T &, T &)
+{
+  __builtin_abort ();
+}
+
+template <int I>
+void operator delete (void *, unsigned, B <I> &, B <I> &)
+{
+  __builtin_abort ();
+}
+
+void *
+operator new (std::size_t, unsigned long, B <0> &, B <0> &)
+{
+  if (v != 10)
+    __builtin_abort ();
+  v += 2;
+  return &u.c[0];
+}
+
+template <typename T, typename U>
+void operator delete (void *, unsigned long, T &, U &)
+{
+  __builtin_abort ();
+}
+
+template <typename T>
+void operator delete (void *, unsigned long, T &, T &)
+{
+  __builtin_abort ();
+}
+
+void *
+operator new (std::size_t, unsigned long long, B <0> &, B <0> &)
+{
+  if (v != 12)
+    __builtin_abort ();
+  v += 4;
+  return &u.c[0];
+}
+
+template <typename T, typename U>
+void operator delete (void *, unsigned long long, T &, U &)
+{
+  if (v != 16)
+    __builtin_abort ();
+  v += 8;
+}
+
+void *
+operator new (std::size_t, long long, B <0> &, B <0> &)
+{
+  if (v != 24)
+    __builtin_abort ();
+  v += 16;
+  return &u.c[0];
+}
+
+template <typename T, typename U>
+void operator delete (void *, long long, T &, U &) = delete;
+
+template <typename T>
+void operator delete (void *, long long, T &, T &) = delete;
+
+template <int I>
+void operator delete (void *, long long, B <I> &, B <I> &) = delete;
+
+void *
+operator new (std::size_t, D <0> &)
+{
+  if (v != 40)
+    __builtin_abort ();
+  v += 32;
+  return &u.c[0];
+}
+
+template <int I>
+void operator delete (void *, D <I> &)
+{
+  if (v != 72)
+    __builtin_abort ();
+  v += 64;
+}
+
+int
+main ()
+{
+  try
+    {
+      T *p = new (a) T (1); // OK, uses #1 and #2.
+      throw 1L;
+    }
+  catch (int)
+    {
+      if (v != 3)
+       __builtin_abort ();
+    }
+  try
+    {
+      T *p = new (b) T (1); // OK, uses #3.  No deallocation function is 
selected (two candidates remain).
+      throw 2L;
+    }
+  catch (int)
+    {
+      if (v != 7)
+       __builtin_abort ();
+    }
+  try
+    {
+      T *p = new (c) T (1); // OK, uses #7 and #9.
+      throw 3L;
+    }
+  catch (int)
+    {
+      if (v != 31)
+       __builtin_abort ();
+    }
+  v = 5;
+  try
+    {
+      V *p = new (a) V (1);
+      throw 4L;
+    }
+  catch (int)
+    {
+      if (v != 8)
+       __builtin_abort ();
+    }
+  try
+    {
+      V *p = new (b) V (1);
+      throw 5L;
+    }
+  catch (int)
+    {
+      if (v != 12)
+       __builtin_abort ();
+    }
+  try
+    {
+      V *p = new (c) V (1);
+      throw 6L;
+    }
+  catch (int)
+    {
+      if (v != 36)
+       __builtin_abort ();
+    }
+  try
+    {
+      T *p = new (0.0, 0, 0.0f) T (1);
+      throw 7L;
+    }
+  catch (int)
+    {
+      if (v != 68)
+       __builtin_abort ();
+    }
+  try
+    {
+      T *p = new (0.0, 0, 0L) T (1);
+      throw 8L;
+    }
+  catch (int)
+    {
+      if (v != 260)
+       __builtin_abort ();
+    }
+  v = 9;
+  try
+    {
+      T *p = new (0U, b, b) T (1);
+      throw 9L;
+    }
+  catch (int)
+    {
+      if (v != 10)
+       __builtin_abort ();
+    }
+  try
+    {
+      T *p = new (0UL, b, b) T (1);
+      throw 10L;
+    }
+  catch (int)
+    {
+      if (v != 12)
+       __builtin_abort ();
+    }
+  try
+    {
+      T *p = new (0ULL, b, b) T (1);
+      throw 12L;
+    }
+  catch (int)
+    {
+      if (v != 24)
+       __builtin_abort ();
+    }
+  try
+    {
+      T *p = new (0LL, b, b) T (1);
+      throw 13L;
+    }
+  catch (int)
+    {
+      if (v != 40)
+       __builtin_abort ();
+    }
+  try
+    {
+      T *p = new (d) T (1);
+      throw 14L;
+    }
+  catch (int)
+    {
+      if (v != 136)
+       __builtin_abort ();
+    }
+}

        Jakub


Reply via email to