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.

> 
> >>  
> >>    if (TREE_CODE (arg) == TEMPLATE_ID_EXPR)
> >>      {
> >> @@ -25361,7 +25377,17 @@ resolve_nondeduced_context (tree orig_expr, 
> >> tsubst_flags_t complain)
> >>        baselink = expr;
> >>        expr = BASELINK_FUNCTIONS (expr);
> >>      }
> >> -
> >> +  if (TREE_CODE (expr) == OVERLOAD
> >> +      && IDENTIFIER_CONV_OP_P (OVL_NAME (expr)))
> >> +    {
> >> +      tree tmpl = OVL_FIRST (expr);
> >> +      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))
> >> +  expr = lookup_template_function (tmpl, targs);
> >> +    }
> >>    if (TREE_CODE (expr) == TEMPLATE_ID_EXPR)
> >>      {
> >>        int good = 0;
> >> diff --git a/gcc/testsuite/g++.dg/template/conv22.C 
> >> b/gcc/testsuite/g++.dg/template/conv22.C
> >> new file mode 100644
> >> index 00000000000..2e703880192
> >> --- /dev/null
> >> +++ b/gcc/testsuite/g++.dg/template/conv22.C
> >> @@ -0,0 +1,28 @@
> >> +// PR c++/122383
> >> +// { dg-do compile }
> >> +
> >> +struct A {
> >> +  template<typename T>
> >> +  operator T() {
> >> +    return 0;
> >> +  }
> >> +
> >> +  template<typename T, typename U>
> >> +  operator T U::*() {
> >> +    return 0;
> >> +  }
> >> +};
> >> +
> >> +template<typename T>
> >> +void f(T(A::*)()) {}
> >> +
> >> +template<typename T, typename U>
> >> +void f(T U::*(A::*)()) {}
> >> +
> >> +int main() {
> >> +  &A::operator int;
> >> +  &A::operator int A::*;
> >> +
> >> +  f(&A::operator int);
> >> +  f(&A::operator int A::*);
> >> +}
> >>
> >> base-commit: b1987874feead5e98f1ea005bd1ce5ff515eda7a
> >> -- 
> >> 2.54.0
> >>
> >>
> 
> 

Reply via email to