Hi!
The following patch implements the P3769R1 paper as a DR, changes
the algorithm to select placement deallocation function among those
which were looked up.
The second testcase according to the paper's Implementation divergence
section should be rejected, but we accept it instead.
That isn't about selection of the single placement deallocation function
if any, but that we have later on
/* Core issue 901: It's ok to new a type with deleted delete. */
if (DECL_DELETED_FN (fn) && alloc_fn)
return NULL_TREE;
https://wg21.link/cwg901 seems to be unresolved for years, shall we keep
it like that, or limit it to !placement, something else?
Anyway, bootstrapped/regtested successfully on x86_64-linux and i686-linux,
ok for trunk?
2026-09-17 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.
* g++.dg/cpp26/placement-new1.C: New test.
* g++.dg/cpp26/placement-new2.C: New test.
--- a/gcc/cp/call.cc 2026-08-27 10:33:18.600752718 +0200
+++ b/gcc/cp/call.cc 2026-09-16 14:39:48.831074327 +0200
@@ -8127,28 +8127,71 @@ 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)
+ {
+ tree arg_types = TYPE_ARG_TYPES (t), arg;
+ unsigned ia;
+ nargs = list_length (arg_types);
+ args = XALLOCAVEC (tree, nargs);
+ for (arg = arg_types, ia = 0; arg != NULL_TREE;
+ arg = TREE_CHAIN (arg), ++ia)
+ args[ia] = TREE_VALUE (arg);
+ }
+ elt = fn_type_unification (elt, NULL_TREE, targs, args,
+ nargs, void_type_node,
+ DEDUCE_EXACT, LOOKUP_NORMAL,
+ NULL, false, false);
+ if (elt == error_mark_node)
+ /* Instantiation failed. */
+ continue;
+ }
+
+ if (!constraints_satisfied_p (elt))
+ continue;
- fn = instantiate_type (t, fns, tf_none);
- if (fn == error_mark_node)
- return NULL_TREE;
+ /* See if there's a match. */
+ tree fntype = static_fn_type (elt);
+ if (!compparms (TYPE_ARG_TYPES (t), TYPE_ARG_TYPES (fntype)))
+ 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 "
--- a/gcc/testsuite/g++.dg/cpp26/placement-new1.C 2026-09-16
17:52:24.258212480 +0200
+++ b/gcc/testsuite/g++.dg/cpp26/placement-new1.C 2026-09-16
17:58:12.156734090 +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 ();
+ }
+}
--- a/gcc/testsuite/g++.dg/cpp26/placement-new2.C 2026-09-16
17:58:57.366152125 +0200
+++ b/gcc/testsuite/g++.dg/cpp26/placement-new2.C 2026-09-16
19:09:31.635384638 +0200
@@ -0,0 +1,30 @@
+// P3769R1 - Clarification of placement new deallocation
+// { dg-do compile { target c++11 } }
+
+namespace std
+{
+ using size_t = decltype (sizeof 0);
+}
+
+template <int I>
+struct A {};
+int v;
+A <0> a;
+A <1> b;
+struct T { T (); };
+struct V {
+ V () = default;
+ V (int) { throw 2; }
+ void *operator new (std::size_t, A <1> &);
+ void operator delete (void *, A <1> &) = delete;
+};
+
+void *operator new (std::size_t, A <0> &);
+void operator delete (void *, A <0> &) = delete;
+
+int
+main ()
+{
+ T *p = new (a) T ();
+ V *q = new (b) V ();
+}
Jakub