c++: Allow constexpr decltype(auto) [PR102229]

Message ID 20211210222909.649695-1-polacek@redhat.com
State Committed
Commit 06d5dcef72542baf49ac245cfde2ad7ecef0916b
Headers
Series c++: Allow constexpr decltype(auto) [PR102229] |

Commit Message

Marek Polacek Dec. 10, 2021, 10:29 p.m. UTC
  My r11-2202 was trying to enforce [dcl.type.auto.deduct]/4, which says
"If the placeholder-type-specifier is of the form type-constraint[opt]
decltype(auto), T shall be the placeholder alone."  But this made us
reject 'constexpr decltype(auto)', which, after clarification from CWG,
should be valid.  [dcl.type.auto.deduct]/4 is supposed to be a syntactic
constraint, not semantic, so it's OK that the constexpr marks the object
as const.

As a consequence, checking TYPE_QUALS in do_auto_deduction is too late,
and we have a FIXME there anyway.  So in this patch I'm attempting to
detect 'const decltype(auto)' earlier.  If I'm going to use TYPE_QUALS,
it needs to happen before we mark the object as const due to constexpr,
that is, before grokdeclarator's

  /* A `constexpr' specifier used in an object declaration declares
     the object as `const'.  */
  if (constexpr_p && innermost_code != cdk_function)
    ...

Constrained decltype(auto) was a little problem, hence the TYPENAME
check.  But in a typename context you can't use decltype(auto) anyway,
I think.

Bootstrapped/regtested on x86_64-pc-linux-gnu, ok for trunk/11?

	PR c++/102229

gcc/cp/ChangeLog:

	* decl.c (check_decltype_auto): New.
	(grokdeclarator): Call it.
	* pt.c (do_auto_deduction): Don't check decltype(auto) here.

gcc/testsuite/ChangeLog:

	* g++.dg/cpp1y/decltype-auto5.C: New test.
---
 gcc/cp/decl.c                               | 58 ++++++++++++++-------
 gcc/cp/pt.c                                 | 13 -----
 gcc/testsuite/g++.dg/cpp1y/decltype-auto5.C | 35 +++++++++++++
 3 files changed, 74 insertions(+), 32 deletions(-)
 create mode 100644 gcc/testsuite/g++.dg/cpp1y/decltype-auto5.C


base-commit: 1e2eee7b29ed2afbc7edea6f3da7e6c8f70b1a4e
  

Comments

Jason Merrill Dec. 13, 2021, 3:02 p.m. UTC | #1
On 12/10/21 17:29, Marek Polacek wrote:
> My r11-2202 was trying to enforce [dcl.type.auto.deduct]/4, which says
> "If the placeholder-type-specifier is of the form type-constraint[opt]
> decltype(auto), T shall be the placeholder alone."  But this made us
> reject 'constexpr decltype(auto)', which, after clarification from CWG,
> should be valid.  [dcl.type.auto.deduct]/4 is supposed to be a syntactic
> constraint, not semantic, so it's OK that the constexpr marks the object
> as const.
> 
> As a consequence, checking TYPE_QUALS in do_auto_deduction is too late,
> and we have a FIXME there anyway.  So in this patch I'm attempting to
> detect 'const decltype(auto)' earlier.  If I'm going to use TYPE_QUALS,
> it needs to happen before we mark the object as const due to constexpr,
> that is, before grokdeclarator's
> 
>    /* A `constexpr' specifier used in an object declaration declares
>       the object as `const'.  */
>    if (constexpr_p && innermost_code != cdk_function)
>      ...
> 
> Constrained decltype(auto) was a little problem, hence the TYPENAME
> check.  But in a typename context you can't use decltype(auto) anyway,
> I think.

I wonder about checking even earlier, like in cp_parser_decl_specifier_seq?

