[PATCH] c++: CTAD within alias template [PR91911]

Patrick Palka ppalka@redhat.com
Wed Jun 30 20:18:23 GMT 2021


On Wed, Jun 30, 2021 at 3:51 PM Jason Merrill <jason@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.


>
> > 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;
> >
>



More information about the Gcc-patches mailing list