On 4/14/25 5:22 PM, Patrick Palka wrote:
Bootstrapped and regtested on x86_64-pc-linux-gnu, does this
look OK for trunk and later 14?
OK.
-- >8 --
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're all contained in the
DECL_TEMPLATE_INSTANTIATIONS list of the most general template of f.
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. 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 existing
specializations formed from a class template scope previous declaration,
hence 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.
---
gcc/cp/pt.cc | 4 +++
gcc/testsuite/g++.dg/template/friend86.C | 25 ++++++++++++++
gcc/testsuite/g++.dg/template/friend87.C | 42 ++++++++++++++++++++++++
3 files changed, 71 insertions(+)
create mode 100644 gcc/testsuite/g++.dg/template/friend86.C
create mode 100644 gcc/testsuite/g++.dg/template/friend87.C
diff --git a/gcc/cp/pt.cc b/gcc/cp/pt.cc
index b7060b4c5aa6..4349b19119b7 100644
--- a/gcc/cp/pt.cc
+++ b/gcc/cp/pt.cc
@@ -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
index 000000000000..9e2c1afb351c
--- /dev/null
+++ b/gcc/testsuite/g++.dg/template/friend86.C
@@ -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
index 000000000000..94c0dfc52924
--- /dev/null
+++ b/gcc/testsuite/g++.dg/template/friend87.C
@@ -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);
+}