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

Jason Merrill jason@redhat.com
Wed Jun 30 20:23:45 GMT 2021


On 6/30/21 4:18 PM, Patrick Palka wrote:
> 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.

Or replacing their level with the appropriate level for the args we're 
dealing with/whether tf_partial is set?

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