On Wed, Jun 30, 2021 at 4:23 PM Jason Merrill <[email protected]> wrote:
>
> On 6/30/21 4:18 PM, Patrick Palka wrote:
> > On Wed, Jun 30, 2021 at 3:51 PM Jason Merrill <[email protected]> wrote:
> >>
> >> On 6/30/21 11:58 AM, Patrick Palka wrote:
> >>> On Wed, 30 Jun 2021, Patrick Palka wrote:
> >>>
> >>>> On Fri, 25 Jun 2021, Jason Merrill wrote:
> >>>>
> >>>>> On 6/25/21 1:11 PM, Patrick Palka wrote:
> >>>>>> On Fri, 25 Jun 2021, Jason Merrill wrote:
> >>>>>>
> >>>>>>> On 6/24/21 4:45 PM, Patrick Palka wrote:
> >>>>>>>> In the first testcase below, during parsing of the alias template
> >>>>>>>> ConstSpanType, transparency of alias template specializations means
> >>>>>>>> we
> >>>>>>>> replace SpanType<T> with SpanType's substituted definition. But this
> >>>>>>>> substitution lowers the level of the CTAD placeholder for span(T())
> >>>>>>>> from
> >>>>>>>> 2 to 1, and so the later instantiantion of ConstSpanType<int>
> >>>>>>>> erroneously substitutes this CTAD placeholder with the template
> >>>>>>>> argument
> >>>>>>>> at level 1 index 0, i.e. with int, before we get a chance to perform
> >>>>>>>> the
> >>>>>>>> CTAD.
> >>>>>>>>
> >>>>>>>> In light of this, it seems we should avoid level lowering when
> >>>>>>>> substituting through through the type-id of a dependent alias
> >>>>>>>> template
> >>>>>>>> specialization. To that end this patch makes lookup_template_class_1
> >>>>>>>> pass tf_partial to tsubst in this situation.
> >>>>>>>
> >>>>>>> This makes sense, but what happens if SpanType is a member template,
> >>>>>>> so
> >>>>>>> that
> >>>>>>> the levels of it and ConstSpanType don't match? Or the other way
> >>>>>>> around?
> >>>>>>
> >>>>>> If SpanType<T> is a member template of say the class template A<U> (and
> >>>>>> thus its level is greater than ConstSpanType):
> >>>>>>
> >>>>>> template<class U>
> >>>>>> struct A {
> >>>>>> template<class T>
> >>>>>> using SpanType = decltype(span(T()));
> >>>>>> };
> >>>>>>
> >>>>>> template<class T>
> >>>>>> using ConstSpanType = span<const typename
> >>>>>> A<int>::SpanType<T>::value_type>;
> >>>>>>
> >>>>>> using type = ConstSpanType<int>;
> >>>>>>
> >>>>>> then this case luckily works even without the patch because
> >>>>>> instantiate_class_template now reuses the specialization
> >>>>>> A<int>::SpanType<T>
> >>>>>> that was formed earlier during instantiation of A<int>, where we
> >>>>>> substitute only a single level of template arguments, so the level of
> >>>>>> the CTAD placeholder inside the defining-type-id of this specialization
> >>>>>> dropped from 3 to 2, so still more than the level of ConstSpanType.
> >>>>>>
> >>>>>> This luck is short-lived though, because if we replace
> >>>>>> A<int>::SpanType<T> with say A<int>::SpanType<const T> then the
> >>>>>> testcase
> >>>>>> breaks again (without the patch) because we no longer can reuse that
> >>>>>> specialization, so we instead form it on the spot by substituting two
> >>>>>> levels of template arguments (U=int,T=T) into the defining-type-id,
> >>>>>> causing the level of the placeholder to drop to 1. I think the patch
> >>>>>> causes its level to remain 3 (though I guess it should really be 2).
> >>>>>>
> >>>>>>
> >>>>>> For the other way around, if ConstSpanType<T> is a member template of
> >>>>>> say the class template B<V> (and thus its level is greater than
> >>>>>> SpanType):
> >>>>>>
> >>>>>> template<class T>
> >>>>>> using SpanType = decltype(span(T()));
> >>>>>>
> >>>>>> template<class V>
> >>>>>> struct B {
> >>>>>> template<class T>
> >>>>>> using ConstSpanType = span<const typename
> >>>>>> SpanType<T>::value_type>;
> >>>>>> };
> >>>>>>
> >>>>>> using type = B<char>::ConstSpanType<int>;
> >>>>>>
> >>>>>> then tf_partial doesn't help here at all; we end up substituting 'int'
> >>>>>> for the CTAD placeholder... What it seems we need is to _increase_ the
> >>>>>> level of the CTAD placeholder from 2 to 3 during the dependent
> >>>>>> substitution..
> >>>>>>
> >>>>>> Hmm, rather than messing with tf_partial, which is apparently only a
> >>>>>> partial solution, maybe we should just make tsubst never substitute a
> >>>>>> CTAD placeholder -- they should always be resolved from
> >>>>>> do_class_deduction,
> >>>>>> and their level doesn't really matter otherwise. (But we'd still want
> >>>>>> to substitute into the CLASS_PLACEHOLDER_TEMPLATE of the placeholder in
> >>>>>> case it's a template template parm.) Something like:
> >>>>>>
> >>>>>> diff --git a/gcc/cp/pt.c b/gcc/cp/pt.c
> >>>>>> index 5107bfbf9d1..dead651ed84 100644
> >>>>>> --- a/gcc/cp/pt.c
> >>>>>> +++ b/gcc/cp/pt.c
> >>>>>> @@ -15552,7 +15550,8 @@ tsubst (tree t, tree args, tsubst_flags_t
> >>>>>> complain,
> >>>>>> tree in_decl)
> >>>>>> levels = TMPL_ARGS_DEPTH (args);
> >>>>>> if (level <= levels
> >>>>>> - && TREE_VEC_LENGTH (TMPL_ARGS_LEVEL (args, level)) > 0)
> >>>>>> + && TREE_VEC_LENGTH (TMPL_ARGS_LEVEL (args, level)) > 0
> >>>>>> + && !template_placeholder_p (t))
> >>>>>> {
> >>>>>> arg = TMPL_ARG (args, level, idx);
> >>>>>>
> >>>>>> seems to work better.
> >>>>>
> >>>>> Makes sense.
> >>>>
> >>>> Here's a patch that implements that. I reckon it's good to have both
> >>>> workarounds in place because the tf_partial workaround is necessary to
> >>>> accept class-deduction93a.C below, and the tsubst workaround is
> >>>> necessary to accept class-deduction-92b.C below.
> >>>
> >>> Whoops, forgot to git-add class-deduction93a.C:
> >>>
> >>> -- >8 --
> >>>
> >>> Subject: [PATCH] c++: CTAD within alias template [PR91911]
> >>>
> >>> In the first testcase below, during parsing of the alias template
> >>> ConstSpanType, transparency of alias template specializations means we
> >>> replace SpanType<T> with SpanType's substituted definition. But this
> >>> substitution lowers the level of the CTAD placeholder for span{T()} from
> >>> 2 to 1, and so the later instantiation of ConstSpanType<int> erroneously
> >>> substitutes this CTAD placeholder with the template argument at level 1
> >>> index 0, i.e. with int, before we get a chance to perform the CTAD.
> >>>
> >>> In light of this, it seems we should avoid level lowering when
> >>> substituting through the type-id of a dependent alias template
> >>> specialization. To that end this patch makes lookup_template_class_1
> >>> pass tf_partial to tsubst in this situation.
> >>>
> >>> Unfortunately, using tf_partial alone isn't sufficient because the
> >>> template context in which we perform the dependent substitution may
> >>> have more levels than the substituted alias template and so we
> >>> end up substituting the CTAD placeholder anyway, as in
> >>> class-deduction92b.C below. (There, it seems we'd need to _increase_
> >>> the level of the placeholder for span{T()} from 2 to 3 during the
> >>> dependent substitution.) Since we never want to resolve a CTAD
> >>> placeholder outside of CTAD proper, this patch takes the relatively
> >>> ad-hoc approach of making tsubst explicitly avoid doing so.
> >>>
> >>> This tsubst workaround doesn't obviate the tf_partial workaround because
> >>> it's still desirable to avoid prematurely level lowering a CTAD
> >>> placeholder:
> >>> it's less work for the compiler, and it gives us a chance to substitute
> >>> a template placeholder that's a template template parameter with a
> >>> concrete template template argument, as in the last testcase below.
> >>
> >> Hmm, what if we combine the template template parameter with the level
> >> mismatch?
> >
> > So for e.g.
> >
> > template<class F, template<class> class Tmpl>
> > using CallableTraitT = CallableTrait<decltype(Tmpl{F()})>;
> >
> > template<class>
> > struct A {
> > template<class F, template<class> class Tmpl>
> > using ReturnType = typename CallableTraitT<F, Tmpl>::ReturnType;
> > };
> >
> > using type = A<int>::ReturnType<int(*)(), function>;
> > using type = int;
> >
> > sadly we crash, because during the dependent substitution of the
> > innermost arguments into the defining-type-id, tf_partial means we
> > don't lower the level of the CTAD placeholder and therefore don't
> > substitute into CLASS_PLACEHOLDER_TEMPLATE, so
> > CLASS_PLACEHOLDER_TEMPLATE remains a template template parm at index 1
> > level 1 (as opposed to level 2). Later during the full
> > instantiation, there is no such template template argument at that
> > position (it's at index 1 level 2 rather).
> >
> > To handle this testcase, it seems we need a way to substitute into
> > CTAD placeholders without lowering their level I guess.
>
> Or replacing their level with the appropriate level for the args we're
> dealing with/whether tf_partial is set?
That sounds like it might work for CTAD placeholders, since we never
want to replace them via tsubst anyway. But I suppose a complete
solution to this problem would also need to adjust the level of 'auto'
that appears inside unevaluated lambdas (and C++23 auto(x) now too, I
guess). And the tricky part with those is that we do sometimes want
to replace 'auto's via tsubst, in particular during
do_auto_deduction..
I wonder if for now the v1 patch (the one consisting of just the
lookup_template_class_1 change) can go in? I noticed that it also
fixes a slew of (essentially duplicate) PRs about simple uses of
unevaluated lambdas within alias templates: 100594, 92211, 103569,
102680, 101315, 101013, 92707. (The template_placeholder_p change in
the v2 patch I realized is buggy -- by avoiding substitution into the
CTAD placeholder, we fall back to level-lowering, but since level <
levels we end up creating a CTAD placeholder with level close to
INT_MIN).
>
> >>
> >>> Bootstrapped and regtested on x86_64-pc-linux-gnu, does this look OK for
> >>> trunk?
> >>>
> >>> PR c++/91911
> >>>
> >>> gcc/cp/ChangeLog:
> >>>
> >>> * pt.c (lookup_template_class_1): When looking up a dependent
> >>> alias template specialization, pass tf_partial to tsubst.
> >>> (tsubst) <case TEMPLATE_TYPE_PARM>: Avoid substituting a CTAD
> >>> placeholder.
> >>>
> >>> gcc/testsuite/ChangeLog:
> >>>
> >>> * g++.dg/cpp1z/class-deduction92.C: New test.
> >>> * g++.dg/cpp1z/class-deduction92a.C: New test.
> >>> * g++.dg/cpp1z/class-deduction92b.C: New test.
> >>> * g++.dg/cpp1z/class-deduction93.C: New test.
> >>> * g++.dg/cpp1z/class-deduction93a.C: New test.
> >>> ---
> >>> gcc/cp/pt.c | 15 +++++++++--
> >>> .../g++.dg/cpp1z/class-deduction92.C | 17 ++++++++++++
> >>> .../g++.dg/cpp1z/class-deduction92a.C | 22 +++++++++++++++
> >>> .../g++.dg/cpp1z/class-deduction92b.C | 22 +++++++++++++++
> >>> .../g++.dg/cpp1z/class-deduction93.C | 25 +++++++++++++++++
> >>> .../g++.dg/cpp1z/class-deduction93a.C | 27 +++++++++++++++++++
> >>> 6 files changed, 126 insertions(+), 2 deletions(-)
> >>> create mode 100644 gcc/testsuite/g++.dg/cpp1z/class-deduction92.C
> >>> create mode 100644 gcc/testsuite/g++.dg/cpp1z/class-deduction92a.C
> >>> create mode 100644 gcc/testsuite/g++.dg/cpp1z/class-deduction92b.C
> >>> create mode 100644 gcc/testsuite/g++.dg/cpp1z/class-deduction93.C
> >>> create mode 100644 gcc/testsuite/g++.dg/cpp1z/class-deduction93a.C
> >>>
> >>> diff --git a/gcc/cp/pt.c b/gcc/cp/pt.c
> >>> index f2039e09cd7..db769d59951 100644
> >>> --- a/gcc/cp/pt.c
> >>> +++ b/gcc/cp/pt.c
> >>> @@ -9954,7 +9954,12 @@ lookup_template_class_1 (tree d1, tree arglist,
> >>> tree in_decl, tree context,
> >>> template-arguments for the template-parameters in the
> >>> type-id of the alias template. */
> >>>
> >>> - t = tsubst (TREE_TYPE (gen_tmpl), arglist, complain, in_decl);
> >>> + /* When substituting a dependent alias template specialization,
> >>> + we pass tf_partial to avoid lowering the level of any 'auto's
> >>> + within its type-id (91911). */
> >>> + t = tsubst (TREE_TYPE (gen_tmpl), arglist,
> >>> + complain | (tf_partial * is_dependent_type),
> >>> + in_decl);
> >>> /* Note that the call above (by indirectly calling
> >>> register_specialization in tsubst_decl) registers the
> >>> TYPE_DECL representing the specialization of the alias
> >>> @@ -15544,9 +15549,15 @@ tsubst (tree t, tree args, tsubst_flags_t
> >>> complain, tree in_decl)
> >>> gcc_assert (TREE_VEC_LENGTH (args) > 0);
> >>> template_parm_level_and_index (t, &level, &idx);
> >>>
> >>> + /* Retrieve the argument for this template parameter. */
> >>> levels = TMPL_ARGS_DEPTH (args);
> >>> if (level <= levels
> >>> - && TREE_VEC_LENGTH (TMPL_ARGS_LEVEL (args, level)) > 0)
> >>> + && TREE_VEC_LENGTH (TMPL_ARGS_LEVEL (args, level)) > 0
> >>> + /* Avoid substituting CTAD placeholders; they get
> >>> + resolved only during CTAD proper. We can get here
> >>> + after dependent substitution of an alias template
> >>> + whose defining-type-id uses CTAD (91911). */
> >>> + && !template_placeholder_p (t))
> >>> {
> >>> arg = TMPL_ARG (args, level, idx);
> >>>
> >>> diff --git a/gcc/testsuite/g++.dg/cpp1z/class-deduction92.C
> >>> b/gcc/testsuite/g++.dg/cpp1z/class-deduction92.C
> >>> new file mode 100644
> >>> index 00000000000..379eb960da6
> >>> --- /dev/null
> >>> +++ b/gcc/testsuite/g++.dg/cpp1z/class-deduction92.C
> >>> @@ -0,0 +1,17 @@
> >>> +// PR c++/91911
> >>> +// { dg-do compile { target c++17 } }
> >>> +
> >>> +template<class T>
> >>> +struct span {
> >>> + using value_type = T;
> >>> + span(T);
> >>> +};
> >>> +
> >>> +template<class T>
> >>> +using SpanType = decltype(span{T()});
> >>> +
> >>> +template<class T>
> >>> +using ConstSpanType = span<const typename SpanType<T>::value_type>;
> >>> +
> >>> +using type = ConstSpanType<int>;
> >>> +using type = span<const int>;
> >>> diff --git a/gcc/testsuite/g++.dg/cpp1z/class-deduction92a.C
> >>> b/gcc/testsuite/g++.dg/cpp1z/class-deduction92a.C
> >>> new file mode 100644
> >>> index 00000000000..b9aa8f3bbf0
> >>> --- /dev/null
> >>> +++ b/gcc/testsuite/g++.dg/cpp1z/class-deduction92a.C
> >>> @@ -0,0 +1,22 @@
> >>> +// PR c++/91911
> >>> +// { dg-do compile { target c++17 } }
> >>> +// A variant of class-deduction92.C where SpanType has more levels than
> >>> +// ConstSpanType.
> >>> +
> >>> +template<class T>
> >>> +struct span {
> >>> + using value_type = T;
> >>> + span(T);
> >>> +};
> >>> +
> >>> +template<class>
> >>> +struct A {
> >>> + template<class T>
> >>> + using SpanType = decltype(span{T()});
> >>> +};
> >>> +
> >>> +template<class T>
> >>> +using ConstSpanType = span<const typename A<int>::SpanType<const
> >>> T>::value_type>;
> >>> +
> >>> +using type = ConstSpanType<int>;
> >>> +using type = span<const int>;
> >>> diff --git a/gcc/testsuite/g++.dg/cpp1z/class-deduction92b.C
> >>> b/gcc/testsuite/g++.dg/cpp1z/class-deduction92b.C
> >>> new file mode 100644
> >>> index 00000000000..0ea0cef0238
> >>> --- /dev/null
> >>> +++ b/gcc/testsuite/g++.dg/cpp1z/class-deduction92b.C
> >>> @@ -0,0 +1,22 @@
> >>> +// PR c++/91911
> >>> +// { dg-do compile { target c++17 } }
> >>> +// A variant of class-deduction92.C where SpanType has fewer levels than
> >>> +// ConstSpanType.
> >>> +
> >>> +template<class T>
> >>> +struct span {
> >>> + using value_type = T;
> >>> + span(T);
> >>> +};
> >>> +
> >>> +template<class T>
> >>> +using SpanType = decltype(span{T()});
> >>> +
> >>> +template<class>
> >>> +struct B {
> >>> + template<class T>
> >>> + using ConstSpanType = span<const typename SpanType<T>::value_type>;
> >>> +};
> >>> +
> >>> +using type = B<int>::ConstSpanType<int>;
> >>> +using type = span<const int>;
> >>> diff --git a/gcc/testsuite/g++.dg/cpp1z/class-deduction93.C
> >>> b/gcc/testsuite/g++.dg/cpp1z/class-deduction93.C
> >>> new file mode 100644
> >>> index 00000000000..20504780d32
> >>> --- /dev/null
> >>> +++ b/gcc/testsuite/g++.dg/cpp1z/class-deduction93.C
> >>> @@ -0,0 +1,25 @@
> >>> +// PR c++/98077
> >>> +// { dg-do compile { target c++17 } }
> >>> +
> >>> +template<class R>
> >>> +struct function {
> >>> + template<class T> function(T);
> >>> + using type = R;
> >>> +};
> >>> +
> >>> +template<class T> function(T) -> function<decltype(T()())>;
> >>> +
> >>> +template<class T>
> >>> +struct CallableTrait;
> >>> +
> >>> +template<class R>
> >>> +struct CallableTrait<function<R>> { using ReturnType = R; };
> >>> +
> >>> +template<class F>
> >>> +using CallableTraitT = CallableTrait<decltype(function{F()})>;
> >>> +
> >>> +template<class F>
> >>> +using ReturnType = typename CallableTraitT<F>::ReturnType;
> >>> +
> >>> +using type = ReturnType<int(*)()>;
> >>> +using type = int;
> >>> diff --git a/gcc/testsuite/g++.dg/cpp1z/class-deduction93a.C
> >>> b/gcc/testsuite/g++.dg/cpp1z/class-deduction93a.C
> >>> new file mode 100644
> >>> index 00000000000..7656e7845fe
> >>> --- /dev/null
> >>> +++ b/gcc/testsuite/g++.dg/cpp1z/class-deduction93a.C
> >>> @@ -0,0 +1,27 @@
> >>> +// PR c++/98077
> >>> +// { dg-do compile { target c++17 } }
> >>> +// A variant of class-deduction93.C where the template placeholder is a
> >>> template
> >>> +// template parameter.
> >>> +
> >>> +template<class R>
> >>> +struct function {
> >>> + template<class T> function(T);
> >>> + using type = R;
> >>> +};
> >>> +
> >>> +template<class T> function(T) -> function<decltype(T()())>;
> >>> +
> >>> +template<class T>
> >>> +struct CallableTrait;
> >>> +
> >>> +template<class R>
> >>> +struct CallableTrait<function<R>> { using ReturnType = R; };
> >>> +
> >>> +template<class F, template<class> class Tmpl>
> >>> +using CallableTraitT = CallableTrait<decltype(Tmpl{F()})>;
> >>> +
> >>> +template<class F, template<class> class Tmpl>
> >>> +using ReturnType = typename CallableTraitT<F, Tmpl>::ReturnType;
> >>> +
> >>> +using type = ReturnType<int(*)(), function>;
> >>> +using type = int;
> >>>
> >>
> >
>