Message ID | 20240709002841.3227323-1-ppalka@redhat.com |
---|---|
State | New |
Headers | show |
Series | c++: prev declared hidden tmpl friend inst [PR112288] | expand |
On 7/8/24 8:28 PM, Patrick Palka wrote: > Bootstrapped and regtested on x86_64-pc-linux-gnu, does this look > OK for trunk/14? OK. Interesting technique in that first test... > -- >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 > > 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() { }
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() { }