On 9/25/26 10:55 AM, Eczbek wrote:
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?

No, I think that citation is obsolete.  The operative rule is now
https://eel.is/c++draft/over.over#5
"After such eliminations, if any, there shall remain exactly one selected function."

And here instantiate_type should eliminate one of the overloads by partial ordering. Why is that not happening here?

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?

I meant that auto d = &D::operator int D::*; worked in your earlier patch.

Jason

Reply via email to