On 2026-09-16 01:53:09, Eczbek wrote:
> On 2026-06-11 02:08:12, Eczbek wrote:
>> On 6/10/26 20:01, Jason Merrill wrote:
>>> On 6/10/26 4:06 PM, Eczbek wrote:
>>>> On 6/10/26 15:39, Patrick Palka wrote:
>>>>> 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>;
>>>>> };
>>>>
>>>> Clang doesn't accept, but MSVC does. I agree it is a good idea to ignore
>>>> this for now. Is there an existing bug report?
>>>>
>>>> Bootstrapped/regtested again. Is the ChangeLog OK?
>>>>
>>>> -- >8 --
>>>>
>>>> Taking the address of a conversion function template instantiation
>>>> incorrectly errors.
>>>>
>>>> PR c++/122383
>>>>
>>>> gcc/cp/ChangeLog:
>>>>
>>>> * pt.cc (resolve_conversion_function_id): Filter a conversion
>>>> function overload set using unify.
>>>> (resolve_overloaded_unification): Call
>>>> resolve_conversion_function_id when resolving
>>>> a conversion-function-id.
>>>> (resolve_nondeduced_context): Likewise.
>>>
>>> I'm afraid this patch is too large to go in under the "legally
>>> insignificant" exception we've been using for previous patches of yours;
>>> any news on that FSF copyright assignment?
>>
>> I suspected as much. I will start that now.
>
> I have copyright assignment now!
Forgot to CC the mailing list.
>
>>
>>>
>>>> gcc/testsuite/ChangeLog:
>>>>
>>>> * g++.dg/template/conv22.C: New test.
>>>> ---
>>>> gcc/cp/pt.cc | 121 +++++++++++++++++--------
>>>> gcc/testsuite/g++.dg/template/conv22.C | 69 ++++++++++++++
>>>> 2 files changed, 151 insertions(+), 39 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..912bb6b5185 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;
>>>> }
>>>> diff --git a/gcc/testsuite/g++.dg/template/conv22.C
>>>> b/gcc/testsuite/g++.dg/template/conv22.C
>>>> new file mode 100644
>>>> index 00000000000..7cc85abc8c3
>>>> --- /dev/null
>>>> +++ b/gcc/testsuite/g++.dg/template/conv22.C
>>>> @@ -0,0 +1,69 @@
>>>> +// PR c++/122383
>>>> +// { dg-do compile { target c++20 } }
>>>> +
>>>> +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::*; // TODO
>>>> + auto d = &D::operator int D::*;
>>>> + deduce(&D::operator int D::*);
>>>> + deduce2(&D::operator int D::*);
>>>> +}
>>>>
>>>> base-commit: b1987874feead5e98f1ea005bd1ce5ff515eda7a
>>>
>>
>