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.
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>&&>));