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