https://gcc.gnu.org/g:30dd420a06ad7d2adf4a672d176caee632f8168a

commit r15-2120-g30dd420a06ad7d2adf4a672d176caee632f8168a
Author: Patrick Palka <ppa...@redhat.com>
Date:   Wed Jul 17 21:02:52 2024 -0400

    c++: prev declared hidden tmpl friend inst [PR112288]
    
    When partially instantiating a previously declared hidden template
    friend definition (at class template scope) such as slot_allocated in
    the first testcase below, tsubst_friend_function needs to go through
    all existing specializations thereof and make them point to the new
    definition.
    
    But when the previous declaration was also at class template scope,
    old_decl is not the most general template, instead it's the partial
    instantiation, and since instantiations are relative to the most general
    template, old_decl's DECL_TEMPLATE_INSTANTIATIONS is empty.  So we
    to consistently use the most general template here.  And when adjusting
    DECL_TI_ARGS to match, only the innermost template arguments should be
    preserved; the outer ones should correspond to the new definition.
    
    Otherwise we fail a checking-only sanity check in instantiate_decl in
    the first testcase, and in the second/third we end up emitting multiple
    definitions of the template friend instantiation, resulting in a link
    failure.
    
            PR c++/112288
    
    gcc/cp/ChangeLog:
    
            * pt.cc (tsubst_friend_function): When adjusting existing
            specializations after defining a previously declared template
            friend, consider the most general template and correct
            DECL_TI_ARGS adjustment.
    
    gcc/testsuite/ChangeLog:
    
            * g++.dg/template/friend80.C: New test.
            * g++.dg/template/friend81.C: New test.
            * g++.dg/template/friend81a.C: New test.
    
    Reviewed-by: Jason Merrill <ja...@redhat.com>

Diff:
---
 gcc/cp/pt.cc                              | 13 +++++++------
 gcc/testsuite/g++.dg/template/friend80.C  | 25 +++++++++++++++++++++++++
 gcc/testsuite/g++.dg/template/friend81.C  | 28 ++++++++++++++++++++++++++++
 gcc/testsuite/g++.dg/template/friend81a.C | 30 ++++++++++++++++++++++++++++++
 4 files changed, 90 insertions(+), 6 deletions(-)

diff --git a/gcc/cp/pt.cc b/gcc/cp/pt.cc
index 0620c8c023a2..057797f213f5 100644
--- a/gcc/cp/pt.cc
+++ b/gcc/cp/pt.cc
@@ -11582,6 +11582,7 @@ tsubst_friend_function (tree decl, tree args)
            ;
          else
            {
+             tree old_template = most_general_template (old_decl);
              tree new_template = TI_TEMPLATE (new_friend_template_info);
              tree new_args = TI_ARGS (new_friend_template_info);
 
@@ -11619,7 +11620,7 @@ tsubst_friend_function (tree decl, tree args)
                  /* Reassign any specializations already in the hash table
                     to the new more general template, and add the
                     additional template args.  */
-                 for (t = DECL_TEMPLATE_INSTANTIATIONS (old_decl);
+                 for (t = DECL_TEMPLATE_INSTANTIATIONS (old_template);
                       t != NULL_TREE;
                       t = TREE_CHAIN (t))
                    {
@@ -11632,15 +11633,15 @@ tsubst_friend_function (tree decl, tree args)
 
                      decl_specializations->remove_elt (&elt);
 
-                     DECL_TI_ARGS (spec)
-                       = add_outermost_template_args (new_args,
-                                                      DECL_TI_ARGS (spec));
+                     tree& spec_args = DECL_TI_ARGS (spec);
+                     spec_args = add_outermost_template_args
+                       (new_args, INNERMOST_TEMPLATE_ARGS (spec_args));
 
                      register_specialization
-                       (spec, new_template, DECL_TI_ARGS (spec), true, 0);
+                       (spec, new_template, spec_args, true, 0);
 
                    }
-                 DECL_TEMPLATE_INSTANTIATIONS (old_decl) = NULL_TREE;
+                 DECL_TEMPLATE_INSTANTIATIONS (old_template) = NULL_TREE;
                }
            }
 
diff --git a/gcc/testsuite/g++.dg/template/friend80.C 
b/gcc/testsuite/g++.dg/template/friend80.C
new file mode 100644
index 000000000000..5c417e12dd0c
--- /dev/null
+++ b/gcc/testsuite/g++.dg/template/friend80.C
@@ -0,0 +1,25 @@
+// PR c++/112288
+// { dg-do compile { target c++11 } }
+
+template<class T>
+struct slot {
+  template<class U>
+  friend constexpr bool slot_allocated(slot<T>, U);
+};
+
+template<class T>
+struct allocate_slot {
+  template<class U>
+  friend constexpr bool slot_allocated(slot<T>, U) { return true; }
+};
+
+template<class T, bool = slot_allocated(slot<T>{}, 42)>
+constexpr int next(int) { return 1; }
+
+template<class T>
+constexpr int next(...) { return (allocate_slot<T>{}, 0); }
+
+// slot_allocated<slot<int>, int>() not defined yet
+static_assert(next<int>(0) == 0, "");
+// now it's defined, need to make existing spec point to defn or else ICE
+static_assert(next<int>(0) == 1, "");
diff --git a/gcc/testsuite/g++.dg/template/friend81.C 
b/gcc/testsuite/g++.dg/template/friend81.C
new file mode 100644
index 000000000000..5d3a2889c0f2
--- /dev/null
+++ b/gcc/testsuite/g++.dg/template/friend81.C
@@ -0,0 +1,28 @@
+// PR c++/112288
+// { dg-do link }
+
+template<class T> struct A;
+template<class T> struct B;
+
+A<int>* a;
+B<int>* b;
+
+template<class T>
+struct B {
+  template<class U>
+  friend int f(A<T>*, B*, U);
+};
+
+template struct B<int>; // f declared
+int n = f(a, b, 0); // f<int> specialized
+
+template<class T>
+struct A {
+  template<class U>
+  friend int f(A*, B<T>*, U) { return 42; }
+};
+
+template struct A<int>; // f defined, need to make existing f<int> point to 
defn
+int m = f(a, b, 0); // reuses existing specialization f<int>
+
+int main() { }
diff --git a/gcc/testsuite/g++.dg/template/friend81a.C 
b/gcc/testsuite/g++.dg/template/friend81a.C
new file mode 100644
index 000000000000..4557fd3c03a5
--- /dev/null
+++ b/gcc/testsuite/g++.dg/template/friend81a.C
@@ -0,0 +1,30 @@
+// PR c++/112288
+// { dg-do link }
+// A version of friend81.C where A and B have a different number of template
+// parameters.
+
+template<class T> struct A;
+template<class T, class = void> struct B;
+
+A<int>* a;
+B<int>* b;
+
+template<class T, class>
+struct B {
+  template<class U>
+  friend int f(A<T>*, B*, U);
+};
+
+template struct B<int>; // f declared
+int n = f(a, b, 0); // f<int> specialized
+
+template<class T>
+struct A {
+  template<class U>
+  friend int f(A*, B<T>*, U) { return 42; }
+};
+
+template struct A<int>; // f defined, need to make existing f<int> point to 
defn
+int m = f(a, b, 0); // reuses existing specialization f<int>
+
+int main() { }

Reply via email to