On Tue, Dec 21, 2021 at 2:03 PM Patrick Palka <ppa...@redhat.com> wrote: > > 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).
Whoops, sorry about the thinko in the last sentence, what I should have said is, ... since level <= levels we end up creating a CTAD placeholder with nonpositive level. > > > > > > >> > > >>> 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; > > >>> > > >> > > > > >