On Fri, 1 Mar 2024, Jason Merrill wrote: > On 2/29/24 14:17, Patrick Palka wrote: > > On Wed, 28 Feb 2024, Jason Merrill wrote: > > > I wonder about, rather than returning it directly, setting its level to 1 > > > for > > > the substitution? > > > > Done, that works nicely. > > > > > Then I wonder if it would be feasible to give all autos level 0 and adjust > > > it > > > here? That's probably not a stage 4 change, though... > > > > It seems feasible. I experimented doing this in the past[1] and ran > > into two complications. One complication was with constrained auto > > deduction, e.g. > > > > template<class T> > > void g() { > > C<T*> auto x = ...; > > }; > > > > Here the underlying concept-id that we enter satisfaction with is > > C<auto, T*> where this auto has level one greater than the template > > depth, and the argument vector we pass has an extra innermost level > > containing the deduced type, so things match up nicely. This seems > > to be the only place where we truly need auto to have a non 0/1 level. > > In my WIP patch in that thread I just made do_auto_deduction build the > > concept-id C<auto, T*> in terms of an auto of the proper level before > > entering satisfaction, which was kind of ugly but worked. > > So maybe set its level to TMPL_ARGS_DEPTH (targs) after add_to_template_args, > rather than 1?
AFAICT in-place type modification in this case would be unsafe or at least difficult to reason about due to the satisfaction/normalization caches. We would cache the result as if the auto had the nonzero level and then (presumably) reset its level back to 0 afterward, leaving the hash tables in an inconsistent state. > > > The other complication was with Concepts TS extended auto deduction: > > > > tuple<auto...> t = tuple<int, char>{}; > > > > because unify_pack_expansion (called from fn_type_unification during > > do_auto_deduction) isn't prepared to see a parameter pack of level 0 > > (unify has no problems with ordinary tparms of level 0 though). This > > shouldn't be too hard to fix though. > > > > How does the following look for trunk and perhaps 13 (there should be > > no functional change for code that doesn't use auto(x))? > > > > [1]: https://gcc.gnu.org/pipermail/gcc-patches/2022-January/587818.html > > > > -- >8 -- > > > > PR c++/110025 > > PR c++/114138 > > > > gcc/cp/ChangeLog: > > > > * cp-tree.h (make_cast_auto): Declare. > > * parser.cc (cp_parser_functional_cast): Replace a parsed auto > > with a level-less one via make_cast_auto. > > * pt.cc (find_parameter_packs_r): Don't treat level-less auto > > as a type parameter pack. > > (tsubst) <case TEMPLATE_TYPE_PARM>: Generalized CTAD placeholder > > handling to all level-less autos. > > (make_cast_auto): Define. > > (do_auto_deduction): Handle replacement of a level-less auto. > > > > gcc/testsuite/ChangeLog: > > > > * g++.dg/cpp23/auto-fncast16.C: New test. > > * g++.dg/cpp23/auto-fncast17.C: New test. > > * g++.dg/cpp23/auto-fncast18.C: New test. > > --- > > gcc/cp/cp-tree.h | 1 + > > gcc/cp/parser.cc | 11 ++++ > > gcc/cp/pt.cc | 37 +++++++++++- > > gcc/testsuite/g++.dg/cpp23/auto-fncast16.C | 12 ++++ > > gcc/testsuite/g++.dg/cpp23/auto-fncast17.C | 15 +++++ > > gcc/testsuite/g++.dg/cpp23/auto-fncast18.C | 69 ++++++++++++++++++++++ > > 6 files changed, 142 insertions(+), 3 deletions(-) > > create mode 100644 gcc/testsuite/g++.dg/cpp23/auto-fncast16.C > > create mode 100644 gcc/testsuite/g++.dg/cpp23/auto-fncast17.C > > create mode 100644 gcc/testsuite/g++.dg/cpp23/auto-fncast18.C > > > > diff --git a/gcc/cp/cp-tree.h b/gcc/cp/cp-tree.h > > index 04c3aa6cd91..6f1da1c7bad 100644 > > --- a/gcc/cp/cp-tree.h > > +++ b/gcc/cp/cp-tree.h > > @@ -7476,6 +7476,7 @@ extern tree make_decltype_auto > > (void); > > extern tree make_constrained_auto (tree, tree); > > extern tree make_constrained_decltype_auto (tree, tree); > > extern tree make_template_placeholder (tree); > > +extern tree make_cast_auto (void); > > extern bool template_placeholder_p (tree); > > extern bool ctad_template_p (tree); > > extern bool unparenthesized_id_or_class_member_access_p (tree); > > diff --git a/gcc/cp/parser.cc b/gcc/cp/parser.cc > > index 3ee9d49fb8e..3dbe6722ba1 100644 > > --- a/gcc/cp/parser.cc > > +++ b/gcc/cp/parser.cc > > @@ -33314,6 +33314,17 @@ cp_parser_functional_cast (cp_parser* parser, tree > > type) > > if (!type) > > type = error_mark_node; > > + if (TREE_CODE (type) == TYPE_DECL > > + && is_auto (TREE_TYPE (type))) > > + type = TREE_TYPE (type); > > + > > + if (is_auto (type) > > + && !AUTO_IS_DECLTYPE (type) > > + && !PLACEHOLDER_TYPE_CONSTRAINTS (type) > > + && !CLASS_PLACEHOLDER_TEMPLATE (type)) > > + /* auto(x) and auto{x} are represented by level-less auto. */ > > + type = make_cast_auto (); > > + > > if (cp_lexer_next_token_is (parser->lexer, CPP_OPEN_BRACE)) > > { > > cp_lexer_set_source_position (parser->lexer); > > diff --git a/gcc/cp/pt.cc b/gcc/cp/pt.cc > > index 2803824d11e..369e33f23c7 100644 > > --- a/gcc/cp/pt.cc > > +++ b/gcc/cp/pt.cc > > @@ -3921,7 +3921,8 @@ find_parameter_packs_r (tree *tp, int *walk_subtrees, > > void* data) > > parameter pack (14.6.3), or the type-specifier-seq of a type-id that > > is a pack expansion, the invented template parameter is a template > > parameter pack. */ > > - if (ppd->type_pack_expansion_p && is_auto (t)) > > + if (ppd->type_pack_expansion_p && is_auto (t) > > + && TEMPLATE_TYPE_LEVEL (t) != 0) > > TEMPLATE_TYPE_PARAMETER_PACK (t) = true; > > if (TEMPLATE_TYPE_PARAMETER_PACK (t)) > > parameter_pack_p = true; > > @@ -16297,9 +16298,19 @@ tsubst (tree t, tree args, tsubst_flags_t complain, > > tree in_decl) > > } > > case TEMPLATE_TYPE_PARM: > > - if (template_placeholder_p (t)) > > + if (TEMPLATE_TYPE_LEVEL (t) == 0) > > { > > + /* This is either an ordinary level-less auto or a CTAD placeholder > > + auto. These get replaced only via do_auto_deduction which, in > > the > > + ordinary case, temporarily overrides its level to 1 before > > calling > > + tsubst. CTAD placeholders are replaced via do_class_deduction. > > */ > > + gcc_checking_assert (is_auto (t)); > > tree tmpl = CLASS_PLACEHOLDER_TEMPLATE (t); > > + if (!tmpl) > > + /* Ordinary level-less auto, nothing to substitute. */ > > + return t; > > + > > + /* Substitute the template of this CTAD placeholder. */ > > tmpl = tsubst_expr (tmpl, args, complain, in_decl); > > if (TREE_CODE (tmpl) == TEMPLATE_TEMPLATE_PARM) > > tmpl = TEMPLATE_TEMPLATE_PARM_TEMPLATE_DECL (tmpl); > > @@ -29311,6 +29322,17 @@ template_placeholder_p (tree t) > > return is_auto (t) && CLASS_PLACEHOLDER_TEMPLATE (t); > > } > > +/* Return an auto for an explicit cast, e.g. auto(x) or auto{x}. > > + Like CTAD placeholders, these have level 0 so that they're not > > + accidentally replaced via tsubst, and are always directly resolved > > + via do_auto_deduction. */ > > + > > +tree > > +make_cast_auto () > > +{ > > + return make_auto_1 (auto_identifier, true, /*level=*/0); > > +} > > + > > /* Make a "constrained auto" type-specifier. This is an auto or > > decltype(auto) type with constraints that must be associated after > > deduction. The constraint is formed from the given concept CON > > @@ -31213,7 +31235,16 @@ do_auto_deduction (tree type, tree init, tree > > auto_node, > > } > > } > > - if (TEMPLATE_TYPE_LEVEL (auto_node) == 1) > > + if (TEMPLATE_TYPE_LEVEL (auto_node) == 0) > > + { > > + /* Substitute this level-less auto via tsubst by temporarily > > + overriding its level to 1. */ > > + TEMPLATE_TYPE_LEVEL (auto_node) = 1; > > + type = tsubst (type, targs, complain, NULL_TREE); > > + TEMPLATE_TYPE_LEVEL (auto_node) = 0; > > + return type; > > + } > > + else if (TEMPLATE_TYPE_LEVEL (auto_node) == 1) > > /* The outer template arguments are already substituted into type > > (but we still may have used them for constraint checking above). > > */; > > else if (context == adc_unify) > > diff --git a/gcc/testsuite/g++.dg/cpp23/auto-fncast16.C > > b/gcc/testsuite/g++.dg/cpp23/auto-fncast16.C > > new file mode 100644 > > index 00000000000..e2c13f6b050 > > --- /dev/null > > +++ b/gcc/testsuite/g++.dg/cpp23/auto-fncast16.C > > @@ -0,0 +1,12 @@ > > +// PR c++/110025 > > +// { dg-do compile { target c++23 } } > > + > > +template<auto V, class = decltype(auto(V)), class = decltype(auto{V})> > > +struct A { }; > > + > > +template<auto V> > > +A<V> f(); > > + > > +int main() { > > + f<0>(); > > +} > > diff --git a/gcc/testsuite/g++.dg/cpp23/auto-fncast17.C > > b/gcc/testsuite/g++.dg/cpp23/auto-fncast17.C > > new file mode 100644 > > index 00000000000..25186dfdbf2 > > --- /dev/null > > +++ b/gcc/testsuite/g++.dg/cpp23/auto-fncast17.C > > @@ -0,0 +1,15 @@ > > +// PR c++/110025 > > +// { dg-do compile { target c++23 } } > > + > > +template<class...> struct tuple; > > + > > +template<auto V> > > +using constant_t = int; > > + > > +template<auto... V> > > +using constants_t = tuple<constant_t<auto(V)>...>; > > + > > +using ty0 = constants_t<>; > > +using ty1 = constants_t<1>; > > +using ty2 = constants_t<1, 2>; > > +using ty3 = constants_t<1, 2, 3>; > > diff --git a/gcc/testsuite/g++.dg/cpp23/auto-fncast18.C > > b/gcc/testsuite/g++.dg/cpp23/auto-fncast18.C > > new file mode 100644 > > index 00000000000..4656723684f > > --- /dev/null > > +++ b/gcc/testsuite/g++.dg/cpp23/auto-fncast18.C > > @@ -0,0 +1,69 @@ > > +// PR c++/114138 > > +// { dg-do compile { target c++23 } } > > + > > +namespace std { > > + template <class T> > > + T&& declval() noexcept requires true; > > + > > + template <class> > > + void declval() noexcept; > > + > > + namespace detail { > > + struct none_such; > > + template <class> > > + using none_such_t = none_such; > > + > > + template <class T> > > + extern const none_such_t<T> _getter_for; > > + > > + template <class T> > > + using _decay_t = decltype(auto(declval<T>())); > > + > > + static_assert(__is_same_as(_decay_t<void>, void)); > > + } > > + > > + template <const auto& Fn, class... Args> > > + using _result_of_t = decltype(Fn(declval<Args>()...)); > > + > > + template <unsigned I, class Tuple> > > + using tuple_element_t = > > _result_of_t<detail::_getter_for<detail::_decay_t<Tuple>>, char(*)[I+1], > > Tuple>; > > + > > + template <class First, class Second> > > + struct pair { > > + First first; > > + Second second; > > + }; > > + > > + template <class> > > + inline constexpr bool _is_pair = false; > > + > > + template <class First, class Second> > > + inline constexpr bool _is_pair<pair<First, Second>> = true; > > + > > + template <class T> > > + concept Pair = _is_pair<decltype(auto(std::declval<T>()))>; > > + > > + template <unsigned I, Pair P> > > + requires (I <= 1) > > + decltype(auto) get(P&& p) noexcept { > > + if constexpr (I == 0) { > > + return (static_cast<P&&>(p).first); > > + } else { > > + return (static_cast<P&&>(p).second); > > + } > > + } > > + > > + namespace detail { > > + inline constexpr auto _pair_getter = > > + []<unsigned J, class Pair>(char(*)[J], Pair&& p) noexcept -> > > decltype(auto) { > > + return std::get<J-1>(static_cast<Pair&&>(p)); > > + }; > > + > > + template <class First, class Second> > > + inline constexpr auto _getter_for<pair<First, Second>> = > > _pair_getter; > > + } > > + > > +} > > + > > +static_assert(__is_same_as(int&, std::tuple_element_t<0, std::pair<int, > > float>&>)); > > +static_assert(__is_same_as(float&&, std::tuple_element_t<1, std::pair<int, > > float>&&>)); > >