On 6/10/26 09:27, Patrick Palka wrote:
> On Tue, 9 Jun 2026, Eczbek wrote:
> 
>> On 6/9/26 10:08, Patrick Palka wrote:
>>> On Mon, 8 Jun 2026, Eczbek wrote:
>>>
>>>> On 6/5/26 11:16, Patrick Palka wrote:
>>>>> On Thu, 4 Jun 2026, Eczbek wrote:
>>>>>
>>>>>> On 6/2/26 16:30, Patrick Palka wrote:
>>>>>>> On Mon, 1 Jun 2026, Eczbek wrote:
>>>>>>>> On 6/1/26 16:33, Jason Merrill wrote:
>>>>>>>>> This assumes a template with a single template parameter; it doesn't 
>>>>>>>>> work for e.g.
>>>>>>>>>
>>>>>>>>> struct A {
>>>>>>>>>   template<typename T, typename U>
>>>>>>>>>   operator T U::*() {
>>>>>>>>>     return 0;
>>>>>>>>>   }
>>>>>>>>> };
>>>>>>>>>
>>>>>>>>> int main() {
>>>>>>>>>   (void) &A::operator int A::*;
>>>>>>>>> }
>>>>>>>>>
>>>>>>>>> to handle the general case you need to deduce the template arguments.
>>>>>>>>
>>>>>>>> Thanks, I did not consider multiple template parameters. Would this be 
>>>>>>>> similar to what TEMPLATE_ID_EXPR does below, or is there some existing 
>>>>>>>> helper that should be used? I'm not sure how to do this.
>>>>>>>
>>>>>>> There are a couple of entrypoints to deduction, all of which ultimately
>>>>>>> call the main workhorse unify.  For simplicity I think we could get away
>>>>>>> with calling unify directly from resolve_nondeduced_context with
>>>>>>>
>>>>>>>   tparms = DECL_TEMPLATE_PARMS of the conversion function template
>>>>>>>   targs = empty TREE_VEC same length as DECL_TEMPLATE_PARMS
>>>>>>>   parm = return type of the template
>>>>>>>   arg = BASELINK_OPTYPE
>>>>>>>   strict = UNIFY_ALLOW_NONE (I think?)
>>>>>>>
>>>>>>> If unify succeeds, then 'targs' will contain the deduced template
>>>>>>> arguments that we need to instantiate the conversion function template
>>>>>>> with.
>>>>>>>
>>>>>>> So for
>>>>>>>
>>>>>>>   struct A {
>>>>>>>     template<class T, class U>
>>>>>>>     operator B<T*, U&>();
>>>>>>>   };
>>>>>>>
>>>>>>>   int main() {
>>>>>>>     &A::operator B<int*, char&>;
>>>>>>>   }
>>>>>>>
>>>>>>> we need to call unify with (where {} is shorthand for a TREE_VEC)
>>>>>>>
>>>>>>>   tparms = {T, U}
>>>>>>>   targs = {NULL, NULL}
>>>>>>>   parm = B<T*, U&>
>>>>>>>   arg = B<int*, char&>
>>>>>>>
>>>>>>> which should succeed and fill in targs with {int, char}.  Instantiating
>>>>>>> the template with {int, char} yields the correct specialization
>>>>>>> operator B<int*, char&>.
>>>>>>
>>>>>> Thank you, I made something work! Bootstrapped/regtested again.
>>>>>>
>>>>>> Jason mentioned that what I wrote in the changelog should be in the main 
>>>>>> commit message, so what should I write in the changelog instead?
>>>>>
>>>>> I think Jason's point is that the main commit message generally
>>>>> shouldn't be empty, even if it just repeats what you wrote in the
>>>>> ChangeLog.  Though generally the ChangeLog ought to describe how the
>>>>> code is changed, and the context/motivation of the change should be in
>>>>> the main commit message, so there shouldn't be too much overlap.
>>>>> Here I'd just write:
>>>>>
>>>>>     (resolve_overloaded_unification): Call unify when resolving a
>>>>>     conversion-function-id.
>>>>>     (resolve_nondeduced_context): Likewise.
>>>>>
>>>>>>
>>>>>> -- >8 --
>>>>>>
>>>>>> ---
>>>>>>  gcc/cp/pt.cc                           | 30 ++++++++++++++++++++++++--
>>>>>>  gcc/testsuite/g++.dg/template/conv22.C | 28 ++++++++++++++++++++++++
>>>>>>  2 files changed, 56 insertions(+), 2 deletions(-)
>>>>>>  create mode 100644 gcc/testsuite/g++.dg/template/conv22.C
>>>>>>
>>>>>> diff --git a/gcc/cp/pt.cc b/gcc/cp/pt.cc
>>>>>> index 6992b5196fe..d914a1c817d 100644
>>>>>> --- a/gcc/cp/pt.cc
>>>>>> +++ b/gcc/cp/pt.cc
>>>>>> @@ -25193,6 +25193,7 @@ resolve_overloaded_unification (tree tparms,
>>>>>>    int good = 0;
>>>>>>    tree goodfn = NULL_TREE;
>>>>>>    bool addr_p;
>>>>>> +  tree baselink = NULL_TREE;
>>>>>>  
>>>>>>    if (TREE_CODE (arg) == ADDR_EXPR)
>>>>>>      {
>>>>>> @@ -25212,7 +25213,22 @@ resolve_overloaded_unification (tree tparms,
>>>>>>  
>>>>>>    /* Strip baselink information.  */
>>>>>>    if (BASELINK_P (arg))
>>>>>> -    arg = BASELINK_FUNCTIONS (arg);
>>>>>> +    {
>>>>>> +      baselink = arg;
>>>>>> +      arg = BASELINK_FUNCTIONS (arg);
>>>>>> +    }
>>>>>> +
>>>>>> +  if (TREE_CODE (arg) == OVERLOAD
>>>>>> +      && IDENTIFIER_CONV_OP_P (OVL_NAME (arg)))
>>>>>> +    {
>>>>>> +      tree tmpl = OVL_FIRST (arg);
>>>>>> +      tree tparms = DECL_INNERMOST_TEMPLATE_PARMS (tmpl);
>>>>>> +      tree targs = make_tree_vec (DECL_NTPARMS (tmpl));
>>>>>> +      tree parm = DECL_CONV_FN_TYPE (DECL_TEMPLATE_RESULT (tmpl));
>>>>>> +      tree optype = BASELINK_OPTYPE (baselink);
>>>>>> +      if (!unify (tparms, targs, parm, optype, UNIFY_ALLOW_NONE, false))
>>>>>> +        arg = lookup_template_function (tmpl, targs);
>>>>>> +    }
>>>>>
>>>>> This needs to be done in a loop over each conversion template in the
>>>>> overload set because the result of deduction can differ for each
>>>>> template:
>>>>>
>>>>>   struct A {
>>>>>     template<class T> operator T();    // #1
>>>>>     template<class T> operator B<T>(); // #2
>>>>>   };
>>>>>
>>>>>   int main() {
>>>>>     &A::operator B<int>; // targs would be {B<int>} for #1 and {int} for 
>>>>> #2
>>>>>   }
>>>>>
>>>>> And instead of calling lookup_template_function to form a template-id, I
>>>>> think we need to call instantiate_template to directly instantiate
>>>>> each template for which deduction succeeded.
>>>>>
>>>>> We could factor out all this into a helper
>>>>>
>>>>>   tree resolve_conversion_function_id (tree fns, tree optype)
>>>>>
>>>>> that takes a conversion operator overload set FNS and target
>>>>> type OPTYPE and returns an overload set containing the instantiated
>>>>> functions that match the target type.
>>>>>
>>>>
>>>> Thanks, I have this:
>>>>
>>>> tree
>>>> resolve_conversion_function_id (tree fns, tree optype)
>>>> {
>>>>   tree overloads = NULL_TREE;
>>>>   for (lkp_iterator iter (fns); iter; ++iter)
>>>>     {
>>>>       tree tmpl = *iter;
>>>>       tree tparms = DECL_INNERMOST_TEMPLATE_PARMS (tmpl);
>>>>       tree targs = make_tree_vec (DECL_NTPARMS (tmpl));
>>>>       tree parm = DECL_CONV_FN_TYPE (DECL_TEMPLATE_RESULT (tmpl));
>>>>       if (unify (tparms, targs, parm, optype, UNIFY_ALLOW_NONE, false))
>>>>    continue;
>>>>       tree fn = instantiate_template (tmpl, targs, tf_none);
>>>>       if (!constraints_satisfied_p (fn))
>>>>    continue;
>>>>       overloads = lookup_add (fn, overloads);
>>>>     }
>>>>   return overloads;
>>>> }
>>>>
>>>> But how can the most "specific" overload be selected from the returned 
>>>> set? Please advise.
>>>
>>> That's the partial ordering step of overload resolution, done by the the
>>> 'tourney' function, see e.g perform_overload_resolution.  But I don't think 
>>> we
>>> want to do do partial ordering here, we should just reject the code if 
>>> there's
>>> more than one viable overload after this step.  So the previous example
>>>
>>>    struct A {
>>>      template<class T> operator T();    // #1
>>>      template<class T> operator B<T>(); // #2
>>>    };
>>>  
>>>    int main() {
>>>      &A::operator B<int>; // targs would be {B<int>} for #1 and {int} for #2
>>>    }
>>>
>>> should be rejected despite #2 being more specialized than #1.
>>>
>>
>>
>> That seems incomplete to me. Both Clang and MSVC appear to select the most 
>> specialized overload: https://godbolt.org/z/q5a755bcK
> 
> Good catch. GCC behaves the same and selects the most specialized conversion
> function if we turn f into a non-template to sidestep template argument
> deduction:
> 
>   void f(B<int>(A::*)());
>   int main() { f(&A::operator B<int>); }
> 
> So the problem is that deduction fails to deduce T=B<int> for the function
> template f.  Once we can convince GCC of that then it should work by virtue of
> the non-template f case working.
> 
> The analogous non-conversion-function testcase is:
> 
>     template<class>
>     struct B {};
> 
>     struct A {
>       template<class T> void g(T);
>       template<class T> void g(T) requires true;
>     };
> 
>     template<class T>
>     void f(void(A::*)(T));
> 
>     int main() { f(&A::g<int>); }
> 
> which GCC does accept by deducing T=int for f, despite multiple function
> templates in the overload set.  So the problem is specific to conversion
> functions.
> 
> Using your resolve_conversion_function_id implementation, the following
> change to resolve_overloaded_unification seems to do the right thing
> without needing to implement additional "more specialized" logic:
> 
> diff --git a/gcc/cp/pt.cc b/gcc/cp/pt.cc
> index 891e89f1d763..3f079457b455 100644
> --- a/gcc/cp/pt.cc
> +++ b/gcc/cp/pt.cc
> @@ -25336,8 +25336,12 @@ resolve_overloaded_unification (tree tparms,
>      arg = TREE_OPERAND (arg, 1);
> 
>    /* Strip baselink information.  */
> +  tree optype = NULL_TREE;
>    if (BASELINK_P (arg))
> +    {
> +      optype = BASELINK_OPTYPE (arg);
>        arg = BASELINK_FUNCTIONS (arg);
> +    }
> 
>    if (TREE_CODE (arg) == TEMPLATE_ID_EXPR)
>      {
> @@ -25401,6 +25405,9 @@ resolve_overloaded_unification (tree tparms,
>         not just the function on its own.  */
>      return false;
>    else
> +    {
> +      if (optype)
> +       arg = resolve_conversion_function_id (arg, optype);
>        for (lkp_iterator iter (arg); iter; ++iter)
>         {
>           tree fn = *iter;
> @@ -25417,6 +25424,7 @@ resolve_overloaded_unification (tree tparms,
>               ++good;
>             }
>         }
> +    }
> 
>    /* [temp.deduct.type] A template-argument can be deduced from a pointer
>       to function or pointer to member function argument if the set of
> 


Thanks, that seems to work! Here's what I have so far, and my tests. 
(void)&D::operator int D::*; currently fails because resolve_nondeduced_context 
only accepts single overloads. How should it also select the most specialized 
overload?


diff --git a/gcc/cp/pt.cc b/gcc/cp/pt.cc
index 6992b5196fe..ded3ed88ec4 100644
--- a/gcc/cp/pt.cc
+++ b/gcc/cp/pt.cc
@@ -25174,6 +25174,31 @@ type_unification_real (tree tparms,
   return unify_success (explain_p);
 }
 
+/* Subroutine of resolve_overloaded_unification and
+   resolve_nondeduced_context.  FNS is a conversion operator overload set and
+   OPTYPE is the target type.  Returns an overload set containing the
+   instantiated functions that match the target type.  */
+
+tree
+resolve_conversion_function_id (tree fns, tree optype)
+{
+  tree overloads = NULL_TREE;
+  for (lkp_iterator iter (fns); iter; ++iter)
+    {
+      tree tmpl = *iter;
+      tree tparms = DECL_INNERMOST_TEMPLATE_PARMS (tmpl);
+      tree targs = make_tree_vec (DECL_NTPARMS (tmpl));
+      tree parm = DECL_CONV_FN_TYPE (DECL_TEMPLATE_RESULT (tmpl));
+      if (unify (tparms, targs, parm, optype, UNIFY_ALLOW_NONE, false))
+       continue;
+      tree fn = instantiate_template (tmpl, targs, tf_none);
+      if (!constraints_satisfied_p (fn))
+       continue;
+      overloads = lookup_add (fn, overloads);
+    }
+  return overloads;
+}
+
 /* Subroutine of type_unification_real.  Args are like the variables
    at the call site.  ARG is an overloaded function (or template-id);
    we try deducing template args from each of the overloads, and if
@@ -25193,6 +25218,7 @@ resolve_overloaded_unification (tree tparms,
   int good = 0;
   tree goodfn = NULL_TREE;
   bool addr_p;
+  tree optype = NULL_TREE;
 
   if (TREE_CODE (arg) == ADDR_EXPR)
     {
@@ -25212,7 +25238,10 @@ resolve_overloaded_unification (tree tparms,
 
   /* Strip baselink information.  */
   if (BASELINK_P (arg))
-    arg = BASELINK_FUNCTIONS (arg);
+    {
+      optype = BASELINK_OPTYPE (arg);
+      arg = BASELINK_FUNCTIONS (arg);
+    }
 
   if (TREE_CODE (arg) == TEMPLATE_ID_EXPR)
     {
@@ -25276,22 +25305,26 @@ resolve_overloaded_unification (tree tparms,
        not just the function on its own.  */
     return false;
   else
-    for (lkp_iterator iter (arg); iter; ++iter)
-      {
-       tree fn = *iter;
-       if (flag_noexcept_type)
-         maybe_instantiate_noexcept (fn, tf_none);
-       if (TREE_CODE (fn) == FUNCTION_DECL && !constraints_satisfied_p (fn))
-         continue;
-       tree elem = TREE_TYPE (fn);
-       if (try_one_overload (tparms, targs, tempargs, parm, elem,
-                             strict, sub_strict, addr_p, explain_p)
-           && (!goodfn || !same_type_p (goodfn, elem)))
-         {
-           goodfn = elem;
-           ++good;
-         }
-      }
+    {
+      if (optype)
+       arg = resolve_conversion_function_id (arg, optype);
+      for (lkp_iterator iter (arg); iter; ++iter)
+       {
+         tree fn = *iter;
+         if (flag_noexcept_type)
+           maybe_instantiate_noexcept (fn, tf_none);
+         if (TREE_CODE (fn) == FUNCTION_DECL && !constraints_satisfied_p (fn))
+           continue;
+         tree elem = TREE_TYPE (fn);
+         if (try_one_overload (tparms, targs, tempargs, parm, elem,
+                               strict, sub_strict, addr_p, explain_p)
+             && (!goodfn || !same_type_p (goodfn, elem)))
+           {
+             goodfn = elem;
+             ++good;
+           }
+       }
+    }
 
   /* [temp.deduct.type] A template-argument can be deduced from a pointer
      to function or pointer to member function argument if the set of
@@ -25335,7 +25368,7 @@ resolve_overloaded_unification (tree tparms,
 tree
 resolve_nondeduced_context (tree orig_expr, tsubst_flags_t complain)
 {
-  tree expr, offset, baselink;
+  tree expr, offset, baselink, optype;
   bool addr;
 
   if (!type_unknown_p (orig_expr))
@@ -25345,6 +25378,7 @@ resolve_nondeduced_context (tree orig_expr, 
tsubst_flags_t complain)
   addr = false;
   offset = NULL_TREE;
   baselink = NULL_TREE;
+  optype = NULL_TREE;
 
   if (TREE_CODE (expr) == ADDR_EXPR)
     {
@@ -25359,14 +25393,14 @@ resolve_nondeduced_context (tree orig_expr, 
tsubst_flags_t complain)
   if (BASELINK_P (expr))
     {
       baselink = expr;
+      optype = BASELINK_OPTYPE (expr);
       expr = BASELINK_FUNCTIONS (expr);
     }
 
+  int good = 0;
+  tree goodfn = NULL_TREE;
   if (TREE_CODE (expr) == TEMPLATE_ID_EXPR)
     {
-      int good = 0;
-      tree goodfn = NULL_TREE;
-
       /* If we got some explicit template args, we need to plug them into
         the affected templates before we try to unify, in case the
         explicit args will completely resolve the templates in question.  */
@@ -25403,28 +25437,37 @@ resolve_nondeduced_context (tree orig_expr, 
tsubst_flags_t complain)
                }
            }
        }
-      if (good == 1)
-       {
-         expr = goodfn;
-         if (baselink)
-           expr = build_baselink (BASELINK_BINFO (baselink),
-                                  BASELINK_ACCESS_BINFO (baselink),
-                                  expr, BASELINK_OPTYPE (baselink));
-         if (offset)
-           {
-             tree base
-               = TYPE_MAIN_VARIANT (TREE_TYPE (TREE_OPERAND (offset, 0)));
-             expr = build_offset_ref (base, expr, addr, complain);
-           }
-         if (addr)
-           expr = cp_build_addr_expr (expr, complain);
-         return expr;
-       }
-      else if (good == 0 && badargs && (complain & tf_error))
+      if (good == 0 && badargs && (complain & tf_error))
        /* There were no good options and at least one bad one, so let the
           user know what the problem is.  */
        instantiate_template (badfn, badargs, complain);
     }
+  else if (optype)
+    {
+      expr = resolve_conversion_function_id (expr, optype);
+      if (OVL_SINGLE_P (expr))
+       {
+         goodfn = OVL_FIRST (expr);
+         good = 1;
+       }
+    }
+  if (good == 1)
+    {
+      expr = goodfn;
+      if (baselink)
+       expr = build_baselink (BASELINK_BINFO (baselink),
+                              BASELINK_ACCESS_BINFO (baselink),
+                              expr, BASELINK_OPTYPE (baselink));
+      if (offset)
+       {
+         tree base
+           = TYPE_MAIN_VARIANT (TREE_TYPE (TREE_OPERAND (offset, 0)));
+         expr = build_offset_ref (base, expr, addr, complain);
+       }
+      if (addr)
+       expr = cp_build_addr_expr (expr, complain);
+      return expr;
+    }
   return orig_expr;
 }



Tests:



struct A {
  template<typename T>
  operator T() {
    return T();
  }
};

struct B {
  operator int() {
    return 0;
  }

  template<typename T>
  operator T() {
    static_assert(false);
  }
};

struct C {
  template<typename T>
  operator T() {
    return T();
  }

  template<typename U>
  operator U() requires(false) {
    return U();
  }
};

struct D {
  template<typename T>
  operator T() {
    static_assert(false);
  }

  template<typename T, typename U>
  operator T U::*() {
    return 0;
  }
};

template<typename R, typename C>
void deduce(R(C::*)()) {}

template<typename R, typename C1, typename C2>
void deduce2(R C1::*(C2::*)()) {}

void test() {
  (void)&A::operator int;
  auto a = &A::operator int;
  deduce(&A::operator int);

  (void)&B::operator int;
  auto b = &B::operator int;
  deduce(&B::operator int);

  (void)&C::operator int;
  auto c = &C::operator int;
  deduce(&C::operator int);

  (void)&D::operator int D::*; // fails
  auto d = &D::operator int D::*;
  deduce(&D::operator int D::*);
  deduce2(&D::operator int D::*);
}

Reply via email to