On 9/22/26 3:15 PM, Jakub Jelinek wrote:
On Tue, Sep 22, 2026 at 02:27:14PM -0400, Jason Merrill wrote:
This is still doing [temp.deduct.type] deduction rather than
[temp.deduct.call] as specified.

That is, we should fill args with the actual args rather than their types,
use DEDUCE_CALL, and not pass a return type.

I've tried incremental
--- a/gcc/cp/call.cc    2026-09-21 18:52:59.556721249 +0200
+++ b/gcc/cp/call.cc    2026-09-22 20:44:53.942872714 +0200
@@ -8151,16 +8151,15 @@
              tree targs = make_tree_vec (DECL_NTPARMS (elt));
              if (args == NULL)
                {
-                 nargs = call_expr_nargs (placement) + 1;
+                 nargs = call_expr_nargs (placement);
                  args = XALLOCAVEC (tree, nargs);
-                 args[0] = TREE_TYPE (addr);
-                 for (unsigned i = 1; i < nargs - 1; ++i)
-                   args[i] = TREE_TYPE (CALL_EXPR_ARG (placement, i));
-                 args[nargs - 1] = void_type_node;
+                 args[0] = addr;
+                 for (unsigned i = 1; i < nargs; ++i)
+                   args[i] = CALL_EXPR_ARG (placement, i);
                }
              elt = fn_type_unification (elt, NULL_TREE, targs, args,
-                                        nargs, void_type_node,
-                                        DEDUCE_EXACT, LOOKUP_NORMAL,
+                                        nargs, NULL_TREE,
+                                        DEDUCE_CALL, LOOKUP_NORMAL,
                                         NULL, false, false);
              if (elt == error_mark_node)
                /* Instantiation failed.  */
but that doesn't work.
E.g. on the reduced
namespace std { using size_t = decltype (sizeof 0); }
template <int I>
struct B {};
B <0> b;
struct T { T () = default; T (int) { throw 1; } };
union U { unsigned char c[sizeof (T)]; T t; } u;
template <int I>
void *operator new (std::size_t, B <I> &)     // #3
{
   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 (); }
int main () {
   try
     {
       T *p = new (b) T (1); // OK, uses #3.  No deallocation function is 
selected (two candidates remain).
       throw 2L;
     }
   catch (int)
     {
     }
}
During build_operator_new_call -> perform_overload_resolution ->
add_candidates -> add_template_candidate -> add_template_candidate_real
fn_type_unification is called also with DEDUCE_CALL and works, but
in that case args[0] is 1 and args[1] is VIEW_CONVERT_EXPR<struct B>(b)
but what placement contains is not VIEW_CONVERT_EXPR<struct B>(b)
but (struct B &) &b instead.
So, we'd in that case need placement to be the vec<tree, va_gc> **placement
argument of build_new_1 rather than the CALL_EXPR which
build_new_1 passes in:
3898          /* The Standard is unclear here, but the right thing to do
3899             is to use the same method for finding deallocation
3900             functions that we use for finding allocation functions.  */
3901          cleanup = (build_op_delete_call
3902                     (dcode,
3903                      alloc_node,
3904                      size,
3905                      globally_qualified_p,
3906                      placement_allocation_fn_p ? alloc_call : NULL_TREE,
3907                      alloc_fn,
3908                      complain));
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.

Jason

Reply via email to