]> git.ipfire.org Git - thirdparty/gcc.git/commitdiff
c++: prev declared hidden tmpl friend inst, cont [PR119807]
authorPatrick Palka <ppalka@redhat.com>
Tue, 15 Apr 2025 13:06:40 +0000 (09:06 -0400)
committerPatrick Palka <ppalka@redhat.com>
Tue, 15 Apr 2025 13:06:40 +0000 (09:06 -0400)
When remapping existing specializations of a hidden template friend from
a previous declaration to the new definition, we must remap only those
specializations that match this new definition, but currently we
remap all specializations (since they all appear in the same
DECL_TEMPLATE_INSTANTIATIONS list of the most general template).

Concretely, in the first testcase below, we form two specializations of
the friend A::f, one with arguments {{0},{bool}} and another with
arguments {{1},{bool}}.  Later when instantiating B, we need to remap
these specializations.  During the B<0> instantiation we only want to
remap the first specialization, and during the B<1> instantiation only
the second specialization, but currently we remap both specializations
twice.

tsubst_friend_function needs to determine if an existing specialization
matches the shape of the new definition, which is tricky in general,
e.g. if the outer template parameters may not match up.  Fortunately we
don't have to reinvent the wheel here since is_specialization_of_friend
seems to do exactly what we need.  We can check this unconditionally,
but I think it's only necessary when dealing with specializations formed
from a class template scope previous declaration, hence the
TMPL_ARGS_HAVE_MULTIPLE_LEVELS check.

PR c++/119807
PR c++/112288

gcc/cp/ChangeLog:

* pt.cc (tsubst_friend_function): Skip remapping an
existing specialization if it doesn't match the shape of
the new friend definition.

gcc/testsuite/ChangeLog:

* g++.dg/template/friend86.C: New test.
* g++.dg/template/friend87.C: New test.

Reviewed-by: Jason Merrill <jason@redhat.com>
gcc/cp/pt.cc
gcc/testsuite/g++.dg/template/friend86.C [new file with mode: 0644]
gcc/testsuite/g++.dg/template/friend87.C [new file with mode: 0644]

index b7060b4c5aa6e65bc85ffcec2d0a56ae66975118..4349b19119b72d0dc850d0136625a72ad19ca975 100644 (file)
@@ -11772,6 +11772,10 @@ tsubst_friend_function (tree decl, tree args)
                      elt.args = DECL_TI_ARGS (spec);
                      elt.spec = NULL_TREE;
 
+                     if (TMPL_ARGS_HAVE_MULTIPLE_LEVELS (DECL_TI_ARGS (spec))
+                         && !is_specialization_of_friend (spec, new_template))
+                       continue;
+
                      decl_specializations->remove_elt (&elt);
 
                      tree& spec_args = DECL_TI_ARGS (spec);
diff --git a/gcc/testsuite/g++.dg/template/friend86.C b/gcc/testsuite/g++.dg/template/friend86.C
new file mode 100644 (file)
index 0000000..9e2c1af
--- /dev/null
@@ -0,0 +1,25 @@
+// PR c++/119807
+// { dg-do run }
+
+template<int N>
+struct A {
+  template<class T> friend int f(A<N>, T);
+};
+
+template struct A<0>;
+template struct A<1>;
+
+int main() {
+  A<0> x;
+  A<1> y;
+  if (f(x, true) != 0) __builtin_abort();
+  if (f(y, true) != 1) __builtin_abort();
+}
+
+template<int N>
+struct B {
+  template<class T> friend int f(A<N>, T) { return N; }
+};
+
+template struct B<0>;
+template struct B<1>;
diff --git a/gcc/testsuite/g++.dg/template/friend87.C b/gcc/testsuite/g++.dg/template/friend87.C
new file mode 100644 (file)
index 0000000..94c0dfc
--- /dev/null
@@ -0,0 +1,42 @@
+// PR c++/119807
+// { dg-do compile { target c++20 } }
+
+using size_t = decltype(sizeof(0));
+
+template<auto tag, size_t current>
+struct CounterReader {
+       template<typename>
+       friend auto counterFlag(CounterReader<tag, current>) noexcept;
+};
+
+template<auto tag, size_t current>
+struct CounterWriter {
+       static constexpr size_t value = current;
+
+       template<typename>
+       friend auto counterFlag(CounterReader<tag, current>) noexcept {}
+};
+
+template<auto tag, auto unique, size_t current = 0, size_t mask = size_t(1) << (sizeof(size_t) * 8 - 1)>
+[[nodiscard]] constexpr size_t counterAdvance() noexcept {
+       if constexpr (!mask) {
+               return CounterWriter<tag, current + 1>::value;
+       } else if constexpr (requires { counterFlag<void>(CounterReader<tag, current | mask>()); }) {
+               return counterAdvance<tag, unique, current | mask, (mask >> 1)>();
+       } 
+       else {
+               return counterAdvance<tag, unique, current, (mask >> 1)>();
+       }
+}
+
+constexpr auto defaultCounterTag = [] {};
+
+template<auto tag = defaultCounterTag, auto unique = [] {}>
+constexpr size_t counter() noexcept {
+       return counterAdvance<tag, unique>();
+}
+
+int main() {
+       static_assert(counter() == 1);
+       static_assert(counter() == 2);
+}