c++: prev declared hidden tmpl friend inst [PR112288]

Message ID 20240709002841.3227323-1-ppalka@redhat.com
State New
Headers
Series c++: prev declared hidden tmpl friend inst [PR112288] |

Checks

Context Check Description
linaro-tcwg-bot/tcwg_gcc_build--master-arm success Build passed
linaro-tcwg-bot/tcwg_gcc_build--master-aarch64 success Build passed
linaro-tcwg-bot/tcwg_gcc_check--master-arm success Test passed
linaro-tcwg-bot/tcwg_gcc_check--master-aarch64 success Test passed

Commit Message

Patrick Palka July 9, 2024, 12:28 a.m. UTC
  Bootstrapped and regtested on x86_64-pc-linux-gnu, does this look
OK for trunk/14?

-- >8 --

When instantiating a previously declared hidden template friend declared
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 (both in TI_TEMPLATE
and the specializations table).

In this class template scope case however old_decl is not the most
general template, instead it's the partial instantiation of the
previous hidden template friend, and function instantiations are always
relative to the most general template so DECL_TEMPLATE_INSTANTIATIONS
is empty.  Instead we need to 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 with multiple
definitions of the template friend instantiation at link time.

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.
---
 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(-)
 create mode 100644 gcc/testsuite/g++.dg/template/friend80.C
 create mode 100644 gcc/testsuite/g++.dg/template/friend81.C
 create mode 100644 gcc/testsuite/g++.dg/template/friend81a.C
  

Patch

diff --git a/gcc/cp/pt.cc b/gcc/cp/pt.cc
index 1f6553790a5..ab231aaaa0f 100644
--- a/gcc/cp/pt.cc
+++ b/gcc/cp/pt.cc
@@ -11602,6 +11602,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);
 
@@ -11639,7 +11640,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))
 		    {
@@ -11652,15 +11653,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 00000000000..5c417e12dd0
--- /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 00000000000..cefcca03ab4
--- /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); // #1
+};
+
+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; } // #2
+};
+
+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 00000000000..b4e4c9ec52a
--- /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 take a different number of template
+// parameters.
+
+template<class T> struct A;
+template<class T, class = T> struct B;
+
+A<int>* a;
+B<int>* b;
+
+template<class T, class>
+struct B {
+  template<class U>
+  friend int f(A<T>*, B*, U); // #1
+};
+
+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; } // #2
+};
+
+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() { }