c++: auto(x) partial substitution [PR110025, PR114138]

Message ID 20240227204839.3396154-1-ppalka@redhat.com
State New
Headers
Series c++: auto(x) partial substitution [PR110025, PR114138] |

Checks

Context Check Description
linaro-tcwg-bot/tcwg_gcc_build--master-arm success Testing passed
linaro-tcwg-bot/tcwg_gcc_build--master-aarch64 success Testing passed
linaro-tcwg-bot/tcwg_gcc_check--master-aarch64 success Testing passed
linaro-tcwg-bot/tcwg_gcc_check--master-arm success Testing passed

Commit Message

Patrick Palka Feb. 27, 2024, 8:48 p.m. UTC
  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
  

Comments

Jason Merrill Feb. 28, 2024, 9:43 p.m. UTC | #1
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.

> +	  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?

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...

Jason
  
Patrick Palka Feb. 29, 2024, 7:17 p.m. UTC | #2
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>&&>));
  
Jason Merrill March 1, 2024, 1:39 p.m. UTC | #3
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?

> 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>&&>));
  
Patrick Palka March 1, 2024, 3:17 p.m. UTC | #4
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>&&>));
> 
>
  
Jason Merrill March 1, 2024, 3:27 p.m. UTC | #5
On 3/1/24 10:17, Patrick Palka wrote:
> 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.

Hmm, sure.

Anyway, the second patch is OK.

Jason
  

Patch

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.  */
+	  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. */
+      gcc_checking_assert (type == auto_node);
+      gcc_checking_assert (!TYPE_QUALS (type));
+      gcc_checking_assert (TREE_VEC_LENGTH (targs) == 1);
+      return TREE_VEC_ELT (targs, 0);
+    }
+  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..bded5caebf4
--- /dev/null
+++ b/gcc/testsuite/g++.dg/cpp23/auto-fncast18.C
@@ -0,0 +1,71 @@ 
+// 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;
+  }
+
+}
+
+int main() {
+  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>&&>));
+}