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