On Fri, 4 Sep 2026, Patrick Palka wrote:
> Bootstrapped and regtested on x86_64-pc-linux-gnu, does this look OK for
> trunk/16? Thought I'd submit a patch even though the fix isn't perfect
> or fully understood (to me).
>
> -- >8 --
>
> Jason's analysis: Here the issue here seems to be that
>
> 1) we don't add op==<__ni<int>,__ni<int>> as a pending specialization to
> any key, so we don't load it until we see the use of it in foo(), at
> which point we've already created another decl for it. And then
> 2) we fail to merge the loaded specialization with the existing one.
>
> If op== were a member template instead of a hidden friend, we'd get
>
> Pending specialization '::__normal_iterator<int>::template
> operator==<_Iter>' entity:4 section:2 also keyed to '::__normal_iterator'
> Pending specialization
> '::__normal_iterator<int>::operator==<::__normal_iterator<int>>' entity:7
> section:3 keyed to '::__normal_iterator'
>
> This lack seems to be because add_specializations completely ignores
> friends; the existing code is clearly wrong, as it calls
> template_args_equal to compare template arguments to a template rather
> than another set of template arguments.
>
> This patch partially corrects the is_friend code path treat to full
> template friend instantiations as ordinary specializations, recording
> them, while continuing to skip partial template friend instantiations.
> The condition TI_TEMPLATE (ti) != entry->tmpl isn't perfect because for
> consistency we probably also want to clear is_friend for instantiations
> of a non-template friend of a class template, for which we'd need something
> like TREE_CODE (spec) != TEMPLATE_DECL, but doing so seems to breaks the
> augmented friend-13.C testcase below. Still, this seems to be a strict
> improvement over the current buggy code which skips all friend
> specializations.
>
> PR c++/125555
>
> gcc/cp/ChangeLog:
>
> * module.cc (depset::hash::add_specializations): Refine
> is_friend logic to clear the flag for full template friend
> instantiations.
>
> gcc/testsuite/ChangeLog:
>
> * g++.dg/modules/friend-13.C: Also test instantiations of a
> non-template friend of a
> * g++.dg/modules/friend-14_a.H: New test.
> * g++.dg/modules/friend-14_b.C: New test.
> ---
> gcc/cp/module.cc | 12 ++++++++++--
> gcc/testsuite/g++.dg/modules/friend-13.C | 1 +
> gcc/testsuite/g++.dg/modules/friend-14_a.H | 14 ++++++++++++++
> gcc/testsuite/g++.dg/modules/friend-14_b.C | 10 ++++++++++
> 4 files changed, 35 insertions(+), 2 deletions(-)
> create mode 100644 gcc/testsuite/g++.dg/modules/friend-14_a.H
> create mode 100644 gcc/testsuite/g++.dg/modules/friend-14_b.C
>
> diff --git a/gcc/cp/module.cc b/gcc/cp/module.cc
> index e61cd700d8fb..03a67ed25365 100644
> --- a/gcc/cp/module.cc
> +++ b/gcc/cp/module.cc
> @@ -15478,8 +15478,16 @@ depset::hash::add_specializations (bool decl_p)
> }
> else if (is_friend)
> {
> - if (TI_TEMPLATE (ti) != entry->tmpl
> - || !template_args_equal (TI_ARGS (ti), entry->tmpl))
> + if (TI_TEMPLATE (ti) != entry->tmpl)
It occurred to me that this condition for detecting a full instantiation
of a template friend is needlessly cryptic. A more obvious condition
(that passes regtesting) is:
if (TREE_CODE (spec) != TEMPLATE_DECL
&& PRIMARY_TEMPLATE_P (TI_TEMPLATE (ti)))
is_friend = false.
else
goto template_friend;
-- >8 --
Subject: [PATCH v2] c++/modules: full template friend specializations [PR125555]
Jason's analysis: Here the issue here seems to be that
1) we don't add op==<__ni<int>,__ni<int>> as a pending specialization to
any key, so we don't load it until we see the use of it in foo(), at
which point we've already created another decl for it. And then
2) we fail to merge the loaded specialization with the existing one.
If op== were a member template instead of a hidden friend, we'd get
Pending specialization '::__normal_iterator<int>::template operator==<_Iter>'
entity:4 section:2 also keyed to '::__normal_iterator'
Pending specialization
'::__normal_iterator<int>::operator==<::__normal_iterator<int>>' entity:7
section:3 keyed to '::__normal_iterator'
This lack seems to be because add_specializations completely ignores
friends; the existing code is clearly wrong, as it calls
template_args_equal to compare template arguments to a template rather
than another set of template arguments.
This patch partially corrects the is_friend code path treat to full
template friend instantiations as ordinary specializations, recording
them, while continuing to skip partial template friend instantiations.
This isn't perfect, because for consistent we probably also want to
clear is_friend in the non-template friend case, but removing the
PRIMARY_TEMPLATE_P check seems to break the augmented friend-13.C
testcase below. Still, this seems to be a strict improvement over the
current buggy code which skips all friend specializations.
PR c++/125555
gcc/cp/ChangeLog:
* module.cc (depset::hash::add_specializations): Refine
is_friend logic to clear the flag for full template friend
instantiations.
gcc/testsuite/ChangeLog:
* g++.dg/modules/friend-13.C: Also test instantiations of a
non-template friend of a
* g++.dg/modules/friend-14_a.H: New test.
* g++.dg/modules/friend-14_b.C: New test.
---
gcc/cp/module.cc | 13 +++++++++++--
gcc/testsuite/g++.dg/modules/friend-13.C | 1 +
gcc/testsuite/g++.dg/modules/friend-14_a.H | 14 ++++++++++++++
gcc/testsuite/g++.dg/modules/friend-14_b.C | 10 ++++++++++
4 files changed, 36 insertions(+), 2 deletions(-)
create mode 100644 gcc/testsuite/g++.dg/modules/friend-14_a.H
create mode 100644 gcc/testsuite/g++.dg/modules/friend-14_b.C
diff --git a/gcc/cp/module.cc b/gcc/cp/module.cc
index 571e7875ef61..a21dbe4b1262 100644
--- a/gcc/cp/module.cc
+++ b/gcc/cp/module.cc
@@ -15479,8 +15479,17 @@ depset::hash::add_specializations (bool decl_p)
}
else if (is_friend)
{
- if (TI_TEMPLATE (ti) != entry->tmpl
- || !template_args_equal (TI_ARGS (ti), entry->tmpl))
+ if (TREE_CODE (spec) != TEMPLATE_DECL
+ && PRIMARY_TEMPLATE_P (TI_TEMPLATE (ti)))
+ /* This is a full instantiation of a template friend (of a
+ class template), treat it as an ordinary specialization
+ (PR125555). */
+ is_friend = false;
+ else
+ /* A partial instantiation of such a template friend, or an
+ instantiation of a non-template friend (of a class
+ template). FIXME we probably should clear is_friend in
+ the latter case too but that breaks friend-13.C. */
goto template_friend;
}
}
diff --git a/gcc/testsuite/g++.dg/modules/friend-13.C
b/gcc/testsuite/g++.dg/modules/friend-13.C
index 8930eca7ded5..f9fd8e366470 100644
--- a/gcc/testsuite/g++.dg/modules/friend-13.C
+++ b/gcc/testsuite/g++.dg/modules/friend-13.C
@@ -5,6 +5,7 @@
module;
template <typename T> struct tuple {
template <typename U> friend void f(tuple, U);
+ friend void g(tuple, int) { }
};
template <typename T> tuple<T> make_unique();
export module M;
diff --git a/gcc/testsuite/g++.dg/modules/friend-14_a.H
b/gcc/testsuite/g++.dg/modules/friend-14_a.H
new file mode 100644
index 000000000000..b303f5cccc5b
--- /dev/null
+++ b/gcc/testsuite/g++.dg/modules/friend-14_a.H
@@ -0,0 +1,14 @@
+// PR c++/125555
+// { dg-additional-options "-fmodule-header" }
+
+template<typename _Tp>
+struct __normal_iterator {
+ template <typename _Iter>
+ friend bool operator==(__normal_iterator, _Iter) { return true; }
+};
+
+template <typename _Up>
+void foo() {
+ __normal_iterator<int> it;
+ it == it;
+}
diff --git a/gcc/testsuite/g++.dg/modules/friend-14_b.C
b/gcc/testsuite/g++.dg/modules/friend-14_b.C
new file mode 100644
index 000000000000..94189e5cc88a
--- /dev/null
+++ b/gcc/testsuite/g++.dg/modules/friend-14_b.C
@@ -0,0 +1,10 @@
+// PR c++/125555
+// { dg-additional-options "-fmodules" }
+
+import "friend-14_a.H";
+
+void test() {
+ __normal_iterator<int> it;
+ it == it;
+ foo<void>();
+}
--
2.55.0.787.g3cb9185f65