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