On Wed, Jun 30, 2021 at 4:23 PM Jason Merrill <ja...@redhat.com> wrote:
>
> On 6/30/21 4:18 PM, Patrick Palka wrote:
> > On Wed, Jun 30, 2021 at 3:51 PM Jason Merrill <ja...@redhat.com> 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;
> >>>
> >>
> >
>

Reply via email to