On Wed, 28 Feb 2024, Jason Merrill wrote: > On 2/27/24 15:48, Patrick Palka wrote: > > Bootstrapped and regtested on x86_64-pc-linux-gnu, does this look > > OK for trunk and perhaps 13? > > > > -- >8 -- > > > > In r12-6773-g09845ad7569bac we gave CTAD placeholders a level of 0 and > > ensured we never replaced them via tsubst. It turns out that autos > > representing an explicit cast need the same treatment and for the same > > reason: such autos appear in an expression context and so their level > > gets easily messed up after partial substitution, leading to premature > > replacement via an incidental tsubst instead of via do_auto_deduction. > > > > This patch fixes this by extending the r12-6773 approach to auto(x) and > > auto{x}. > > > > 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 deduction of a level-less non-CTAD > > 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 | 31 +++++++++- > > 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 | 71 ++++++++++++++++++++++ > > 6 files changed, 138 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..1e518e6ef51 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 using a 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..620fe5cdbfa 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,14 @@ 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) > > { > > + /* Level-less auto must be replaced via do_auto_deduction. */ > > This comment could use clarification about the CTAD case.
Fixed. > > > + gcc_checking_assert (is_auto (t)); > > tree tmpl = CLASS_PLACEHOLDER_TEMPLATE (t); > > + if (!tmpl) > > + return t; > > + > > tmpl = tsubst_expr (tmpl, args, complain, in_decl); > > if (TREE_CODE (tmpl) == TEMPLATE_TEMPLATE_PARM) > > tmpl = TEMPLATE_TEMPLATE_PARM_TEMPLATE_DECL (tmpl); > > @@ -29311,6 +29317,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 +31230,15 @@ do_auto_deduction (tree type, tree init, tree > > auto_node, > > } > > } > > - if (TEMPLATE_TYPE_LEVEL (auto_node) == 1) > > + if (TEMPLATE_TYPE_LEVEL (auto_node) == 0) > > + { > > + /* Level-less auto can't be replaced via tsubst, so do it directly. > > */ > > 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. 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>&&>)); -- 2.44.0.53.g0f9d4d28b7