On 2026-09-25 09:52:55, Jason Merrill wrote:
> On 9/24/26 8:58 PM, Eczbek wrote:
>> On 2026-09-24 19:06:05, Jason Merrill wrote:
>>> On 9/23/26 10:12 AM, Eczbek wrote:
>>>> On 2026-09-22 17:41:12, Jason Merrill wrote:
>>>>> On 9/22/26 8:19 AM, Eczbek wrote:
>>>>>> On 2026-09-21 13:05:37, 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.
>>>>>>>
>>>>>>> And EDG. And I think it's clear we should under
>>>>>>> https://eel.is/c++draft/over.over which does consider the case of no
>>>>>>> target type.
>>>>>>>
>>>>>>>> I agree it is a good idea to ignore this for now. Is there an existing
>>>>>>>> bug report?
>>>>>>>
>>>>>>> I think let's not give up on this so quickly, at least not without
>>>>>>> analyzing it.
>>>>>>>
>>>>>>> It seems that the key difference between this and
>>>>>>>
>>>>>>> auto x = &A::g<int>;
>>>>>>>
>>>>>>> is that in the declaration, we first go through
>>>>>>> resolve_nondeduced_context (twice, both times to no effect), then
>>>>>>> resolve_overloaded_unification to determine the type of x, then
>>>>>>> resolve_address_of_overloaded_function to resolve which actual function
>>>>>>> we want.
>>>>>>>
>>>>>>> Note that resolve_overloaded_unification is only interested in the
>>>>>>> type; once we have a type we don't care about the individual overloads.
>>>>>>> So if we have optype I think we should just unify on that and not
>>>>>>> bother with resolve_conversion_function_id.
>>>>>>
>>>>>> To clarify, does this mean we should directly call unify? If so, I'm not
>>>>>> sure how to get all of the arguments for it here without looping over
>>>>>> the overloads.
>>>>>
>>>>> I was thinking
>>>>>
>>>>> unify (tparms, targs, parm, optype, strict, explain_p)
>>>>
>>>> Sorry, I'm not sure how to do this properly. This is what I tried:
>>>>
>>>> if (BASELINK_P (arg)
>>>> && unify (tparms, targs, parm, BASELINK_OPTYPE (arg), strict,
>>>> explain_p))
>>>> return false;
>>>>
>>>> but this breaks all of the `deduce` tests.
>>>
>>> Ah, right, we need a function type, not just the optype. And now it occurs
>>> to me that the functions might differ in noexcept even if they match
>>> otherwise. So never mind, let's leave resolve_overloaded_unification as in
>>> your last patch.
>>>
>>>>>>> And then in resolve_nondeduced_context, if we have an optype we can
>>>>>>> pass that to resolve_address_of_overloaded_function, and again we don't
>>>>>>> need the new function.
>>>>>>
>>>>>> I'm guessing that would look something like this, but I can't figure out
>>>>>> how to pass explicit_targs:
>>>>>>
>>>>>> if (BASELINK_P (expr))
>>>>>> expr =
>>>>>> resolve_address_of_overloaded_function (BASELINK_OPTYPE (expr),
>>>>>> BASELINK_FUNCTIONS (expr),
>>>>>> complain, true,
>>>>>> /* explicit_targs? */,
>>>>>> BASELINK_ACCESS_BINFO (expr));
>>>>>
>>>>> explicit_targs would be NULL_TREE in this case...
>>>>>> I'm also having trouble with this because
>>>>>> resolve_address_of_overloaded_function is declared in class.cc but I'm
>>>>>> in pt.cc. Please advise.
>>>>>
>>>>> ...but it seems it's better to call instantiate_type rather than resolve_
>>>>> directly.
>>>>
>>>> This is what I tried:
>>>>
>>>> if (BASELINK_P (expr))
>>>> {
>>>> baselink = expr;
>>>> expr = instantiate_type (BASELINK_OPTYPE (expr),
>>>> BASELINK_FUNCTIONS (expr), complain);
>>>> }
>>>>
>>>> but this breaks other tests. `&A::operator int;` results in:
>>>> error: cannot resolve overloaded function ‘operator T’ based on
>>>> conversion to type ‘int’
>>>
>>> And here we would want to check whether if there are multiple results of
>>> resolve_conversion_function_id they have the same type, and if so pass that
>>> type to instantiate_type.
>>
>> To clarify, do you mean that if resolve_conversion_function_id returns an
>> overload set with more than one overload, we should iterate over every
>> overload to check if any two have the same function type? I don't understand
>> the reasoning behind that and how to use instantiate_type here.
>
> To see if they *all* have the same function type. And then pass that
> function type to instantiate_type.
Oh, thanks. I tried this:
tree fns = resolve_conversion_function_id (expr, optype);
if (OVL_SINGLE_P (fns))
{
goodfn = OVL_FIRST (fns);
good = 1;
}
else if (fns)
{
tree type = TREE_TYPE (OVL_FIRST (fns));
good = 1;
for (ovl_iterator iter (fns); ++iter;)
if (!same_type_p (type, TREE_TYPE (*iter)))
{
good = 0;
break;
}
if (good)
goodfn = instantiate_type (type, fns, complain);
good = goodfn != error_mark_node;
}
But it seems to only break this case:
error: cannot resolve overloaded function ‘operator int D::*’ based on
conversion to type ‘int D::* (D::)()’
77 | auto d = &D::operator int D::*;
| ^
I also looked at convert_to_void, which says that [over.over] forbids taking
the address of an overloaded function in `(void)&D::operator int D::*;` or
`&D::operator int D::*, 0;`. This might mean that my test is correct to fail,
but do you know which specific paragraph states this?
>
>>>
>>>> Another case to consider is selecting the overload based on the qualifiers
>>>> of the target type: https://godbolt.org/z/aY7Gorrx6
>>>> I discussed this with Waffl3x and we believe that Clang is correct to
>>>> select the template overload. My patch doesn't affect this.
>>>
>>> They are indeed correct. Normally
>>> instantiate_type/resolve_address_of_overloaded_function take care of that,
>>> but presumably they don't consider BASELINK_OPTYPE currently, so perhaps we
>>> need resolve_conversion_function_id there too.
>
> ...though the auto declaration worked, so it seems there's something subtler
> going wrong.
I assume you mean `auto b = &B::operator int;`. Is this not the case because it
simply picks the non-template overload?
>
> Jason
>
>>> Jason
>>>
>>>>>>> And then to handle the non-conversion example above, we could change
>>>>>>> the existing loop to handle the case where multiple results have the
>>>>>>> same type, and use resolve_address_of_overloaded_function in that case
>>>>>>> as well. Or perhaps replace the existing loop with a call to
>>>>>>> do_auto_deduction. This could indeed be a follow-up patch, but I'd
>>>>>>> like to address it while we're thinking about it rather than drop it
>>>>>>> until some unknown time in the future.
>>>>>>>
>>>>>>> After these changes, the first resolve_nondeduced_context should fully
>>>>>>> resolve the expression, so later ones don't need to do anything.
>>>>>>> Though I also wonder about removing the two calls to
>>>>>>> resolve_nondeduced_context in the auto path, since they seem to be
>>>>>>> redundant.
>>>>>>>
>>>>>>> Jason
>>>>>>>
>>>>>>>> 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.
>>>>>>>>
>>>>>>>> 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
>>>>>>>
>>>>>>
>>>>>
>>>>
>>>
>>
>