Message ID | 20210624204529.4136660-1-ppalka@redhat.com |
---|---|
State | New |
Headers | show |
Series | c++: CTAD within alias template [PR91911] | expand |
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? > 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. > > gcc/testsuite/ChangeLog: > > * g++.dg/cpp1z/class-deduction92.C: New test. > --- > gcc/cp/pt.c | 7 +++++- > .../g++.dg/cpp1z/class-deduction92.C | 17 +++++++++++++ > .../g++.dg/cpp1z/class-deduction93.C | 25 +++++++++++++++++++ > 3 files changed, 48 insertions(+), 1 deletion(-) > create mode 100644 gcc/testsuite/g++.dg/cpp1z/class-deduction92.C > create mode 100644 gcc/testsuite/g++.dg/cpp1z/class-deduction93.C > > diff --git a/gcc/cp/pt.c b/gcc/cp/pt.c > index f73c7471a33..23c5f515716 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' > + in its type-id which might correspond to CTAD placeholders. */ > + t = tsubst (TREE_TYPE (gen_tmpl), arglist, > + complain | (is_dependent_type * tf_partial), > + 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 > 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..ae3c55508b2 > --- /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-deduction93.C b/gcc/testsuite/g++.dg/cpp1z/class-deduction93.C > new file mode 100644 > index 00000000000..eebc986832e > --- /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 = decltype(function{F()}); > + > +template<class F> > +using ReturnType = typename CallableTraitT<F>::type; > + > +using type = ReturnType<int(*)()>; > +using type = int; >
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. > > > 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. > > > > gcc/testsuite/ChangeLog: > > > > * g++.dg/cpp1z/class-deduction92.C: New test. > > --- > > gcc/cp/pt.c | 7 +++++- > > .../g++.dg/cpp1z/class-deduction92.C | 17 +++++++++++++ > > .../g++.dg/cpp1z/class-deduction93.C | 25 +++++++++++++++++++ > > 3 files changed, 48 insertions(+), 1 deletion(-) > > create mode 100644 gcc/testsuite/g++.dg/cpp1z/class-deduction92.C > > create mode 100644 gcc/testsuite/g++.dg/cpp1z/class-deduction93.C > > > > diff --git a/gcc/cp/pt.c b/gcc/cp/pt.c > > index f73c7471a33..23c5f515716 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' > > + in its type-id which might correspond to CTAD placeholders. */ > > + t = tsubst (TREE_TYPE (gen_tmpl), arglist, > > + complain | (is_dependent_type * tf_partial), > > + 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 > > 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..ae3c55508b2 > > --- /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-deduction93.C > > b/gcc/testsuite/g++.dg/cpp1z/class-deduction93.C > > new file mode 100644 > > index 00000000000..eebc986832e > > --- /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 = decltype(function{F()}); > > + > > +template<class F> > > +using ReturnType = typename CallableTraitT<F>::type; > > + > > +using type = ReturnType<int(*)()>; > > +using type = int; > > > >
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. >> >>> 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. >>> >>> gcc/testsuite/ChangeLog: >>> >>> * g++.dg/cpp1z/class-deduction92.C: New test. >>> --- >>> gcc/cp/pt.c | 7 +++++- >>> .../g++.dg/cpp1z/class-deduction92.C | 17 +++++++++++++ >>> .../g++.dg/cpp1z/class-deduction93.C | 25 +++++++++++++++++++ >>> 3 files changed, 48 insertions(+), 1 deletion(-) >>> create mode 100644 gcc/testsuite/g++.dg/cpp1z/class-deduction92.C >>> create mode 100644 gcc/testsuite/g++.dg/cpp1z/class-deduction93.C >>> >>> diff --git a/gcc/cp/pt.c b/gcc/cp/pt.c >>> index f73c7471a33..23c5f515716 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' >>> + in its type-id which might correspond to CTAD placeholders. */ >>> + t = tsubst (TREE_TYPE (gen_tmpl), arglist, >>> + complain | (is_dependent_type * tf_partial), >>> + 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 >>> 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..ae3c55508b2 >>> --- /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-deduction93.C >>> b/gcc/testsuite/g++.dg/cpp1z/class-deduction93.C >>> new file mode 100644 >>> index 00000000000..eebc986832e >>> --- /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 = decltype(function{F()}); >>> + >>> +template<class F> >>> +using ReturnType = typename CallableTraitT<F>::type; >>> + >>> +using type = ReturnType<int(*)()>; >>> +using type = int; >>> >> >> >
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. -- >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 good 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. 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 +++++++++++++++++++ 5 files changed, 99 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 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;
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. 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;
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? > 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; >
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; > > >
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; >>> >> >
On Wed, Jun 30, 2021 at 4:23 PM Jason Merrill <jason@redhat.com> wrote: > > 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? 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; > >>> > >> > > >
On Tue, Dec 21, 2021 at 2:03 PM Patrick Palka <ppalka@redhat.com> wrote: > > On Wed, Jun 30, 2021 at 4:23 PM Jason Merrill <jason@redhat.com> wrote: > > > > 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? > > 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; > > >>> > > >> > > > > >
On 12/21/21 14:08, Patrick Palka wrote: > On Tue, Dec 21, 2021 at 2:03 PM Patrick Palka <ppalka@redhat.com> wrote: >> >> On Wed, Jun 30, 2021 at 4:23 PM Jason Merrill <jason@redhat.com> wrote: >>> >>> 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? >> >> 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 nonpositive level. > - 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' > + in its type-id which might correspond to CTAD placeholders. */ > + t = tsubst (TREE_TYPE (gen_tmpl), arglist, > + complain | (is_dependent_type * tf_partial), > + in_decl); So, if we aren't changing any containing template scopes from dependent to non-dependent, we don't want to mess with levels. I think is_dependent_type is a bit too broad here; I'd expect this could cause trouble when e.g. instantiating a class A<int> with a member template B and we have both B<U> and an auto in the signature of a member template. I think what we want to check is whether the outermost args are dependent. It would also be safer to handle adding tf_partial for alias templates in instantiate_alias_template rather than lookup_template_class. Perhaps in alias_ctad_tweaks as well. I tried adding an assert that tf_partial is set any time we see dependent outermost args; I expected to need to override that for deduction guide rewriting, but also seem to hit problems in concepts and TTP. Attached in case you're interested; I don't think this is going to become a patch suitable for GCC 12. The use of uses_template_parms_level was a kludge because dependent_template_arg_p returns true for null args. Jason
On Wed, 22 Dec 2021, Jason Merrill wrote: > On 12/21/21 14:08, Patrick Palka wrote: > > On Tue, Dec 21, 2021 at 2:03 PM Patrick Palka <ppalka@redhat.com> wrote: > > > > > > On Wed, Jun 30, 2021 at 4:23 PM Jason Merrill <jason@redhat.com> wrote: > > > > > > > > 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? > > > > > > 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 nonpositive level. > > > - 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' > > + in its type-id which might correspond to CTAD placeholders. */ > > + t = tsubst (TREE_TYPE (gen_tmpl), arglist, > > + complain | (is_dependent_type * tf_partial), > > + in_decl); > > So, if we aren't changing any containing template scopes from dependent to > non-dependent, we don't want to mess with levels. > > I think is_dependent_type is a bit too broad here; I'd expect this could cause > trouble when e.g. instantiating a class A<int> with a member template B and we > have both B<U> and an auto in the signature of a member template. I think > what we want to check is whether the outermost args are dependent. Ah yeah, I see what you mean... > > It would also be safer to handle adding tf_partial for alias templates in > instantiate_alias_template rather than lookup_template_class. Perhaps in > alias_ctad_tweaks as well. > > I tried adding an assert that tf_partial is set any time we see dependent > outermost args; I expected to need to override that for deduction guide > rewriting, but also seem to hit problems in concepts and TTP. Attached in > case you're interested; I don't think this is going to become a patch suitable > for GCC 12. The use of uses_template_parms_level was a kludge because > dependent_template_arg_p returns true for null args. Interesting. In light of this general problem, I wonder if representing autos as template parameters with one level greater than the template depth, while convenient, is ultimately not the best approach? Back to the original issue concerning CTAD within alias templates, here's an approach that seems to work well. The idea is to treat CTAD placeholders less like template parameters, by giving them the special level 0, and by making tsubst avoid substituting them like template parameters. With this approach the representation of a CTAD placeholder no longer depends on the template depth, so alias template transparency for alias templates that contain CTAD just works. I tried extending this approach to all autos (i.e. giving all autos a level of 0 and adjusting tsubst accordingly), and it nearly works modulo handling concepts TS auto... deduction: tuple<auto...> x = tuple<int, char>{}. since unify_pack_expansion isn't prepared to see a parameter pack of level 0. This is likely fixable, but I suppose it'd be good to first get confirmation that this is a good approach before continuing down this patch. Below the patch that implements this approach only for CTAD placeholders. Attached is an incremental WIP diff that additionally extends the approach to all autos, which passes testing modulo the concept TS thing. -- >8 -- Subject: [PATCH] c++: CTAD inside 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 its instantiated definition. But this instantiation 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. Although we represent CTAD placeholders as template parameters, we never actually want to replace them via tsubst. So this patch adjusts tsubst to handle CTAD placeholders by simply substituting the template and returning a new CTAD placeholder. Moreover, this means that the level of a CTAD placeholder doesn't matter, so we may as well give them all the same level. This patch gives them the special level 0. The change in grokdeclarator makes us reject an invalid function return type consisting of a CTAD placeholder sooner (as in pr88187.C), which helps guarantee that splice_late_return_type doesn't see or need to handle a erroneous CTAD placeholder return type. The change in tsubst_decl removes a CHECKING_P workaround added in 2017, which would otherwise now get triggered for variables with CTAD placeholder types (since their level is 0). Alternatively, we could just guard the workaround with !template_placeholder_p if that's preferable. Bootstrapped and regtested on x86_64-pc-linux-gnu, does this look OK for trunk? PR c++/91911 gcc/cp/ChangeLog: * decl.c (grokdeclarator): Diagnose CTAD placeholder in function return type even when !funcdecl_p. * pt.c (keep_template_parm): Punt on a level 0 template parm. (tsubst_decl) <case VAR_DECL>: Remove CHECKING_P workaround. (tsubst) <case TEMPLATE_TYPE_PARM>: Handle CTAD placeholders specially. (make_auto_1): Add defaulted 'level' parameter. (make_template_placeholder): Pass 0 as 'level' to make_auto_1. gcc/testsuite/ChangeLog: * g++.dg/cpp1z/class-deduction100.C: New test. * g++.dg/cpp1z/class-deduction100a.C: New test. * g++.dg/cpp1z/class-deduction100b.C: New test. * g++.dg/cpp1z/class-deduction101.C: New test. * g++.dg/cpp1z/class-deduction101a.C: New test. * g++.dg/cpp1z/class-deduction101b.C: New test. --- gcc/cp/decl.c | 6 +- gcc/cp/pt.c | 60 ++++++++----------- .../g++.dg/cpp1z/class-deduction100.C | 17 ++++++ .../g++.dg/cpp1z/class-deduction100a.C | 22 +++++++ .../g++.dg/cpp1z/class-deduction100b.C | 22 +++++++ .../g++.dg/cpp1z/class-deduction101.C | 25 ++++++++ .../g++.dg/cpp1z/class-deduction101a.C | 27 +++++++++ .../g++.dg/cpp1z/class-deduction101b.C | 30 ++++++++++ gcc/testsuite/g++.dg/other/pr88187.C | 2 +- 9 files changed, 173 insertions(+), 38 deletions(-) create mode 100644 gcc/testsuite/g++.dg/cpp1z/class-deduction100.C create mode 100644 gcc/testsuite/g++.dg/cpp1z/class-deduction100a.C create mode 100644 gcc/testsuite/g++.dg/cpp1z/class-deduction100b.C create mode 100644 gcc/testsuite/g++.dg/cpp1z/class-deduction101.C create mode 100644 gcc/testsuite/g++.dg/cpp1z/class-deduction101a.C create mode 100644 gcc/testsuite/g++.dg/cpp1z/class-deduction101b.C diff --git a/gcc/cp/decl.c b/gcc/cp/decl.c index 0b71c00f5ab..406a9163ffd 100644 --- a/gcc/cp/decl.c +++ b/gcc/cp/decl.c @@ -12635,11 +12635,11 @@ grokdeclarator (const cp_declarator *declarator, if (!tmpl) if (tree late_auto = type_uses_auto (late_return_type)) tmpl = CLASS_PLACEHOLDER_TEMPLATE (late_auto); - if (tmpl && funcdecl_p) + if (tmpl) { - if (!dguide_name_p (unqualified_id)) + if (!funcdecl_p || !dguide_name_p (unqualified_id)) { - error_at (declarator->id_loc, "deduced class " + error_at (typespec_loc, "deduced class " "type %qD in function return type", DECL_NAME (tmpl)); inform (DECL_SOURCE_LOCATION (tmpl), diff --git a/gcc/cp/pt.c b/gcc/cp/pt.c index c587966adbe..08e34c7404a 100644 --- a/gcc/cp/pt.c +++ b/gcc/cp/pt.c @@ -10660,7 +10660,7 @@ keep_template_parm (tree t, void* data) int level; int index; template_parm_level_and_index (t, &level, &index); - if (level > ftpi->max_depth) + if (level == 0 || level > ftpi->max_depth) return 0; if (TREE_CODE (t) == BOUND_TEMPLATE_TEMPLATE_PARM) @@ -14796,20 +14796,7 @@ tsubst_decl (tree t, tree args, tsubst_flags_t complain) && VAR_HAD_UNKNOWN_BOUND (t) && type != error_mark_node) type = strip_array_domain (type); - tree sub_args = args; - if (tree auto_node = type_uses_auto (type)) - { - /* Mask off any template args past the variable's context so we - don't replace the auto with an unrelated argument. */ - int nouter = TEMPLATE_TYPE_LEVEL (auto_node) - 1; - int extra = TMPL_ARGS_DEPTH (args) - nouter; - if (extra > 0) - /* This should never happen with the new lambda instantiation - model, but keep the handling just in case. */ - gcc_assert (!CHECKING_P), - sub_args = strip_innermost_template_args (args, extra); - } - type = tsubst (type, sub_args, complain, in_decl); + type = tsubst (type, args, complain, in_decl); /* Substituting the type might have recursively instantiated this same alias (c++/86171). */ if (gen_tmpl && DECL_ALIAS_TEMPLATE_P (gen_tmpl) @@ -15561,6 +15548,19 @@ tsubst (tree t, tree args, tsubst_flags_t complain, tree in_decl) } case TEMPLATE_TYPE_PARM: + if (template_placeholder_p (t)) + { + tree tmpl = CLASS_PLACEHOLDER_TEMPLATE (t); + tmpl = tsubst_copy (tmpl, args, complain, in_decl); + if (TREE_CODE (tmpl) == TEMPLATE_TEMPLATE_PARM) + tmpl = TEMPLATE_TEMPLATE_PARM_TEMPLATE_DECL (tmpl); + + if (tmpl != CLASS_PLACEHOLDER_TEMPLATE (t)) + return make_template_placeholder (tmpl); + else + return t; + } + /* Fall through. */ case TEMPLATE_TEMPLATE_PARM: case BOUND_TEMPLATE_TEMPLATE_PARM: case TEMPLATE_PARM_INDEX: @@ -15734,7 +15734,6 @@ tsubst (tree t, tree args, tsubst_flags_t complain, tree in_decl) of a constrained placeholder. */; else if (TREE_CODE (t) == TEMPLATE_TYPE_PARM && !PLACEHOLDER_TYPE_CONSTRAINTS_INFO (t) - && !CLASS_PLACEHOLDER_TEMPLATE (t) && (arg = TEMPLATE_TYPE_PARM_INDEX (t), r = TEMPLATE_PARM_DESCENDANTS (arg)) && (TEMPLATE_PARM_LEVEL (r) @@ -15753,19 +15752,10 @@ tsubst (tree t, tree args, tsubst_flags_t complain, tree in_decl) TYPE_REFERENCE_TO (r) = NULL_TREE; if (TREE_CODE (t) == TEMPLATE_TYPE_PARM) - { + if (tree ci = PLACEHOLDER_TYPE_CONSTRAINTS_INFO (t)) /* Propagate constraints on placeholders since they are only instantiated during satisfaction. */ - if (tree ci = PLACEHOLDER_TYPE_CONSTRAINTS_INFO (t)) - PLACEHOLDER_TYPE_CONSTRAINTS_INFO (r) = ci; - else if (tree pl = CLASS_PLACEHOLDER_TEMPLATE (t)) - { - pl = tsubst_copy (pl, args, complain, in_decl); - if (TREE_CODE (pl) == TEMPLATE_TEMPLATE_PARM) - pl = TEMPLATE_TEMPLATE_PARM_TEMPLATE_DECL (pl); - CLASS_PLACEHOLDER_TEMPLATE (r) = pl; - } - } + PLACEHOLDER_TYPE_CONSTRAINTS_INFO (r) = ci; if (TREE_CODE (r) == TEMPLATE_TEMPLATE_PARM) /* We have reduced the level of the template @@ -28491,18 +28481,18 @@ make_args_non_dependent (vec<tree, va_gc> *args) } /* Returns a type which represents 'auto' or 'decltype(auto)'. We use a - TEMPLATE_TYPE_PARM with a level one deeper than the actual template - parms. If set_canonical is true, we set TYPE_CANONICAL on it. */ + TEMPLATE_TYPE_PARM with a level one deeper than the actual template parms, + by default. If set_canonical is true, we set TYPE_CANONICAL on it. */ static tree -make_auto_1 (tree name, bool set_canonical) +make_auto_1 (tree name, bool set_canonical, + int level = current_template_depth + 1) { tree au = cxx_make_type (TEMPLATE_TYPE_PARM); TYPE_NAME (au) = build_decl (input_location, TYPE_DECL, name, au); TYPE_STUB_DECL (au) = TYPE_NAME (au); TEMPLATE_TYPE_PARM_INDEX (au) = build_template_parm_index - (0, current_template_depth + 1, current_template_depth + 1, - TYPE_NAME (au), NULL_TREE); + (0, level, level, TYPE_NAME (au), NULL_TREE); if (set_canonical) TYPE_CANONICAL (au) = canonical_type_parameter (au); DECL_ARTIFICIAL (TYPE_NAME (au)) = 1; @@ -28525,12 +28515,14 @@ make_auto (void) return make_auto_1 (auto_identifier, true); } -/* Return a C++17 deduction placeholder for class template TMPL. */ +/* Return a C++17 deduction placeholder for class template TMPL. + There are represented as an 'auto' with the special level 0 and + CLASS_PLACEHOLDER_TEMPLATE set. */ tree make_template_placeholder (tree tmpl) { - tree t = make_auto_1 (auto_identifier, false); + tree t = make_auto_1 (auto_identifier, false, /*level=*/0); CLASS_PLACEHOLDER_TEMPLATE (t) = tmpl; /* Our canonical type depends on the placeholder. */ TYPE_CANONICAL (t) = canonical_type_parameter (t); diff --git a/gcc/testsuite/g++.dg/cpp1z/class-deduction100.C b/gcc/testsuite/g++.dg/cpp1z/class-deduction100.C new file mode 100644 index 00000000000..379eb960da6 --- /dev/null +++ b/gcc/testsuite/g++.dg/cpp1z/class-deduction100.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-deduction100a.C b/gcc/testsuite/g++.dg/cpp1z/class-deduction100a.C new file mode 100644 index 00000000000..958ac4f9762 --- /dev/null +++ b/gcc/testsuite/g++.dg/cpp1z/class-deduction100a.C @@ -0,0 +1,22 @@ +// PR c++/91911 +// { dg-do compile { target c++17 } } +// A variant of class-deduction100.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-deduction100b.C b/gcc/testsuite/g++.dg/cpp1z/class-deduction100b.C new file mode 100644 index 00000000000..9b5c684449c --- /dev/null +++ b/gcc/testsuite/g++.dg/cpp1z/class-deduction100b.C @@ -0,0 +1,22 @@ +// PR c++/91911 +// { dg-do compile { target c++17 } } +// A variant of class-deduction100.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-deduction101.C b/gcc/testsuite/g++.dg/cpp1z/class-deduction101.C new file mode 100644 index 00000000000..20504780d32 --- /dev/null +++ b/gcc/testsuite/g++.dg/cpp1z/class-deduction101.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-deduction101a.C b/gcc/testsuite/g++.dg/cpp1z/class-deduction101a.C new file mode 100644 index 00000000000..77b05d23b62 --- /dev/null +++ b/gcc/testsuite/g++.dg/cpp1z/class-deduction101a.C @@ -0,0 +1,27 @@ +// PR c++/98077 +// { dg-do compile { target c++17 } } +// A variant of class-deduction101.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; diff --git a/gcc/testsuite/g++.dg/cpp1z/class-deduction101b.C b/gcc/testsuite/g++.dg/cpp1z/class-deduction101b.C new file mode 100644 index 00000000000..50df934796d --- /dev/null +++ b/gcc/testsuite/g++.dg/cpp1z/class-deduction101b.C @@ -0,0 +1,30 @@ +// PR c++/98077 +// { dg-do compile { target c++17 } } +// A variant of class-deduction101.C where the template placeholder is a template +// template parameter and ReturnType has more levels than CallableTraitT. + +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> +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; diff --git a/gcc/testsuite/g++.dg/other/pr88187.C b/gcc/testsuite/g++.dg/other/pr88187.C index 13466d3ce57..c33644d14ca 100644 --- a/gcc/testsuite/g++.dg/other/pr88187.C +++ b/gcc/testsuite/g++.dg/other/pr88187.C @@ -4,4 +4,4 @@ template <int> struct A; void f (A ()); // { dg-error "6:variable or field 'f' declared void" "" { target c++14_down } } // { dg-error "missing template arguments before '\\(' token" "" { target c++14_down } .-1 } - // { dg-error "placeholder .A. not permitted in this context" "" { target c++17 } .-2 } + // { dg-error "deduced class type .A. in function return type" "" { target c++17 } .-2 }
On Mon, 3 Jan 2022, Patrick Palka wrote: > On Wed, 22 Dec 2021, Jason Merrill wrote: > > > On 12/21/21 14:08, Patrick Palka wrote: > > > On Tue, Dec 21, 2021 at 2:03 PM Patrick Palka <ppalka@redhat.com> wrote: > > > > > > > > On Wed, Jun 30, 2021 at 4:23 PM Jason Merrill <jason@redhat.com> wrote: > > > > > > > > > > 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? > > > > > > > > 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 nonpositive level. > > > > > - 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' > > > + in its type-id which might correspond to CTAD placeholders. */ > > > + t = tsubst (TREE_TYPE (gen_tmpl), arglist, > > > + complain | (is_dependent_type * tf_partial), > > > + in_decl); > > > > So, if we aren't changing any containing template scopes from dependent to > > non-dependent, we don't want to mess with levels. > > > > I think is_dependent_type is a bit too broad here; I'd expect this could cause > > trouble when e.g. instantiating a class A<int> with a member template B and we > > have both B<U> and an auto in the signature of a member template. I think > > what we want to check is whether the outermost args are dependent. > > Ah yeah, I see what you mean... > > > > > It would also be safer to handle adding tf_partial for alias templates in > > instantiate_alias_template rather than lookup_template_class. Perhaps in > > alias_ctad_tweaks as well. > > > > I tried adding an assert that tf_partial is set any time we see dependent > > outermost args; I expected to need to override that for deduction guide > > rewriting, but also seem to hit problems in concepts and TTP. Attached in > > case you're interested; I don't think this is going to become a patch suitable > > for GCC 12. The use of uses_template_parms_level was a kludge because > > dependent_template_arg_p returns true for null args. > > Interesting. In light of this general problem, I wonder if representing > autos as template parameters with one level greater than the template > depth, while convenient, is ultimately not the best approach? > > Back to the original issue concerning CTAD within alias templates, > here's an approach that seems to work well. The idea is to treat > CTAD placeholders less like template parameters, by giving them > the special level 0, and by making tsubst avoid substituting them > like template parameters. With this approach the representation > of a CTAD placeholder no longer depends on the template depth, so > alias template transparency for alias templates that contain CTAD > just works. > > I tried extending this approach to all autos (i.e. giving all > autos a level of 0 and adjusting tsubst accordingly), and it nearly > works modulo handling concepts TS auto... deduction: > > tuple<auto...> x = tuple<int, char>{}. > > since unify_pack_expansion isn't prepared to see a parameter pack of > level 0. This is likely fixable, but I suppose it'd be good to first > get confirmation that this is a good approach before continuing down > this patch. > > Below the patch that implements this approach only for CTAD > placeholders. Attached is an incremental WIP diff that additionally > extends the approach to all autos, which passes testing modulo the > concept TS thing. I should note one other complication with extending this approach to all autos is the handling of the constraint on a constrained auto such as template<class T> void g() { C<T*> auto x = ...; } We represent such a constraint as a concept-id whose first argument is the auto itself, e.g. C<auto, T*> for the above example, and we check satisfaction by passing the deduced type as an additional innermost template argument level, and this works precisely because the level of the auto is one greater than the template depth. So by giving autos a level of 0, we also have to somehow change the representation of the concept-id or change the way we check satisfaction of a constrained auto. One idea is to surgically replace the first argument of the concept-id with the deduced type before checking satisfaction. But this approach is not quite equivalent: it may sometimes lead to unexpected hard errors during normalization, e.g. in g++.dg/cpp2a/concepts-return-req1.C (there we'd complain about the substitution of T::type with T=int during normalization). So it's important that the first argument remains dependent before checking satisfaction. Another idea is to surgically replace the first argument of the concept-id with 'decltype(dependent-init)'. This seems like it'd work, but we currently don't have the dependent initializer readily available from do_auto_deduction. The approach the incremental patch takes here is to give the first argument of the concept-id a different, dummy auto with level 1 + template_depth while giving the overall constrained auto level 0. So it's not quite true that all autos have level 0 after the patch -- the one exception is the implicit first argument of a concept-id that represents the constraint on a constrained auto. Hmm, perhaps it might be better to not call this dummy parm an auto at all, and instead just treat it as an anonymous template parm (with level 1 + template_depth), so that we can truly say "all autos have level 0"... > > -- >8 -- > > Subject: [PATCH] c++: CTAD inside 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 its instantiated definition. But this > instantiation 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. > > Although we represent CTAD placeholders as template parameters, we never > actually want to replace them via tsubst. So this patch adjusts tsubst > to handle CTAD placeholders by simply substituting the template and > returning a new CTAD placeholder. Moreover, this means that the level > of a CTAD placeholder doesn't matter, so we may as well give them all > the same level. This patch gives them the special level 0. > > The change in grokdeclarator makes us reject an invalid function > return type consisting of a CTAD placeholder sooner (as in pr88187.C), > which helps guarantee that splice_late_return_type doesn't see or need > to handle a erroneous CTAD placeholder return type. > > The change in tsubst_decl removes a CHECKING_P workaround added in 2017, > which would otherwise now get triggered for variables with CTAD placeholder > types (since their level is 0). Alternatively, we could just guard the > workaround with !template_placeholder_p if that's preferable. > > Bootstrapped and regtested on x86_64-pc-linux-gnu, does this look OK for > trunk? > > PR c++/91911 > > gcc/cp/ChangeLog: > > * decl.c (grokdeclarator): Diagnose CTAD placeholder in function > return type even when !funcdecl_p. > * pt.c (keep_template_parm): Punt on a level 0 template parm. > (tsubst_decl) <case VAR_DECL>: Remove CHECKING_P workaround. > (tsubst) <case TEMPLATE_TYPE_PARM>: Handle CTAD placeholders > specially. > (make_auto_1): Add defaulted 'level' parameter. > (make_template_placeholder): Pass 0 as 'level' to make_auto_1. > > gcc/testsuite/ChangeLog: > > * g++.dg/cpp1z/class-deduction100.C: New test. > * g++.dg/cpp1z/class-deduction100a.C: New test. > * g++.dg/cpp1z/class-deduction100b.C: New test. > * g++.dg/cpp1z/class-deduction101.C: New test. > * g++.dg/cpp1z/class-deduction101a.C: New test. > * g++.dg/cpp1z/class-deduction101b.C: New test. > --- > gcc/cp/decl.c | 6 +- > gcc/cp/pt.c | 60 ++++++++----------- > .../g++.dg/cpp1z/class-deduction100.C | 17 ++++++ > .../g++.dg/cpp1z/class-deduction100a.C | 22 +++++++ > .../g++.dg/cpp1z/class-deduction100b.C | 22 +++++++ > .../g++.dg/cpp1z/class-deduction101.C | 25 ++++++++ > .../g++.dg/cpp1z/class-deduction101a.C | 27 +++++++++ > .../g++.dg/cpp1z/class-deduction101b.C | 30 ++++++++++ > gcc/testsuite/g++.dg/other/pr88187.C | 2 +- > 9 files changed, 173 insertions(+), 38 deletions(-) > create mode 100644 gcc/testsuite/g++.dg/cpp1z/class-deduction100.C > create mode 100644 gcc/testsuite/g++.dg/cpp1z/class-deduction100a.C > create mode 100644 gcc/testsuite/g++.dg/cpp1z/class-deduction100b.C > create mode 100644 gcc/testsuite/g++.dg/cpp1z/class-deduction101.C > create mode 100644 gcc/testsuite/g++.dg/cpp1z/class-deduction101a.C > create mode 100644 gcc/testsuite/g++.dg/cpp1z/class-deduction101b.C > > diff --git a/gcc/cp/decl.c b/gcc/cp/decl.c > index 0b71c00f5ab..406a9163ffd 100644 > --- a/gcc/cp/decl.c > +++ b/gcc/cp/decl.c > @@ -12635,11 +12635,11 @@ grokdeclarator (const cp_declarator *declarator, > if (!tmpl) > if (tree late_auto = type_uses_auto (late_return_type)) > tmpl = CLASS_PLACEHOLDER_TEMPLATE (late_auto); > - if (tmpl && funcdecl_p) > + if (tmpl) > { > - if (!dguide_name_p (unqualified_id)) > + if (!funcdecl_p || !dguide_name_p (unqualified_id)) > { > - error_at (declarator->id_loc, "deduced class " > + error_at (typespec_loc, "deduced class " > "type %qD in function return type", > DECL_NAME (tmpl)); > inform (DECL_SOURCE_LOCATION (tmpl), > diff --git a/gcc/cp/pt.c b/gcc/cp/pt.c > index c587966adbe..08e34c7404a 100644 > --- a/gcc/cp/pt.c > +++ b/gcc/cp/pt.c > @@ -10660,7 +10660,7 @@ keep_template_parm (tree t, void* data) > int level; > int index; > template_parm_level_and_index (t, &level, &index); > - if (level > ftpi->max_depth) > + if (level == 0 || level > ftpi->max_depth) > return 0; > > if (TREE_CODE (t) == BOUND_TEMPLATE_TEMPLATE_PARM) > @@ -14796,20 +14796,7 @@ tsubst_decl (tree t, tree args, tsubst_flags_t complain) > && VAR_HAD_UNKNOWN_BOUND (t) > && type != error_mark_node) > type = strip_array_domain (type); > - tree sub_args = args; > - if (tree auto_node = type_uses_auto (type)) > - { > - /* Mask off any template args past the variable's context so we > - don't replace the auto with an unrelated argument. */ > - int nouter = TEMPLATE_TYPE_LEVEL (auto_node) - 1; > - int extra = TMPL_ARGS_DEPTH (args) - nouter; > - if (extra > 0) > - /* This should never happen with the new lambda instantiation > - model, but keep the handling just in case. */ > - gcc_assert (!CHECKING_P), > - sub_args = strip_innermost_template_args (args, extra); > - } > - type = tsubst (type, sub_args, complain, in_decl); > + type = tsubst (type, args, complain, in_decl); > /* Substituting the type might have recursively instantiated this > same alias (c++/86171). */ > if (gen_tmpl && DECL_ALIAS_TEMPLATE_P (gen_tmpl) > @@ -15561,6 +15548,19 @@ tsubst (tree t, tree args, tsubst_flags_t complain, tree in_decl) > } > > case TEMPLATE_TYPE_PARM: > + if (template_placeholder_p (t)) > + { > + tree tmpl = CLASS_PLACEHOLDER_TEMPLATE (t); > + tmpl = tsubst_copy (tmpl, args, complain, in_decl); > + if (TREE_CODE (tmpl) == TEMPLATE_TEMPLATE_PARM) > + tmpl = TEMPLATE_TEMPLATE_PARM_TEMPLATE_DECL (tmpl); > + > + if (tmpl != CLASS_PLACEHOLDER_TEMPLATE (t)) > + return make_template_placeholder (tmpl); > + else > + return t; > + } > + /* Fall through. */ > case TEMPLATE_TEMPLATE_PARM: > case BOUND_TEMPLATE_TEMPLATE_PARM: > case TEMPLATE_PARM_INDEX: > @@ -15734,7 +15734,6 @@ tsubst (tree t, tree args, tsubst_flags_t complain, tree in_decl) > of a constrained placeholder. */; > else if (TREE_CODE (t) == TEMPLATE_TYPE_PARM > && !PLACEHOLDER_TYPE_CONSTRAINTS_INFO (t) > - && !CLASS_PLACEHOLDER_TEMPLATE (t) > && (arg = TEMPLATE_TYPE_PARM_INDEX (t), > r = TEMPLATE_PARM_DESCENDANTS (arg)) > && (TEMPLATE_PARM_LEVEL (r) > @@ -15753,19 +15752,10 @@ tsubst (tree t, tree args, tsubst_flags_t complain, tree in_decl) > TYPE_REFERENCE_TO (r) = NULL_TREE; > > if (TREE_CODE (t) == TEMPLATE_TYPE_PARM) > - { > + if (tree ci = PLACEHOLDER_TYPE_CONSTRAINTS_INFO (t)) > /* Propagate constraints on placeholders since they are > only instantiated during satisfaction. */ > - if (tree ci = PLACEHOLDER_TYPE_CONSTRAINTS_INFO (t)) > - PLACEHOLDER_TYPE_CONSTRAINTS_INFO (r) = ci; > - else if (tree pl = CLASS_PLACEHOLDER_TEMPLATE (t)) > - { > - pl = tsubst_copy (pl, args, complain, in_decl); > - if (TREE_CODE (pl) == TEMPLATE_TEMPLATE_PARM) > - pl = TEMPLATE_TEMPLATE_PARM_TEMPLATE_DECL (pl); > - CLASS_PLACEHOLDER_TEMPLATE (r) = pl; > - } > - } > + PLACEHOLDER_TYPE_CONSTRAINTS_INFO (r) = ci; > > if (TREE_CODE (r) == TEMPLATE_TEMPLATE_PARM) > /* We have reduced the level of the template > @@ -28491,18 +28481,18 @@ make_args_non_dependent (vec<tree, va_gc> *args) > } > > /* Returns a type which represents 'auto' or 'decltype(auto)'. We use a > - TEMPLATE_TYPE_PARM with a level one deeper than the actual template > - parms. If set_canonical is true, we set TYPE_CANONICAL on it. */ > + TEMPLATE_TYPE_PARM with a level one deeper than the actual template parms, > + by default. If set_canonical is true, we set TYPE_CANONICAL on it. */ > > static tree > -make_auto_1 (tree name, bool set_canonical) > +make_auto_1 (tree name, bool set_canonical, > + int level = current_template_depth + 1) > { > tree au = cxx_make_type (TEMPLATE_TYPE_PARM); > TYPE_NAME (au) = build_decl (input_location, TYPE_DECL, name, au); > TYPE_STUB_DECL (au) = TYPE_NAME (au); > TEMPLATE_TYPE_PARM_INDEX (au) = build_template_parm_index > - (0, current_template_depth + 1, current_template_depth + 1, > - TYPE_NAME (au), NULL_TREE); > + (0, level, level, TYPE_NAME (au), NULL_TREE); > if (set_canonical) > TYPE_CANONICAL (au) = canonical_type_parameter (au); > DECL_ARTIFICIAL (TYPE_NAME (au)) = 1; > @@ -28525,12 +28515,14 @@ make_auto (void) > return make_auto_1 (auto_identifier, true); > } > > -/* Return a C++17 deduction placeholder for class template TMPL. */ > +/* Return a C++17 deduction placeholder for class template TMPL. > + There are represented as an 'auto' with the special level 0 and > + CLASS_PLACEHOLDER_TEMPLATE set. */ > > tree > make_template_placeholder (tree tmpl) > { > - tree t = make_auto_1 (auto_identifier, false); > + tree t = make_auto_1 (auto_identifier, false, /*level=*/0); > CLASS_PLACEHOLDER_TEMPLATE (t) = tmpl; > /* Our canonical type depends on the placeholder. */ > TYPE_CANONICAL (t) = canonical_type_parameter (t); > diff --git a/gcc/testsuite/g++.dg/cpp1z/class-deduction100.C b/gcc/testsuite/g++.dg/cpp1z/class-deduction100.C > new file mode 100644 > index 00000000000..379eb960da6 > --- /dev/null > +++ b/gcc/testsuite/g++.dg/cpp1z/class-deduction100.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-deduction100a.C b/gcc/testsuite/g++.dg/cpp1z/class-deduction100a.C > new file mode 100644 > index 00000000000..958ac4f9762 > --- /dev/null > +++ b/gcc/testsuite/g++.dg/cpp1z/class-deduction100a.C > @@ -0,0 +1,22 @@ > +// PR c++/91911 > +// { dg-do compile { target c++17 } } > +// A variant of class-deduction100.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-deduction100b.C b/gcc/testsuite/g++.dg/cpp1z/class-deduction100b.C > new file mode 100644 > index 00000000000..9b5c684449c > --- /dev/null > +++ b/gcc/testsuite/g++.dg/cpp1z/class-deduction100b.C > @@ -0,0 +1,22 @@ > +// PR c++/91911 > +// { dg-do compile { target c++17 } } > +// A variant of class-deduction100.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-deduction101.C b/gcc/testsuite/g++.dg/cpp1z/class-deduction101.C > new file mode 100644 > index 00000000000..20504780d32 > --- /dev/null > +++ b/gcc/testsuite/g++.dg/cpp1z/class-deduction101.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-deduction101a.C b/gcc/testsuite/g++.dg/cpp1z/class-deduction101a.C > new file mode 100644 > index 00000000000..77b05d23b62 > --- /dev/null > +++ b/gcc/testsuite/g++.dg/cpp1z/class-deduction101a.C > @@ -0,0 +1,27 @@ > +// PR c++/98077 > +// { dg-do compile { target c++17 } } > +// A variant of class-deduction101.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; > diff --git a/gcc/testsuite/g++.dg/cpp1z/class-deduction101b.C b/gcc/testsuite/g++.dg/cpp1z/class-deduction101b.C > new file mode 100644 > index 00000000000..50df934796d > --- /dev/null > +++ b/gcc/testsuite/g++.dg/cpp1z/class-deduction101b.C > @@ -0,0 +1,30 @@ > +// PR c++/98077 > +// { dg-do compile { target c++17 } } > +// A variant of class-deduction101.C where the template placeholder is a template > +// template parameter and ReturnType has more levels than CallableTraitT. > + > +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> > +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; > diff --git a/gcc/testsuite/g++.dg/other/pr88187.C b/gcc/testsuite/g++.dg/other/pr88187.C > index 13466d3ce57..c33644d14ca 100644 > --- a/gcc/testsuite/g++.dg/other/pr88187.C > +++ b/gcc/testsuite/g++.dg/other/pr88187.C > @@ -4,4 +4,4 @@ > template <int> struct A; > void f (A ()); // { dg-error "6:variable or field 'f' declared void" "" { target c++14_down } } > // { dg-error "missing template arguments before '\\(' token" "" { target c++14_down } .-1 } > - // { dg-error "placeholder .A. not permitted in this context" "" { target c++17 } .-2 } > + // { dg-error "deduced class type .A. in function return type" "" { target c++17 } .-2 } > -- > 2.34.1.390.g2ae0a9cb82 >
On Mon, Jan 3, 2022 at 10:24 AM Patrick Palka <ppalka@redhat.com> wrote: > > On Wed, 22 Dec 2021, Jason Merrill wrote: > > > On 12/21/21 14:08, Patrick Palka wrote: > > > On Tue, Dec 21, 2021 at 2:03 PM Patrick Palka <ppalka@redhat.com> wrote: > > > > > > > > On Wed, Jun 30, 2021 at 4:23 PM Jason Merrill <jason@redhat.com> wrote: > > > > > > > > > > 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? > > > > > > > > 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 nonpositive level. > > > > > - 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' > > > + in its type-id which might correspond to CTAD placeholders. */ > > > + t = tsubst (TREE_TYPE (gen_tmpl), arglist, > > > + complain | (is_dependent_type * tf_partial), > > > + in_decl); > > > > So, if we aren't changing any containing template scopes from dependent to > > non-dependent, we don't want to mess with levels. > > > > I think is_dependent_type is a bit too broad here; I'd expect this could cause > > trouble when e.g. instantiating a class A<int> with a member template B and we > > have both B<U> and an auto in the signature of a member template. I think > > what we want to check is whether the outermost args are dependent. > > Ah yeah, I see what you mean... > > > > > It would also be safer to handle adding tf_partial for alias templates in > > instantiate_alias_template rather than lookup_template_class. Perhaps in > > alias_ctad_tweaks as well. > > > > I tried adding an assert that tf_partial is set any time we see dependent > > outermost args; I expected to need to override that for deduction guide > > rewriting, but also seem to hit problems in concepts and TTP. Attached in > > case you're interested; I don't think this is going to become a patch suitable > > for GCC 12. The use of uses_template_parms_level was a kludge because > > dependent_template_arg_p returns true for null args. > > Interesting. In light of this general problem, I wonder if representing > autos as template parameters with one level greater than the template > depth, while convenient, is ultimately not the best approach? > > Back to the original issue concerning CTAD within alias templates, > here's an approach that seems to work well. The idea is to treat > CTAD placeholders less like template parameters, by giving them > the special level 0, and by making tsubst avoid substituting them > like template parameters. With this approach the representation > of a CTAD placeholder no longer depends on the template depth, so > alias template transparency for alias templates that contain CTAD > just works. > > I tried extending this approach to all autos (i.e. giving all > autos a level of 0 and adjusting tsubst accordingly), and it nearly > works modulo handling concepts TS auto... deduction: > > tuple<auto...> x = tuple<int, char>{}. > > since unify_pack_expansion isn't prepared to see a parameter pack of > level 0. This is likely fixable, but I suppose it'd be good to first > get confirmation that this is a good approach before continuing down > this patch. > > Below the patch that implements this approach only for CTAD > placeholders. Attached is an incremental WIP diff that additionally > extends the approach to all autos, which passes testing modulo the > concept TS thing. Ping. It's probably too risky to commit to this approach for all autos for GCC 12 at this point, but perhaps the CTAD-only patch (inlined below) might be worth considering for GCC 12? Seems to fix the regression PR103672 as well (though I think we just got lucky that we accepted this testcase previously). > > -- >8 -- > > Subject: [PATCH] c++: CTAD inside 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 its instantiated definition. But this > instantiation 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. > > Although we represent CTAD placeholders as template parameters, we never > actually want to replace them via tsubst. So this patch adjusts tsubst > to handle CTAD placeholders by simply substituting the template and > returning a new CTAD placeholder. Moreover, this means that the level > of a CTAD placeholder doesn't matter, so we may as well give them all > the same level. This patch gives them the special level 0. > > The change in grokdeclarator makes us reject an invalid function > return type consisting of a CTAD placeholder sooner (as in pr88187.C), > which helps guarantee that splice_late_return_type doesn't see or need > to handle a erroneous CTAD placeholder return type. > > The change in tsubst_decl removes a CHECKING_P workaround added in 2017, > which would otherwise now get triggered for variables with CTAD placeholder > types (since their level is 0). Alternatively, we could just guard the > workaround with !template_placeholder_p if that's preferable. > > Bootstrapped and regtested on x86_64-pc-linux-gnu, does this look OK for > trunk? > > PR c++/91911 > > gcc/cp/ChangeLog: > > * decl.c (grokdeclarator): Diagnose CTAD placeholder in function > return type even when !funcdecl_p. > * pt.c (keep_template_parm): Punt on a level 0 template parm. > (tsubst_decl) <case VAR_DECL>: Remove CHECKING_P workaround. > (tsubst) <case TEMPLATE_TYPE_PARM>: Handle CTAD placeholders > specially. > (make_auto_1): Add defaulted 'level' parameter. > (make_template_placeholder): Pass 0 as 'level' to make_auto_1. > > gcc/testsuite/ChangeLog: > > * g++.dg/cpp1z/class-deduction100.C: New test. > * g++.dg/cpp1z/class-deduction100a.C: New test. > * g++.dg/cpp1z/class-deduction100b.C: New test. > * g++.dg/cpp1z/class-deduction101.C: New test. > * g++.dg/cpp1z/class-deduction101a.C: New test. > * g++.dg/cpp1z/class-deduction101b.C: New test. > --- > gcc/cp/decl.c | 6 +- > gcc/cp/pt.c | 60 ++++++++----------- > .../g++.dg/cpp1z/class-deduction100.C | 17 ++++++ > .../g++.dg/cpp1z/class-deduction100a.C | 22 +++++++ > .../g++.dg/cpp1z/class-deduction100b.C | 22 +++++++ > .../g++.dg/cpp1z/class-deduction101.C | 25 ++++++++ > .../g++.dg/cpp1z/class-deduction101a.C | 27 +++++++++ > .../g++.dg/cpp1z/class-deduction101b.C | 30 ++++++++++ > gcc/testsuite/g++.dg/other/pr88187.C | 2 +- > 9 files changed, 173 insertions(+), 38 deletions(-) > create mode 100644 gcc/testsuite/g++.dg/cpp1z/class-deduction100.C > create mode 100644 gcc/testsuite/g++.dg/cpp1z/class-deduction100a.C > create mode 100644 gcc/testsuite/g++.dg/cpp1z/class-deduction100b.C > create mode 100644 gcc/testsuite/g++.dg/cpp1z/class-deduction101.C > create mode 100644 gcc/testsuite/g++.dg/cpp1z/class-deduction101a.C > create mode 100644 gcc/testsuite/g++.dg/cpp1z/class-deduction101b.C > > diff --git a/gcc/cp/decl.c b/gcc/cp/decl.c > index 0b71c00f5ab..406a9163ffd 100644 > --- a/gcc/cp/decl.c > +++ b/gcc/cp/decl.c > @@ -12635,11 +12635,11 @@ grokdeclarator (const cp_declarator *declarator, > if (!tmpl) > if (tree late_auto = type_uses_auto (late_return_type)) > tmpl = CLASS_PLACEHOLDER_TEMPLATE (late_auto); > - if (tmpl && funcdecl_p) > + if (tmpl) > { > - if (!dguide_name_p (unqualified_id)) > + if (!funcdecl_p || !dguide_name_p (unqualified_id)) > { > - error_at (declarator->id_loc, "deduced class " > + error_at (typespec_loc, "deduced class " > "type %qD in function return type", > DECL_NAME (tmpl)); > inform (DECL_SOURCE_LOCATION (tmpl), > diff --git a/gcc/cp/pt.c b/gcc/cp/pt.c > index c587966adbe..08e34c7404a 100644 > --- a/gcc/cp/pt.c > +++ b/gcc/cp/pt.c > @@ -10660,7 +10660,7 @@ keep_template_parm (tree t, void* data) > int level; > int index; > template_parm_level_and_index (t, &level, &index); > - if (level > ftpi->max_depth) > + if (level == 0 || level > ftpi->max_depth) > return 0; > > if (TREE_CODE (t) == BOUND_TEMPLATE_TEMPLATE_PARM) > @@ -14796,20 +14796,7 @@ tsubst_decl (tree t, tree args, tsubst_flags_t complain) > && VAR_HAD_UNKNOWN_BOUND (t) > && type != error_mark_node) > type = strip_array_domain (type); > - tree sub_args = args; > - if (tree auto_node = type_uses_auto (type)) > - { > - /* Mask off any template args past the variable's context so we > - don't replace the auto with an unrelated argument. */ > - int nouter = TEMPLATE_TYPE_LEVEL (auto_node) - 1; > - int extra = TMPL_ARGS_DEPTH (args) - nouter; > - if (extra > 0) > - /* This should never happen with the new lambda instantiation > - model, but keep the handling just in case. */ > - gcc_assert (!CHECKING_P), > - sub_args = strip_innermost_template_args (args, extra); > - } > - type = tsubst (type, sub_args, complain, in_decl); > + type = tsubst (type, args, complain, in_decl); > /* Substituting the type might have recursively instantiated this > same alias (c++/86171). */ > if (gen_tmpl && DECL_ALIAS_TEMPLATE_P (gen_tmpl) > @@ -15561,6 +15548,19 @@ tsubst (tree t, tree args, tsubst_flags_t complain, tree in_decl) > } > > case TEMPLATE_TYPE_PARM: > + if (template_placeholder_p (t)) > + { > + tree tmpl = CLASS_PLACEHOLDER_TEMPLATE (t); > + tmpl = tsubst_copy (tmpl, args, complain, in_decl); > + if (TREE_CODE (tmpl) == TEMPLATE_TEMPLATE_PARM) > + tmpl = TEMPLATE_TEMPLATE_PARM_TEMPLATE_DECL (tmpl); > + > + if (tmpl != CLASS_PLACEHOLDER_TEMPLATE (t)) > + return make_template_placeholder (tmpl); > + else > + return t; > + } > + /* Fall through. */ > case TEMPLATE_TEMPLATE_PARM: > case BOUND_TEMPLATE_TEMPLATE_PARM: > case TEMPLATE_PARM_INDEX: > @@ -15734,7 +15734,6 @@ tsubst (tree t, tree args, tsubst_flags_t complain, tree in_decl) > of a constrained placeholder. */; > else if (TREE_CODE (t) == TEMPLATE_TYPE_PARM > && !PLACEHOLDER_TYPE_CONSTRAINTS_INFO (t) > - && !CLASS_PLACEHOLDER_TEMPLATE (t) > && (arg = TEMPLATE_TYPE_PARM_INDEX (t), > r = TEMPLATE_PARM_DESCENDANTS (arg)) > && (TEMPLATE_PARM_LEVEL (r) > @@ -15753,19 +15752,10 @@ tsubst (tree t, tree args, tsubst_flags_t complain, tree in_decl) > TYPE_REFERENCE_TO (r) = NULL_TREE; > > if (TREE_CODE (t) == TEMPLATE_TYPE_PARM) > - { > + if (tree ci = PLACEHOLDER_TYPE_CONSTRAINTS_INFO (t)) > /* Propagate constraints on placeholders since they are > only instantiated during satisfaction. */ > - if (tree ci = PLACEHOLDER_TYPE_CONSTRAINTS_INFO (t)) > - PLACEHOLDER_TYPE_CONSTRAINTS_INFO (r) = ci; > - else if (tree pl = CLASS_PLACEHOLDER_TEMPLATE (t)) > - { > - pl = tsubst_copy (pl, args, complain, in_decl); > - if (TREE_CODE (pl) == TEMPLATE_TEMPLATE_PARM) > - pl = TEMPLATE_TEMPLATE_PARM_TEMPLATE_DECL (pl); > - CLASS_PLACEHOLDER_TEMPLATE (r) = pl; > - } > - } > + PLACEHOLDER_TYPE_CONSTRAINTS_INFO (r) = ci; > > if (TREE_CODE (r) == TEMPLATE_TEMPLATE_PARM) > /* We have reduced the level of the template > @@ -28491,18 +28481,18 @@ make_args_non_dependent (vec<tree, va_gc> *args) > } > > /* Returns a type which represents 'auto' or 'decltype(auto)'. We use a > - TEMPLATE_TYPE_PARM with a level one deeper than the actual template > - parms. If set_canonical is true, we set TYPE_CANONICAL on it. */ > + TEMPLATE_TYPE_PARM with a level one deeper than the actual template parms, > + by default. If set_canonical is true, we set TYPE_CANONICAL on it. */ > > static tree > -make_auto_1 (tree name, bool set_canonical) > +make_auto_1 (tree name, bool set_canonical, > + int level = current_template_depth + 1) > { > tree au = cxx_make_type (TEMPLATE_TYPE_PARM); > TYPE_NAME (au) = build_decl (input_location, TYPE_DECL, name, au); > TYPE_STUB_DECL (au) = TYPE_NAME (au); > TEMPLATE_TYPE_PARM_INDEX (au) = build_template_parm_index > - (0, current_template_depth + 1, current_template_depth + 1, > - TYPE_NAME (au), NULL_TREE); > + (0, level, level, TYPE_NAME (au), NULL_TREE); > if (set_canonical) > TYPE_CANONICAL (au) = canonical_type_parameter (au); > DECL_ARTIFICIAL (TYPE_NAME (au)) = 1; > @@ -28525,12 +28515,14 @@ make_auto (void) > return make_auto_1 (auto_identifier, true); > } > > -/* Return a C++17 deduction placeholder for class template TMPL. */ > +/* Return a C++17 deduction placeholder for class template TMPL. > + There are represented as an 'auto' with the special level 0 and > + CLASS_PLACEHOLDER_TEMPLATE set. */ > > tree > make_template_placeholder (tree tmpl) > { > - tree t = make_auto_1 (auto_identifier, false); > + tree t = make_auto_1 (auto_identifier, false, /*level=*/0); > CLASS_PLACEHOLDER_TEMPLATE (t) = tmpl; > /* Our canonical type depends on the placeholder. */ > TYPE_CANONICAL (t) = canonical_type_parameter (t); > diff --git a/gcc/testsuite/g++.dg/cpp1z/class-deduction100.C b/gcc/testsuite/g++.dg/cpp1z/class-deduction100.C > new file mode 100644 > index 00000000000..379eb960da6 > --- /dev/null > +++ b/gcc/testsuite/g++.dg/cpp1z/class-deduction100.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-deduction100a.C b/gcc/testsuite/g++.dg/cpp1z/class-deduction100a.C > new file mode 100644 > index 00000000000..958ac4f9762 > --- /dev/null > +++ b/gcc/testsuite/g++.dg/cpp1z/class-deduction100a.C > @@ -0,0 +1,22 @@ > +// PR c++/91911 > +// { dg-do compile { target c++17 } } > +// A variant of class-deduction100.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-deduction100b.C b/gcc/testsuite/g++.dg/cpp1z/class-deduction100b.C > new file mode 100644 > index 00000000000..9b5c684449c > --- /dev/null > +++ b/gcc/testsuite/g++.dg/cpp1z/class-deduction100b.C > @@ -0,0 +1,22 @@ > +// PR c++/91911 > +// { dg-do compile { target c++17 } } > +// A variant of class-deduction100.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-deduction101.C b/gcc/testsuite/g++.dg/cpp1z/class-deduction101.C > new file mode 100644 > index 00000000000..20504780d32 > --- /dev/null > +++ b/gcc/testsuite/g++.dg/cpp1z/class-deduction101.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-deduction101a.C b/gcc/testsuite/g++.dg/cpp1z/class-deduction101a.C > new file mode 100644 > index 00000000000..77b05d23b62 > --- /dev/null > +++ b/gcc/testsuite/g++.dg/cpp1z/class-deduction101a.C > @@ -0,0 +1,27 @@ > +// PR c++/98077 > +// { dg-do compile { target c++17 } } > +// A variant of class-deduction101.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; > diff --git a/gcc/testsuite/g++.dg/cpp1z/class-deduction101b.C b/gcc/testsuite/g++.dg/cpp1z/class-deduction101b.C > new file mode 100644 > index 00000000000..50df934796d > --- /dev/null > +++ b/gcc/testsuite/g++.dg/cpp1z/class-deduction101b.C > @@ -0,0 +1,30 @@ > +// PR c++/98077 > +// { dg-do compile { target c++17 } } > +// A variant of class-deduction101.C where the template placeholder is a template > +// template parameter and ReturnType has more levels than CallableTraitT. > + > +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> > +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; > diff --git a/gcc/testsuite/g++.dg/other/pr88187.C b/gcc/testsuite/g++.dg/other/pr88187.C > index 13466d3ce57..c33644d14ca 100644 > --- a/gcc/testsuite/g++.dg/other/pr88187.C > +++ b/gcc/testsuite/g++.dg/other/pr88187.C > @@ -4,4 +4,4 @@ > template <int> struct A; > void f (A ()); // { dg-error "6:variable or field 'f' declared void" "" { target c++14_down } } > // { dg-error "missing template arguments before '\\(' token" "" { target c++14_down } .-1 } > - // { dg-error "placeholder .A. not permitted in this context" "" { target c++17 } .-2 } > + // { dg-error "deduced class type .A. in function return type" "" { target c++17 } .-2 } > -- > 2.34.1.390.g2ae0a9cb82
On 1/3/22 10:24, Patrick Palka wrote: > On Wed, 22 Dec 2021, Jason Merrill wrote: > >> On 12/21/21 14:08, Patrick Palka wrote: >>> On Tue, Dec 21, 2021 at 2:03 PM Patrick Palka <ppalka@redhat.com> wrote: >>>> >>>> On Wed, Jun 30, 2021 at 4:23 PM Jason Merrill <jason@redhat.com> wrote: >>>>> >>>>> 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? >>>> >>>> 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 nonpositive level. >> >>> - 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' >>> + in its type-id which might correspond to CTAD placeholders. */ >>> + t = tsubst (TREE_TYPE (gen_tmpl), arglist, >>> + complain | (is_dependent_type * tf_partial), >>> + in_decl); >> >> So, if we aren't changing any containing template scopes from dependent to >> non-dependent, we don't want to mess with levels. >> >> I think is_dependent_type is a bit too broad here; I'd expect this could cause >> trouble when e.g. instantiating a class A<int> with a member template B and we >> have both B<U> and an auto in the signature of a member template. I think >> what we want to check is whether the outermost args are dependent. > > Ah yeah, I see what you mean... > >> >> It would also be safer to handle adding tf_partial for alias templates in >> instantiate_alias_template rather than lookup_template_class. Perhaps in >> alias_ctad_tweaks as well. >> >> I tried adding an assert that tf_partial is set any time we see dependent >> outermost args; I expected to need to override that for deduction guide >> rewriting, but also seem to hit problems in concepts and TTP. Attached in >> case you're interested; I don't think this is going to become a patch suitable >> for GCC 12. The use of uses_template_parms_level was a kludge because >> dependent_template_arg_p returns true for null args. > > Interesting. In light of this general problem, I wonder if representing > autos as template parameters with one level greater than the template > depth, while convenient, is ultimately not the best approach? > > Back to the original issue concerning CTAD within alias templates, > here's an approach that seems to work well. The idea is to treat > CTAD placeholders less like template parameters, by giving them > the special level 0, and by making tsubst avoid substituting them > like template parameters. With this approach the representation > of a CTAD placeholder no longer depends on the template depth, so > alias template transparency for alias templates that contain CTAD > just works. > > I tried extending this approach to all autos (i.e. giving all > autos a level of 0 and adjusting tsubst accordingly), and it nearly > works modulo handling concepts TS auto... deduction: > > tuple<auto...> x = tuple<int, char>{}. > > since unify_pack_expansion isn't prepared to see a parameter pack of > level 0. This is likely fixable, but I suppose it'd be good to first > get confirmation that this is a good approach before continuing down > this patch. > > Below the patch that implements this approach only for CTAD > placeholders. Attached is an incremental WIP diff that additionally > extends the approach to all autos, which passes testing modulo the > concept TS thing. > > -- >8 -- > > Subject: [PATCH] c++: CTAD inside 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 its instantiated definition. But this > instantiation 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. > > Although we represent CTAD placeholders as template parameters, we never > actually want to replace them via tsubst. So this patch adjusts tsubst > to handle CTAD placeholders by simply substituting the template and > returning a new CTAD placeholder. Moreover, this means that the level > of a CTAD placeholder doesn't matter, so we may as well give them all > the same level. This patch gives them the special level 0. > > The change in grokdeclarator makes us reject an invalid function > return type consisting of a CTAD placeholder sooner (as in pr88187.C), > which helps guarantee that splice_late_return_type doesn't see or need > to handle a erroneous CTAD placeholder return type. > > The change in tsubst_decl removes a CHECKING_P workaround added in 2017, > which would otherwise now get triggered for variables with CTAD placeholder > types (since their level is 0). Alternatively, we could just guard the > workaround with !template_placeholder_p if that's preferable. > > Bootstrapped and regtested on x86_64-pc-linux-gnu, does this look OK for > trunk? > > PR c++/91911 > > gcc/cp/ChangeLog: > > * decl.c (grokdeclarator): Diagnose CTAD placeholder in function > return type even when !funcdecl_p. > * pt.c (keep_template_parm): Punt on a level 0 template parm. > (tsubst_decl) <case VAR_DECL>: Remove CHECKING_P workaround. > (tsubst) <case TEMPLATE_TYPE_PARM>: Handle CTAD placeholders > specially. > (make_auto_1): Add defaulted 'level' parameter. > (make_template_placeholder): Pass 0 as 'level' to make_auto_1. > > gcc/testsuite/ChangeLog: > > * g++.dg/cpp1z/class-deduction100.C: New test. > * g++.dg/cpp1z/class-deduction100a.C: New test. > * g++.dg/cpp1z/class-deduction100b.C: New test. > * g++.dg/cpp1z/class-deduction101.C: New test. > * g++.dg/cpp1z/class-deduction101a.C: New test. > * g++.dg/cpp1z/class-deduction101b.C: New test. > --- > gcc/cp/decl.c | 6 +- > gcc/cp/pt.c | 60 ++++++++----------- > .../g++.dg/cpp1z/class-deduction100.C | 17 ++++++ > .../g++.dg/cpp1z/class-deduction100a.C | 22 +++++++ > .../g++.dg/cpp1z/class-deduction100b.C | 22 +++++++ > .../g++.dg/cpp1z/class-deduction101.C | 25 ++++++++ > .../g++.dg/cpp1z/class-deduction101a.C | 27 +++++++++ > .../g++.dg/cpp1z/class-deduction101b.C | 30 ++++++++++ > gcc/testsuite/g++.dg/other/pr88187.C | 2 +- > 9 files changed, 173 insertions(+), 38 deletions(-) > create mode 100644 gcc/testsuite/g++.dg/cpp1z/class-deduction100.C > create mode 100644 gcc/testsuite/g++.dg/cpp1z/class-deduction100a.C > create mode 100644 gcc/testsuite/g++.dg/cpp1z/class-deduction100b.C > create mode 100644 gcc/testsuite/g++.dg/cpp1z/class-deduction101.C > create mode 100644 gcc/testsuite/g++.dg/cpp1z/class-deduction101a.C > create mode 100644 gcc/testsuite/g++.dg/cpp1z/class-deduction101b.C > > diff --git a/gcc/cp/decl.c b/gcc/cp/decl.c > index 0b71c00f5ab..406a9163ffd 100644 > --- a/gcc/cp/decl.c > +++ b/gcc/cp/decl.c > @@ -12635,11 +12635,11 @@ grokdeclarator (const cp_declarator *declarator, > if (!tmpl) > if (tree late_auto = type_uses_auto (late_return_type)) > tmpl = CLASS_PLACEHOLDER_TEMPLATE (late_auto); > - if (tmpl && funcdecl_p) > + if (tmpl) > { > - if (!dguide_name_p (unqualified_id)) > + if (!funcdecl_p || !dguide_name_p (unqualified_id)) > { > - error_at (declarator->id_loc, "deduced class " > + error_at (typespec_loc, "deduced class " > "type %qD in function return type", > DECL_NAME (tmpl)); > inform (DECL_SOURCE_LOCATION (tmpl), This seems like it could go in separately. > diff --git a/gcc/cp/pt.c b/gcc/cp/pt.c > index c587966adbe..08e34c7404a 100644 > --- a/gcc/cp/pt.c > +++ b/gcc/cp/pt.c > @@ -10660,7 +10660,7 @@ keep_template_parm (tree t, void* data) > int level; > int index; > template_parm_level_and_index (t, &level, &index); > - if (level > ftpi->max_depth) > + if (level == 0 || level > ftpi->max_depth) > return 0; > > if (TREE_CODE (t) == BOUND_TEMPLATE_TEMPLATE_PARM) > @@ -14796,20 +14796,7 @@ tsubst_decl (tree t, tree args, tsubst_flags_t complain) > && VAR_HAD_UNKNOWN_BOUND (t) > && type != error_mark_node) > type = strip_array_domain (type); > - tree sub_args = args; > - if (tree auto_node = type_uses_auto (type)) > - { > - /* Mask off any template args past the variable's context so we > - don't replace the auto with an unrelated argument. */ > - int nouter = TEMPLATE_TYPE_LEVEL (auto_node) - 1; > - int extra = TMPL_ARGS_DEPTH (args) - nouter; > - if (extra > 0) > - /* This should never happen with the new lambda instantiation > - model, but keep the handling just in case. */ > - gcc_assert (!CHECKING_P), > - sub_args = strip_innermost_template_args (args, extra); > - } > - type = tsubst (type, sub_args, complain, in_decl); > + type = tsubst (type, args, complain, in_decl); > /* Substituting the type might have recursively instantiated this > same alias (c++/86171). */ > if (gen_tmpl && DECL_ALIAS_TEMPLATE_P (gen_tmpl) Isn't this code still needed for non-CTAD autos? > @@ -15561,6 +15548,19 @@ tsubst (tree t, tree args, tsubst_flags_t complain, tree in_decl) > } > > case TEMPLATE_TYPE_PARM: > + if (template_placeholder_p (t)) > + { > + tree tmpl = CLASS_PLACEHOLDER_TEMPLATE (t); > + tmpl = tsubst_copy (tmpl, args, complain, in_decl); > + if (TREE_CODE (tmpl) == TEMPLATE_TEMPLATE_PARM) > + tmpl = TEMPLATE_TEMPLATE_PARM_TEMPLATE_DECL (tmpl); > + > + if (tmpl != CLASS_PLACEHOLDER_TEMPLATE (t)) > + return make_template_placeholder (tmpl); > + else > + return t; > + } > + /* Fall through. */ > case TEMPLATE_TEMPLATE_PARM: > case BOUND_TEMPLATE_TEMPLATE_PARM: > case TEMPLATE_PARM_INDEX: > @@ -15734,7 +15734,6 @@ tsubst (tree t, tree args, tsubst_flags_t complain, tree in_decl) > of a constrained placeholder. */; > else if (TREE_CODE (t) == TEMPLATE_TYPE_PARM > && !PLACEHOLDER_TYPE_CONSTRAINTS_INFO (t) > - && !CLASS_PLACEHOLDER_TEMPLATE (t) > && (arg = TEMPLATE_TYPE_PARM_INDEX (t), > r = TEMPLATE_PARM_DESCENDANTS (arg)) > && (TEMPLATE_PARM_LEVEL (r) > @@ -15753,19 +15752,10 @@ tsubst (tree t, tree args, tsubst_flags_t complain, tree in_decl) > TYPE_REFERENCE_TO (r) = NULL_TREE; > > if (TREE_CODE (t) == TEMPLATE_TYPE_PARM) > - { > + if (tree ci = PLACEHOLDER_TYPE_CONSTRAINTS_INFO (t)) > /* Propagate constraints on placeholders since they are > only instantiated during satisfaction. */ > - if (tree ci = PLACEHOLDER_TYPE_CONSTRAINTS_INFO (t)) > - PLACEHOLDER_TYPE_CONSTRAINTS_INFO (r) = ci; > - else if (tree pl = CLASS_PLACEHOLDER_TEMPLATE (t)) > - { > - pl = tsubst_copy (pl, args, complain, in_decl); > - if (TREE_CODE (pl) == TEMPLATE_TEMPLATE_PARM) > - pl = TEMPLATE_TEMPLATE_PARM_TEMPLATE_DECL (pl); > - CLASS_PLACEHOLDER_TEMPLATE (r) = pl; > - } > - } > + PLACEHOLDER_TYPE_CONSTRAINTS_INFO (r) = ci; > > if (TREE_CODE (r) == TEMPLATE_TEMPLATE_PARM) > /* We have reduced the level of the template > @@ -28491,18 +28481,18 @@ make_args_non_dependent (vec<tree, va_gc> *args) > } > > /* Returns a type which represents 'auto' or 'decltype(auto)'. We use a > - TEMPLATE_TYPE_PARM with a level one deeper than the actual template > - parms. If set_canonical is true, we set TYPE_CANONICAL on it. */ > + TEMPLATE_TYPE_PARM with a level one deeper than the actual template parms, > + by default. If set_canonical is true, we set TYPE_CANONICAL on it. */ > > static tree > -make_auto_1 (tree name, bool set_canonical) > +make_auto_1 (tree name, bool set_canonical, > + int level = current_template_depth + 1) > { > tree au = cxx_make_type (TEMPLATE_TYPE_PARM); > TYPE_NAME (au) = build_decl (input_location, TYPE_DECL, name, au); > TYPE_STUB_DECL (au) = TYPE_NAME (au); > TEMPLATE_TYPE_PARM_INDEX (au) = build_template_parm_index > - (0, current_template_depth + 1, current_template_depth + 1, > - TYPE_NAME (au), NULL_TREE); > + (0, level, level, TYPE_NAME (au), NULL_TREE); > if (set_canonical) > TYPE_CANONICAL (au) = canonical_type_parameter (au); > DECL_ARTIFICIAL (TYPE_NAME (au)) = 1; > @@ -28525,12 +28515,14 @@ make_auto (void) > return make_auto_1 (auto_identifier, true); > } > > -/* Return a C++17 deduction placeholder for class template TMPL. */ > +/* Return a C++17 deduction placeholder for class template TMPL. > + There are represented as an 'auto' with the special level 0 and > + CLASS_PLACEHOLDER_TEMPLATE set. */ > > tree > make_template_placeholder (tree tmpl) > { > - tree t = make_auto_1 (auto_identifier, false); > + tree t = make_auto_1 (auto_identifier, false, /*level=*/0); > CLASS_PLACEHOLDER_TEMPLATE (t) = tmpl; > /* Our canonical type depends on the placeholder. */ > TYPE_CANONICAL (t) = canonical_type_parameter (t); > diff --git a/gcc/testsuite/g++.dg/cpp1z/class-deduction100.C b/gcc/testsuite/g++.dg/cpp1z/class-deduction100.C > new file mode 100644 > index 00000000000..379eb960da6 > --- /dev/null > +++ b/gcc/testsuite/g++.dg/cpp1z/class-deduction100.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-deduction100a.C b/gcc/testsuite/g++.dg/cpp1z/class-deduction100a.C > new file mode 100644 > index 00000000000..958ac4f9762 > --- /dev/null > +++ b/gcc/testsuite/g++.dg/cpp1z/class-deduction100a.C > @@ -0,0 +1,22 @@ > +// PR c++/91911 > +// { dg-do compile { target c++17 } } > +// A variant of class-deduction100.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-deduction100b.C b/gcc/testsuite/g++.dg/cpp1z/class-deduction100b.C > new file mode 100644 > index 00000000000..9b5c684449c > --- /dev/null > +++ b/gcc/testsuite/g++.dg/cpp1z/class-deduction100b.C > @@ -0,0 +1,22 @@ > +// PR c++/91911 > +// { dg-do compile { target c++17 } } > +// A variant of class-deduction100.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-deduction101.C b/gcc/testsuite/g++.dg/cpp1z/class-deduction101.C > new file mode 100644 > index 00000000000..20504780d32 > --- /dev/null > +++ b/gcc/testsuite/g++.dg/cpp1z/class-deduction101.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-deduction101a.C b/gcc/testsuite/g++.dg/cpp1z/class-deduction101a.C > new file mode 100644 > index 00000000000..77b05d23b62 > --- /dev/null > +++ b/gcc/testsuite/g++.dg/cpp1z/class-deduction101a.C > @@ -0,0 +1,27 @@ > +// PR c++/98077 > +// { dg-do compile { target c++17 } } > +// A variant of class-deduction101.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; > diff --git a/gcc/testsuite/g++.dg/cpp1z/class-deduction101b.C b/gcc/testsuite/g++.dg/cpp1z/class-deduction101b.C > new file mode 100644 > index 00000000000..50df934796d > --- /dev/null > +++ b/gcc/testsuite/g++.dg/cpp1z/class-deduction101b.C > @@ -0,0 +1,30 @@ > +// PR c++/98077 > +// { dg-do compile { target c++17 } } > +// A variant of class-deduction101.C where the template placeholder is a template > +// template parameter and ReturnType has more levels than CallableTraitT. > + > +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> > +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; > diff --git a/gcc/testsuite/g++.dg/other/pr88187.C b/gcc/testsuite/g++.dg/other/pr88187.C > index 13466d3ce57..c33644d14ca 100644 > --- a/gcc/testsuite/g++.dg/other/pr88187.C > +++ b/gcc/testsuite/g++.dg/other/pr88187.C > @@ -4,4 +4,4 @@ > template <int> struct A; > void f (A ()); // { dg-error "6:variable or field 'f' declared void" "" { target c++14_down } } > // { dg-error "missing template arguments before '\\(' token" "" { target c++14_down } .-1 } > - // { dg-error "placeholder .A. not permitted in this context" "" { target c++17 } .-2 } > + // { dg-error "deduced class type .A. in function return type" "" { target c++17 } .-2 }
On Wed, 19 Jan 2022, Jason Merrill wrote: > On 1/3/22 10:24, Patrick Palka wrote: > > On Wed, 22 Dec 2021, Jason Merrill wrote: > > > > > On 12/21/21 14:08, Patrick Palka wrote: > > > > On Tue, Dec 21, 2021 at 2:03 PM Patrick Palka <ppalka@redhat.com> wrote: > > > > > > > > > > On Wed, Jun 30, 2021 at 4:23 PM Jason Merrill <jason@redhat.com> > > > > > wrote: > > > > > > > > > > > > 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? > > > > > > > > > > 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 nonpositive level. > > > > > > > - 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' > > > > + in its type-id which might correspond to CTAD placeholders. > > > > */ > > > > + t = tsubst (TREE_TYPE (gen_tmpl), arglist, > > > > + complain | (is_dependent_type * tf_partial), > > > > + in_decl); > > > > > > So, if we aren't changing any containing template scopes from dependent to > > > non-dependent, we don't want to mess with levels. > > > > > > I think is_dependent_type is a bit too broad here; I'd expect this could > > > cause > > > trouble when e.g. instantiating a class A<int> with a member template B > > > and we > > > have both B<U> and an auto in the signature of a member template. I think > > > what we want to check is whether the outermost args are dependent. > > > > Ah yeah, I see what you mean... > > > > > > > > It would also be safer to handle adding tf_partial for alias templates in > > > instantiate_alias_template rather than lookup_template_class. Perhaps in > > > alias_ctad_tweaks as well. > > > > > > I tried adding an assert that tf_partial is set any time we see dependent > > > outermost args; I expected to need to override that for deduction guide > > > rewriting, but also seem to hit problems in concepts and TTP. Attached in > > > case you're interested; I don't think this is going to become a patch > > > suitable > > > for GCC 12. The use of uses_template_parms_level was a kludge because > > > dependent_template_arg_p returns true for null args. > > > > Interesting. In light of this general problem, I wonder if representing > > autos as template parameters with one level greater than the template > > depth, while convenient, is ultimately not the best approach? > > > > Back to the original issue concerning CTAD within alias templates, > > here's an approach that seems to work well. The idea is to treat > > CTAD placeholders less like template parameters, by giving them > > the special level 0, and by making tsubst avoid substituting them > > like template parameters. With this approach the representation > > of a CTAD placeholder no longer depends on the template depth, so > > alias template transparency for alias templates that contain CTAD > > just works. > > > > I tried extending this approach to all autos (i.e. giving all > > autos a level of 0 and adjusting tsubst accordingly), and it nearly > > works modulo handling concepts TS auto... deduction: > > > > tuple<auto...> x = tuple<int, char>{}. > > > > since unify_pack_expansion isn't prepared to see a parameter pack of > > level 0. This is likely fixable, but I suppose it'd be good to first > > get confirmation that this is a good approach before continuing down > > this patch. > > > > Below the patch that implements this approach only for CTAD > > placeholders. Attached is an incremental WIP diff that additionally > > extends the approach to all autos, which passes testing modulo the > > concept TS thing. > > > > -- >8 -- > > > > Subject: [PATCH] c++: CTAD inside 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 its instantiated definition. But this > > instantiation 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. > > > > Although we represent CTAD placeholders as template parameters, we never > > actually want to replace them via tsubst. So this patch adjusts tsubst > > to handle CTAD placeholders by simply substituting the template and > > returning a new CTAD placeholder. Moreover, this means that the level > > of a CTAD placeholder doesn't matter, so we may as well give them all > > the same level. This patch gives them the special level 0. > > > > The change in grokdeclarator makes us reject an invalid function > > return type consisting of a CTAD placeholder sooner (as in pr88187.C), > > which helps guarantee that splice_late_return_type doesn't see or need > > to handle a erroneous CTAD placeholder return type. > > > > The change in tsubst_decl removes a CHECKING_P workaround added in 2017, > > which would otherwise now get triggered for variables with CTAD placeholder > > types (since their level is 0). Alternatively, we could just guard the > > workaround with !template_placeholder_p if that's preferable. > > > > Bootstrapped and regtested on x86_64-pc-linux-gnu, does this look OK for > > trunk? > > > > PR c++/91911 > > > > gcc/cp/ChangeLog: > > > > * decl.c (grokdeclarator): Diagnose CTAD placeholder in function > > return type even when !funcdecl_p. > > * pt.c (keep_template_parm): Punt on a level 0 template parm. > > (tsubst_decl) <case VAR_DECL>: Remove CHECKING_P workaround. > > (tsubst) <case TEMPLATE_TYPE_PARM>: Handle CTAD placeholders > > specially. > > (make_auto_1): Add defaulted 'level' parameter. > > (make_template_placeholder): Pass 0 as 'level' to make_auto_1. > > > > gcc/testsuite/ChangeLog: > > > > * g++.dg/cpp1z/class-deduction100.C: New test. > > * g++.dg/cpp1z/class-deduction100a.C: New test. > > * g++.dg/cpp1z/class-deduction100b.C: New test. > > * g++.dg/cpp1z/class-deduction101.C: New test. > > * g++.dg/cpp1z/class-deduction101a.C: New test. > > * g++.dg/cpp1z/class-deduction101b.C: New test. > > --- > > gcc/cp/decl.c | 6 +- > > gcc/cp/pt.c | 60 ++++++++----------- > > .../g++.dg/cpp1z/class-deduction100.C | 17 ++++++ > > .../g++.dg/cpp1z/class-deduction100a.C | 22 +++++++ > > .../g++.dg/cpp1z/class-deduction100b.C | 22 +++++++ > > .../g++.dg/cpp1z/class-deduction101.C | 25 ++++++++ > > .../g++.dg/cpp1z/class-deduction101a.C | 27 +++++++++ > > .../g++.dg/cpp1z/class-deduction101b.C | 30 ++++++++++ > > gcc/testsuite/g++.dg/other/pr88187.C | 2 +- > > 9 files changed, 173 insertions(+), 38 deletions(-) > > create mode 100644 gcc/testsuite/g++.dg/cpp1z/class-deduction100.C > > create mode 100644 gcc/testsuite/g++.dg/cpp1z/class-deduction100a.C > > create mode 100644 gcc/testsuite/g++.dg/cpp1z/class-deduction100b.C > > create mode 100644 gcc/testsuite/g++.dg/cpp1z/class-deduction101.C > > create mode 100644 gcc/testsuite/g++.dg/cpp1z/class-deduction101a.C > > create mode 100644 gcc/testsuite/g++.dg/cpp1z/class-deduction101b.C > > > > diff --git a/gcc/cp/decl.c b/gcc/cp/decl.c > > index 0b71c00f5ab..406a9163ffd 100644 > > --- a/gcc/cp/decl.c > > +++ b/gcc/cp/decl.c > > @@ -12635,11 +12635,11 @@ grokdeclarator (const cp_declarator *declarator, > > if (!tmpl) > > if (tree late_auto = type_uses_auto (late_return_type)) > > tmpl = CLASS_PLACEHOLDER_TEMPLATE (late_auto); > > - if (tmpl && funcdecl_p) > > + if (tmpl) > > { > > - if (!dguide_name_p (unqualified_id)) > > + if (!funcdecl_p || !dguide_name_p (unqualified_id)) > > { > > - error_at (declarator->id_loc, "deduced class " > > + error_at (typespec_loc, "deduced class " > > "type %qD in function return type", > > DECL_NAME (tmpl)); > > inform (DECL_SOURCE_LOCATION (tmpl), > > This seems like it could go in separately. Like so? -- >8 Subject: [PATCH] c++: consistently diagnose bare CTAD placeholder as fn return type Relax slightly the existing code for diagnosing a bare CTAD placeholder as the return type of a function declarator to also handle the abstract declarator case. gcc/cp/ChangeLog: * decl.cc (grokdeclarator): Diagnose CTAD placeholder in function return type even when !funcdecl_p. gcc/testsuite/ChangeLog: * g++.dg/other/pr88187.C: Adjust expected diagnostic accordingly. --- gcc/cp/decl.cc | 6 +++--- gcc/testsuite/g++.dg/other/pr88187.C | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/gcc/cp/decl.cc b/gcc/cp/decl.cc index 2dade75c489..1cbe9a34be0 100644 --- a/gcc/cp/decl.cc +++ b/gcc/cp/decl.cc @@ -12647,11 +12647,11 @@ grokdeclarator (const cp_declarator *declarator, if (!tmpl) if (tree late_auto = type_uses_auto (late_return_type)) tmpl = CLASS_PLACEHOLDER_TEMPLATE (late_auto); - if (tmpl && funcdecl_p) + if (tmpl) { - if (!dguide_name_p (unqualified_id)) + if (!funcdecl_p || !dguide_name_p (unqualified_id)) { - error_at (declarator->id_loc, "deduced class " + error_at (typespec_loc, "deduced class " "type %qD in function return type", DECL_NAME (tmpl)); inform (DECL_SOURCE_LOCATION (tmpl), diff --git a/gcc/testsuite/g++.dg/other/pr88187.C b/gcc/testsuite/g++.dg/other/pr88187.C index 13466d3ce57..7812e3fd5b3 100644 --- a/gcc/testsuite/g++.dg/other/pr88187.C +++ b/gcc/testsuite/g++.dg/other/pr88187.C @@ -4,4 +4,4 @@ template <int> struct A; void f (A ()); // { dg-error "6:variable or field 'f' declared void" "" { target c++14_down } } // { dg-error "missing template arguments before '\\(' token" "" { target c++14_down } .-1 } - // { dg-error "placeholder .A. not permitted in this context" "" { target c++17 } .-2 } + // { dg-error "deduced class type 'A' in function return type" "" { target c++17 } .-2 }
On 1/19/22 17:32, Patrick Palka wrote: > On Wed, 19 Jan 2022, Jason Merrill wrote: > >> On 1/3/22 10:24, Patrick Palka wrote: >>> On Wed, 22 Dec 2021, Jason Merrill wrote: >>> >>>> On 12/21/21 14:08, Patrick Palka wrote: >>>>> On Tue, Dec 21, 2021 at 2:03 PM Patrick Palka <ppalka@redhat.com> wrote: >>>>>> >>>>>> On Wed, Jun 30, 2021 at 4:23 PM Jason Merrill <jason@redhat.com> >>>>>> wrote: >>>>>>> >>>>>>> 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? >>>>>> >>>>>> 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 nonpositive level. >>>> >>>>> - 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' >>>>> + in its type-id which might correspond to CTAD placeholders. >>>>> */ >>>>> + t = tsubst (TREE_TYPE (gen_tmpl), arglist, >>>>> + complain | (is_dependent_type * tf_partial), >>>>> + in_decl); >>>> >>>> So, if we aren't changing any containing template scopes from dependent to >>>> non-dependent, we don't want to mess with levels. >>>> >>>> I think is_dependent_type is a bit too broad here; I'd expect this could >>>> cause >>>> trouble when e.g. instantiating a class A<int> with a member template B >>>> and we >>>> have both B<U> and an auto in the signature of a member template. I think >>>> what we want to check is whether the outermost args are dependent. >>> >>> Ah yeah, I see what you mean... >>> >>>> >>>> It would also be safer to handle adding tf_partial for alias templates in >>>> instantiate_alias_template rather than lookup_template_class. Perhaps in >>>> alias_ctad_tweaks as well. >>>> >>>> I tried adding an assert that tf_partial is set any time we see dependent >>>> outermost args; I expected to need to override that for deduction guide >>>> rewriting, but also seem to hit problems in concepts and TTP. Attached in >>>> case you're interested; I don't think this is going to become a patch >>>> suitable >>>> for GCC 12. The use of uses_template_parms_level was a kludge because >>>> dependent_template_arg_p returns true for null args. >>> >>> Interesting. In light of this general problem, I wonder if representing >>> autos as template parameters with one level greater than the template >>> depth, while convenient, is ultimately not the best approach? >>> >>> Back to the original issue concerning CTAD within alias templates, >>> here's an approach that seems to work well. The idea is to treat >>> CTAD placeholders less like template parameters, by giving them >>> the special level 0, and by making tsubst avoid substituting them >>> like template parameters. With this approach the representation >>> of a CTAD placeholder no longer depends on the template depth, so >>> alias template transparency for alias templates that contain CTAD >>> just works. >>> >>> I tried extending this approach to all autos (i.e. giving all >>> autos a level of 0 and adjusting tsubst accordingly), and it nearly >>> works modulo handling concepts TS auto... deduction: >>> >>> tuple<auto...> x = tuple<int, char>{}. >>> >>> since unify_pack_expansion isn't prepared to see a parameter pack of >>> level 0. This is likely fixable, but I suppose it'd be good to first >>> get confirmation that this is a good approach before continuing down >>> this patch. >>> >>> Below the patch that implements this approach only for CTAD >>> placeholders. Attached is an incremental WIP diff that additionally >>> extends the approach to all autos, which passes testing modulo the >>> concept TS thing. >>> >>> -- >8 -- >>> >>> Subject: [PATCH] c++: CTAD inside 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 its instantiated definition. But this >>> instantiation 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. >>> >>> Although we represent CTAD placeholders as template parameters, we never >>> actually want to replace them via tsubst. So this patch adjusts tsubst >>> to handle CTAD placeholders by simply substituting the template and >>> returning a new CTAD placeholder. Moreover, this means that the level >>> of a CTAD placeholder doesn't matter, so we may as well give them all >>> the same level. This patch gives them the special level 0. >>> >>> The change in grokdeclarator makes us reject an invalid function >>> return type consisting of a CTAD placeholder sooner (as in pr88187.C), >>> which helps guarantee that splice_late_return_type doesn't see or need >>> to handle a erroneous CTAD placeholder return type. >>> >>> The change in tsubst_decl removes a CHECKING_P workaround added in 2017, >>> which would otherwise now get triggered for variables with CTAD placeholder >>> types (since their level is 0). Alternatively, we could just guard the >>> workaround with !template_placeholder_p if that's preferable. >>> >>> Bootstrapped and regtested on x86_64-pc-linux-gnu, does this look OK for >>> trunk? >>> >>> PR c++/91911 >>> >>> gcc/cp/ChangeLog: >>> >>> * decl.c (grokdeclarator): Diagnose CTAD placeholder in function >>> return type even when !funcdecl_p. >>> * pt.c (keep_template_parm): Punt on a level 0 template parm. >>> (tsubst_decl) <case VAR_DECL>: Remove CHECKING_P workaround. >>> (tsubst) <case TEMPLATE_TYPE_PARM>: Handle CTAD placeholders >>> specially. >>> (make_auto_1): Add defaulted 'level' parameter. >>> (make_template_placeholder): Pass 0 as 'level' to make_auto_1. >>> >>> gcc/testsuite/ChangeLog: >>> >>> * g++.dg/cpp1z/class-deduction100.C: New test. >>> * g++.dg/cpp1z/class-deduction100a.C: New test. >>> * g++.dg/cpp1z/class-deduction100b.C: New test. >>> * g++.dg/cpp1z/class-deduction101.C: New test. >>> * g++.dg/cpp1z/class-deduction101a.C: New test. >>> * g++.dg/cpp1z/class-deduction101b.C: New test. >>> --- >>> gcc/cp/decl.c | 6 +- >>> gcc/cp/pt.c | 60 ++++++++----------- >>> .../g++.dg/cpp1z/class-deduction100.C | 17 ++++++ >>> .../g++.dg/cpp1z/class-deduction100a.C | 22 +++++++ >>> .../g++.dg/cpp1z/class-deduction100b.C | 22 +++++++ >>> .../g++.dg/cpp1z/class-deduction101.C | 25 ++++++++ >>> .../g++.dg/cpp1z/class-deduction101a.C | 27 +++++++++ >>> .../g++.dg/cpp1z/class-deduction101b.C | 30 ++++++++++ >>> gcc/testsuite/g++.dg/other/pr88187.C | 2 +- >>> 9 files changed, 173 insertions(+), 38 deletions(-) >>> create mode 100644 gcc/testsuite/g++.dg/cpp1z/class-deduction100.C >>> create mode 100644 gcc/testsuite/g++.dg/cpp1z/class-deduction100a.C >>> create mode 100644 gcc/testsuite/g++.dg/cpp1z/class-deduction100b.C >>> create mode 100644 gcc/testsuite/g++.dg/cpp1z/class-deduction101.C >>> create mode 100644 gcc/testsuite/g++.dg/cpp1z/class-deduction101a.C >>> create mode 100644 gcc/testsuite/g++.dg/cpp1z/class-deduction101b.C >>> >>> diff --git a/gcc/cp/decl.c b/gcc/cp/decl.c >>> index 0b71c00f5ab..406a9163ffd 100644 >>> --- a/gcc/cp/decl.c >>> +++ b/gcc/cp/decl.c >>> @@ -12635,11 +12635,11 @@ grokdeclarator (const cp_declarator *declarator, >>> if (!tmpl) >>> if (tree late_auto = type_uses_auto (late_return_type)) >>> tmpl = CLASS_PLACEHOLDER_TEMPLATE (late_auto); >>> - if (tmpl && funcdecl_p) >>> + if (tmpl) >>> { >>> - if (!dguide_name_p (unqualified_id)) >>> + if (!funcdecl_p || !dguide_name_p (unqualified_id)) >>> { >>> - error_at (declarator->id_loc, "deduced class " >>> + error_at (typespec_loc, "deduced class " >>> "type %qD in function return type", >>> DECL_NAME (tmpl)); >>> inform (DECL_SOURCE_LOCATION (tmpl), >> >> This seems like it could go in separately. > > Like so? Yes (OK). > -- >8 > > Subject: [PATCH] c++: consistently diagnose bare CTAD placeholder as fn return > type > > Relax slightly the existing code for diagnosing a bare CTAD placeholder > as the return type of a function declarator to also handle the abstract > declarator case. > > gcc/cp/ChangeLog: > > * decl.cc (grokdeclarator): Diagnose CTAD placeholder in > function return type even when !funcdecl_p. > > gcc/testsuite/ChangeLog: > > * g++.dg/other/pr88187.C: Adjust expected diagnostic accordingly. > --- > gcc/cp/decl.cc | 6 +++--- > gcc/testsuite/g++.dg/other/pr88187.C | 2 +- > 2 files changed, 4 insertions(+), 4 deletions(-) ... >> Isn't this code still needed for non-CTAD autos? > > Probably only in theory -- the safeguard was added over 4 years ago so I > presume it's dead code by now. Shall we keep it and guard it with > !template_placeholder_p (auto_node) instead? Ah, I didn't read closely enough to see that we were already asserting that it was dead code. Go ahead and remove it, then; the patch is OK. Jason
diff --git a/gcc/cp/pt.c b/gcc/cp/pt.c index f73c7471a33..23c5f515716 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' + in its type-id which might correspond to CTAD placeholders. */ + t = tsubst (TREE_TYPE (gen_tmpl), arglist, + complain | (is_dependent_type * tf_partial), + 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 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..ae3c55508b2 --- /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-deduction93.C b/gcc/testsuite/g++.dg/cpp1z/class-deduction93.C new file mode 100644 index 00000000000..eebc986832e --- /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 = decltype(function{F()}); + +template<class F> +using ReturnType = typename CallableTraitT<F>::type; + +using type = ReturnType<int(*)()>; +using type = int;