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


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