On Wed, 10 Jun 2026, Eczbek wrote:

> 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?

I'm not sure it should.  Clang doesn't accept that code either, right?
In any case, let's ignore that case for now and treat it as a separate
issue because we also don't accept the non-convfn version of that code:

    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() {
      &A::g<int>;
    };

> 
> 
> 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