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::*);
> }
>
>