> Bootstrapped/regtested on x86_64-pc-linux-gnu, ok for trunk/11?
> 
> 	PR c++/102229
> 
> gcc/cp/ChangeLog:
> 
> 	* decl.c (check_decltype_auto): New.
> 	(grokdeclarator): Call it.
> 	* pt.c (do_auto_deduction): Don't check decltype(auto) here.
> 
> gcc/testsuite/ChangeLog:
> 
> 	* g++.dg/cpp1y/decltype-auto5.C: New test.
> ---
>   gcc/cp/decl.c                               | 58 ++++++++++++++-------
>   gcc/cp/pt.c                                 | 13 -----
>   gcc/testsuite/g++.dg/cpp1y/decltype-auto5.C | 35 +++++++++++++
>   3 files changed, 74 insertions(+), 32 deletions(-)
>   create mode 100644 gcc/testsuite/g++.dg/cpp1y/decltype-auto5.C
> 
> diff --git a/gcc/cp/decl.c b/gcc/cp/decl.c
> index 56f80775ca0..196eac287eb 100644
> --- a/gcc/cp/decl.c
> +++ b/gcc/cp/decl.c
> @@ -11354,6 +11354,33 @@ name_unnamed_type (tree type, tree decl)
>     gcc_assert (!TYPE_UNNAMED_P (type));
>   }
>   
> +/* Check that decltype(auto) was well-formed: only plain decltype(auto)
> +   is allowed.  TYPE might contain a decltype(auto).  Returns true if
> +   there was a problem, false otherwise.  */
> +
> +static bool
> +check_decltype_auto (location_t loc, tree type)
> +{
> +  if (tree a = type_uses_auto (type))
> +    {
> +      if (AUTO_IS_DECLTYPE (a))
> +	{
> +	  if (a != type)
> +	    {
> +	      error_at (loc, "%qT as type rather than plain "
> +			"%<decltype(auto)%>", type);
> +	      return true;
> +	    }
> +	  else if (TYPE_QUALS (type) != TYPE_UNQUALIFIED)
> +	    {
> +	      error_at (loc, "%<decltype(auto)%> cannot be cv-qualified");
> +	      return true;
> +	    }
> +	}
> +    }
> +  return false;
> +}
> +
>   /* Given declspecs and a declarator (abstract or otherwise), determine
>      the name and type of the object declared and construct a DECL node
>      for it.
> @@ -12702,25 +12729,9 @@ grokdeclarator (const cp_declarator *declarator,
>   			  "allowed");
>   		return error_mark_node;
>   	      }
> -	    /* Only plain decltype(auto) is allowed.  */
> -	    if (tree a = type_uses_auto (type))
> -	      {
> -		if (AUTO_IS_DECLTYPE (a))
> -		  {
> -		    if (a != type)
> -		      {
> -			error_at (typespec_loc, "%qT as type rather than "
> -				  "plain %<decltype(auto)%>", type);
> -			return error_mark_node;
> -		      }
> -		    else if (TYPE_QUALS (type) != TYPE_UNQUALIFIED)
> -		      {
> -			error_at (typespec_loc, "%<decltype(auto)%> cannot be "
> -				  "cv-qualified");
> -			return error_mark_node;
> -		      }
> -		  }
> -	      }
> +
> +	    if (check_decltype_auto (typespec_loc, type))
> +	      return error_mark_node;
>   
>   	    if (ctype == NULL_TREE
>   		&& decl_context == FIELD
> @@ -13080,6 +13091,15 @@ grokdeclarator (const cp_declarator *declarator,
>   
>     id_loc = declarator ? declarator->id_loc : input_location;
>   
> +  if (innermost_code != cdk_function
> +    /* Don't check this if it can be the artifical decltype(auto)
> +       we created when building a constraint in a compound-requirement:
> +       that the type-constraint is plain is going to be checked in
> +       cp_parser_compound_requirement.  */
> +      && decl_context != TYPENAME
> +      && check_decltype_auto (id_loc, type))
> +    return error_mark_node;
> +
>     /* A `constexpr' specifier used in an object declaration declares
>        the object as `const'.  */
>     if (constexpr_p && innermost_code != cdk_function)
> diff --git a/gcc/cp/pt.c b/gcc/cp/pt.c
> index 9834baf34db..6d5da17b9d0 100644
> --- a/gcc/cp/pt.c
> +++ b/gcc/cp/pt.c
> @@ -29912,19 +29912,6 @@ do_auto_deduction (tree type, tree init, tree auto_node,
>   	return error_mark_node;
>         targs = make_tree_vec (1);
>         TREE_VEC_ELT (targs, 0) = deduced;
> -      /* FIXME: These errors ought to be diagnosed at parse time. */
> -      if (type != auto_node)
> -	{
> -          if (complain & tf_error)
> -	    error ("%qT as type rather than plain %<decltype(auto)%>", type);
> -	  return error_mark_node;
> -	}
> -      else if (TYPE_QUALS (type) != TYPE_UNQUALIFIED)
> -	{
> -	  if (complain & tf_error)
> -	    error ("%<decltype(auto)%> cannot be cv-qualified");
> -	  return error_mark_node;
> -	}
>       }
>     else
>       {
> diff --git a/gcc/testsuite/g++.dg/cpp1y/decltype-auto5.C b/gcc/testsuite/g++.dg/cpp1y/decltype-auto5.C
> new file mode 100644
> index 00000000000..01cc54fb71d
> --- /dev/null
> +++ b/gcc/testsuite/g++.dg/cpp1y/decltype-auto5.C
> @@ -0,0 +1,35 @@
> +// PR c++/102229
> +// { dg-do compile { target c++14 } }
> +
> +struct S {
> +  constexpr static decltype(auto) x = 42;
> +  const constexpr static decltype(auto) y = 42; // { dg-error "cannot be cv-qualified" }
> +
> +  constexpr decltype(auto) mfn1 () { return 0; }
> +  const constexpr decltype(auto) mfn2 () { return 0; } // { dg-error "cannot be cv-qualified" }
> +};
> +
> +constexpr decltype(auto) i = 42;
> +const constexpr decltype(auto) j = 42; // { dg-error "cannot be cv-qualified" }
> +
> +constexpr decltype(auto) fn() { return 42; }
> +const decltype(auto) fn2() { return 42; } // { dg-error "cannot be cv-qualified" }
> +
> +auto constexpr foo() -> const decltype(auto) // { dg-error "cannot be cv-qualified" }
> +{
> +  return 0;
> +}
> +
> +#if __cpp_concepts
> +template<typename>
> +concept C = true;
> +
> +constexpr C decltype(auto) x1 = 0;
> +const constexpr C decltype(auto) x2 = 0; // { dg-error "cannot be cv-qualified" "" { target c++20 } }
> +
> +constexpr C decltype(auto) fn3() { return 0; }
> +const constexpr C decltype(auto) fn4() { return 0; } // { dg-error "cannot be cv-qualified" "" { target c++20 } }
> +#endif
> +
> +template<const decltype(auto) = 42> // { dg-error "cannot be cv-qualified" }
> +void g ();
> 
> base-commit: 1e2eee7b29ed2afbc7edea6f3da7e6c8f70b1a4e
  
Marek Polacek Dec. 15, 2021, 8:20 p.m. UTC | #2
On Mon, Dec 13, 2021 at 10:02:24AM -0500, Jason Merrill wrote:
> On 12/10/21 17:29, Marek Polacek wrote:
> > My r11-2202 was trying to enforce [dcl.type.auto.deduct]/4, which says
> > "If the placeholder-type-specifier is of the form type-constraint[opt]
> > decltype(auto), T shall be the placeholder alone."  But this made us
> > reject 'constexpr decltype(auto)', which, after clarification from CWG,
> > should be valid.  [dcl.type.auto.deduct]/4 is supposed to be a syntactic
> > constraint, not semantic, so it's OK that the constexpr marks the object
> > as const.
> > 
> > As a consequence, checking TYPE_QUALS in do_auto_deduction is too late,
> > and we have a FIXME there anyway.  So in this patch I'm attempting to
> > detect 'const decltype(auto)' earlier.  If I'm going to use TYPE_QUALS,
> > it needs to happen before we mark the object as const due to constexpr,
> > that is, before grokdeclarator's
> > 
> >    /* A `constexpr' specifier used in an object declaration declares
> >       the object as `const'.  */
> >    if (constexpr_p && innermost_code != cdk_function)
> >      ...
> > 
> > Constrained decltype(auto) was a little problem, hence the TYPENAME
> > check.  But in a typename context you can't use decltype(auto) anyway,
> > I think.
> 
> I wonder about checking even earlier, like in cp_parser_decl_specifier_seq?

That _almost_ works except it wouldn't detect things like 'decltype(auto)*'
because the '*' isn't parsed in cp_parser_decl_specifier_seq, only in
declarator.  So the

     if (a != type)
       {
         error_at (loc, "%qT as type rather than plain "
           "%<decltype(auto)%>", type);

check wouldn't work.  Maybe I could just check if the next token is * or &
and give an error then.

Marek
  
Jason Merrill Dec. 15, 2021, 9:17 p.m. UTC | #3
On 12/15/21 15:20, Marek Polacek wrote:
> On Mon, Dec 13, 2021 at 10:02:24AM -0500, Jason Merrill wrote:
>> On 12/10/21 17:29, Marek Polacek wrote:
>>> My r11-2202 was trying to enforce [dcl.type.auto.deduct]/4, which says
>>> "If the placeholder-type-specifier is of the form type-constraint[opt]
>>> decltype(auto), T shall be the placeholder alone."  But this made us
>>> reject 'constexpr decltype(auto)', which, after clarification from CWG,
>>> should be valid.  [dcl.type.auto.deduct]/4 is supposed to be a syntactic
>>> constraint, not semantic, so it's OK that the constexpr marks the object
>>> as const.
>>>
>>> As a consequence, checking TYPE_QUALS in do_auto_deduction is too late,
>>> and we have a FIXME there anyway.  So in this patch I'm attempting to
>>> detect 'const decltype(auto)' earlier.  If I'm going to use TYPE_QUALS,
>>> it needs to happen before we mark the object as const due to constexpr,
>>> that is, before grokdeclarator's
>>>
>>>     /* A `constexpr' specifier used in an object declaration declares
>>>        the object as `const'.  */
>>>     if (constexpr_p && innermost_code != cdk_function)
>>>       ...
>>>
>>> Constrained decltype(auto) was a little problem, hence the TYPENAME
>>> check.  But in a typename context you can't use decltype(auto) anyway,
>>> I think.
>>
>> I wonder about checking even earlier, like in cp_parser_decl_specifier_seq?
> 
> That _almost_ works except it wouldn't detect things like 'decltype(auto)*'
> because the '*' isn't parsed in cp_parser_decl_specifier_seq, only in
> declarator.  So the

Ah, right.

>       if (a != type)
>         {
>           error_at (loc, "%qT as type rather than plain "
>             "%<decltype(auto)%>", type);
> 
> check wouldn't work.  Maybe I could just check if the next token is * or &
> and give an error then.

No, checking in grokdeclarator makes sense.

> Constrained decltype(auto) was a little problem, hence the TYPENAME
> check.  But in a typename context you can't use decltype(auto) anyway,
> I think.

Maybe check PLACEHOLDER_TYPE_CONSTRAINTS in check_decltype_auto instead?

Jason
  
Marek Polacek Dec. 15, 2021, 9:28 p.m. UTC | #4
On Wed, Dec 15, 2021 at 04:17:37PM -0500, Jason Merrill wrote:
> On 12/15/21 15:20, Marek Polacek wrote:
> > On Mon, Dec 13, 2021 at 10:02:24AM -0500, Jason Merrill wrote:
> > > On 12/10/21 17:29, Marek Polacek wrote:
> > > > My r11-2202 was trying to enforce [dcl.type.auto.deduct]/4, which says
> > > > "If the placeholder-type-specifier is of the form type-constraint[opt]
> > > > decltype(auto), T shall be the placeholder alone."  But this made us
> > > > reject 'constexpr decltype(auto)', which, after clarification from CWG,
> > > > should be valid.  [dcl.type.auto.deduct]/4 is supposed to be a syntactic
> > > > constraint, not semantic, so it's OK that the constexpr marks the object
> > > > as const.
> > > > 
> > > > As a consequence, checking TYPE_QUALS in do_auto_deduction is too late,
> > > > and we have a FIXME there anyway.  So in this patch I'm attempting to
> > > > detect 'const decltype(auto)' earlier.  If I'm going to use TYPE_QUALS,
> > > > it needs to happen before we mark the object as const due to constexpr,
> > > > that is, before grokdeclarator's
> > > > 
> > > >     /* A `constexpr' specifier used in an object declaration declares
> > > >        the object as `const'.  */
> > > >     if (constexpr_p && innermost_code != cdk_function)
> > > >       ...
> > > > 
> > > > Constrained decltype(auto) was a little problem, hence the TYPENAME
> > > > check.  But in a typename context you can't use decltype(auto) anyway,
> > > > I think.
> > > 
> > > I wonder about checking even earlier, like in cp_parser_decl_specifier_seq?
> > 
> > That _almost_ works except it wouldn't detect things like 'decltype(auto)*'
> > because the '*' isn't parsed in cp_parser_decl_specifier_seq, only in
> > declarator.  So the
> 
> Ah, right.
> 
> >       if (a != type)
> >         {
> >           error_at (loc, "%qT as type rather than plain "
> >             "%<decltype(auto)%>", type);
> > 
> > check wouldn't work.  Maybe I could just check if the next token is * or &
> > and give an error then.
> 
> No, checking in grokdeclarator makes sense.
> 
> > Constrained decltype(auto) was a little problem, hence the TYPENAME
> > check.  But in a typename context you can't use decltype(auto) anyway,
> > I think.
> 
> Maybe check PLACEHOLDER_TYPE_CONSTRAINTS in check_decltype_auto instead?

I've tried that, but that is also true for

const constexpr C decltype(auto) x2 = 0;
const constexpr C decltype(auto) fn4() { return 0; }

where we do want to check if the auto has quals.  Therefore the not very
pretty TYPENAME check :/.

Marek
  
Jason Merrill Dec. 15, 2021, 9:51 p.m. UTC | #5
On 12/15/21 16:28, Marek Polacek wrote:
> On Wed, Dec 15, 2021 at 04:17:37PM -0500, Jason Merrill wrote:
>> On 12/15/21 15:20, Marek Polacek wrote:
>>> On Mon, Dec 13, 2021 at 10:02:24AM -0500, Jason Merrill wrote:
>>>> On 12/10/21 17:29, Marek Polacek wrote:
>>>>> My r11-2202 was trying to enforce [dcl.type.auto.deduct]/4, which says
>>>>> "If the placeholder-type-specifier is of the form type-constraint[opt]
>>>>> decltype(auto), T shall be the placeholder alone."  But this made us
>>>>> reject 'constexpr decltype(auto)', which, after clarification from CWG,
>>>>> should be valid.  [dcl.type.auto.deduct]/4 is supposed to be a syntactic
>>>>> constraint, not semantic, so it's OK that the constexpr marks the object
>>>>> as const.
>>>>>
>>>>> As a consequence, checking TYPE_QUALS in do_auto_deduction is too late,
>>>>> and we have a FIXME there anyway.  So in this patch I'm attempting to
>>>>> detect 'const decltype(auto)' earlier.  If I'm going to use TYPE_QUALS,
>>>>> it needs to happen before we mark the object as const due to constexpr,
>>>>> that is, before grokdeclarator's
>>>>>
>>>>>      /* A `constexpr' specifier used in an object declaration declares
>>>>>         the object as `const'.  */
>>>>>      if (constexpr_p && innermost_code != cdk_function)
>>>>>        ...
>>>>>
>>>>> Constrained decltype(auto) was a little problem, hence the TYPENAME
>>>>> check.  But in a typename context you can't use decltype(auto) anyway,
>>>>> I think.
>>>>
>>>> I wonder about checking even earlier, like in cp_parser_decl_specifier_seq?
>>>
>>> That _almost_ works except it wouldn't detect things like 'decltype(auto)*'
>>> because the '*' isn't parsed in cp_parser_decl_specifier_seq, only in
>>> declarator.  So the
>>
>> Ah, right.
>>
>>>        if (a != type)
>>>          {
>>>            error_at (loc, "%qT as type rather than plain "
>>>              "%<decltype(auto)%>", type);
>>>
>>> check wouldn't work.  Maybe I could just check if the next token is * or &
>>> and give an error then.
>>
>> No, checking in grokdeclarator makes sense.
>>
>>> Constrained decltype(auto) was a little problem, hence the TYPENAME
>>> check.  But in a typename context you can't use decltype(auto) anyway,
>>> I think.
>>
>> Maybe check PLACEHOLDER_TYPE_CONSTRAINTS in check_decltype_auto instead?
> 
> I've tried that, but that is also true for
> 
> const constexpr C decltype(auto) x2 = 0;
> const constexpr C decltype(auto) fn4() { return 0; }
> 
> where we do want to check if the auto has quals.  Therefore the not very
> pretty TYPENAME check :/.

Aha.  The patch is OK.

Jason
  

Patch

diff --git a/gcc/cp/decl.c b/gcc/cp/decl.c
index 56f80775ca0..196eac287eb 100644
--- a/gcc/cp/decl.c
+++ b/gcc/cp/decl.c
@@ -11354,6 +11354,33 @@  name_unnamed_type (tree type, tree decl)
   gcc_assert (!TYPE_UNNAMED_P (type));
 }
 
+/* Check that decltype(auto) was well-formed: only plain decltype(auto)
+   is allowed.  TYPE might contain a decltype(auto).  Returns true if
+   there was a problem, false otherwise.  */
+
+static bool
+check_decltype_auto (location_t loc, tree type)
+{
+  if (tree a = type_uses_auto (type))
+    {
+      if (AUTO_IS_DECLTYPE (a))
+	{
+	  if (a != type)
+	    {
+	      error_at (loc, "%qT as type rather than plain "
+			"%<decltype(auto)%>", type);
+	      return true;
+	    }
+	  else if (TYPE_QUALS (type) != TYPE_UNQUALIFIED)
+	    {
+	      error_at (loc, "%<decltype(auto)%> cannot be cv-qualified");
+	      return true;
+	    }
+	}
+    }
+  return false;
+}
+
 /* Given declspecs and a declarator (abstract or otherwise), determine
    the name and type of the object declared and construct a DECL node
    for it.
@@ -12702,25 +12729,9 @@  grokdeclarator (const cp_declarator *declarator,
 			  "allowed");
 		return error_mark_node;
 	      }
-	    /* Only plain decltype(auto) is allowed.  */
-	    if (tree a = type_uses_auto (type))
-	      {
-		if (AUTO_IS_DECLTYPE (a))
-		  {
-		    if (a != type)
-		      {
-			error_at (typespec_loc, "%qT as type rather than "
-				  "plain %<decltype(auto)%>", type);
-			return error_mark_node;
-		      }
-		    else if (TYPE_QUALS (type) != TYPE_UNQUALIFIED)
-		      {
-			error_at (typespec_loc, "%<decltype(auto)%> cannot be "
-				  "cv-qualified");
-			return error_mark_node;
-		      }
-		  }
-	      }
+
+	    if (check_decltype_auto (typespec_loc, type))
+	      return error_mark_node;
 
 	    if (ctype == NULL_TREE
 		&& decl_context == FIELD
@@ -13080,6 +13091,15 @@  grokdeclarator (const cp_declarator *declarator,
 
   id_loc = declarator ? declarator->id_loc : input_location;
 
+  if (innermost_code != cdk_function
+    /* Don't check this if it can be the artifical decltype(auto)
+       we created when building a constraint in a compound-requirement:
+       that the type-constraint is plain is going to be checked in
+       cp_parser_compound_requirement.  */
+      && decl_context != TYPENAME
+      && check_decltype_auto (id_loc, type))
+    return error_mark_node;
+
   /* A `constexpr' specifier used in an object declaration declares
      the object as `const'.  */
   if (constexpr_p && innermost_code != cdk_function)
diff --git a/gcc/cp/pt.c b/gcc/cp/pt.c
index 9834baf34db..6d5da17b9d0 100644
--- a/gcc/cp/pt.c
+++ b/gcc/cp/pt.c
@@ -29912,19 +29912,6 @@  do_auto_deduction (tree type, tree init, tree auto_node,
 	return error_mark_node;
       targs = make_tree_vec (1);
       TREE_VEC_ELT (targs, 0) = deduced;
-      /* FIXME: These errors ought to be diagnosed at parse time. */
-      if (type != auto_node)
-	{
-          if (complain & tf_error)
-	    error ("%qT as type rather than plain %<decltype(auto)%>", type);
-	  return error_mark_node;
-	}
-      else if (TYPE_QUALS (type) != TYPE_UNQUALIFIED)
-	{
-	  if (complain & tf_error)
-	    error ("%<decltype(auto)%> cannot be cv-qualified");
-	  return error_mark_node;
-	}
     }
   else
     {
diff --git a/gcc/testsuite/g++.dg/cpp1y/decltype-auto5.C b/gcc/testsuite/g++.dg/cpp1y/decltype-auto5.C
new file mode 100644
index 00000000000..01cc54fb71d
--- /dev/null
+++ b/gcc/testsuite/g++.dg/cpp1y/decltype-auto5.C
@@ -0,0 +1,35 @@ 
+// PR c++/102229
+// { dg-do compile { target c++14 } }
+
+struct S {
+  constexpr static decltype(auto) x = 42;
+  const constexpr static decltype(auto) y = 42; // { dg-error "cannot be cv-qualified" }
+
+  constexpr decltype(auto) mfn1 () { return 0; }
+  const constexpr decltype(auto) mfn2 () { return 0; } // { dg-error "cannot be cv-qualified" }
+};
+
+constexpr decltype(auto) i = 42;
+const constexpr decltype(auto) j = 42; // { dg-error "cannot be cv-qualified" }
+
+constexpr decltype(auto) fn() { return 42; }
+const decltype(auto) fn2() { return 42; } // { dg-error "cannot be cv-qualified" }
+
+auto constexpr foo() -> const decltype(auto) // { dg-error "cannot be cv-qualified" }
+{
+  return 0;
+}
+
+#if __cpp_concepts
+template<typename>
+concept C = true;
+
+constexpr C decltype(auto) x1 = 0;
+const constexpr C decltype(auto) x2 = 0; // { dg-error "cannot be cv-qualified" "" { target c++20 } }
+
+constexpr C decltype(auto) fn3() { return 0; }
+const constexpr C decltype(auto) fn4() { return 0; } // { dg-error "cannot be cv-qualified" "" { target c++20 } }
+#endif
+
+template<const decltype(auto) = 42> // { dg-error "cannot be cv-qualified" }
+void g ();