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