c++: Implement CWG3140 - Allowing expansion over non-constant std::array

Message ID adSqX_RgbhkHtK2g@tucnak
State New
Headers
Series c++: Implement CWG3140 - Allowing expansion over non-constant std::array |

Commit Message

Jakub Jelinek April 7, 2026, 6:55 a.m. UTC
  Hi!

The following patch implements CWG3140, which allows non-constexpr
iterating expansion statements over non-constexpr ranges like std::array
where the range size is still known at compile time.
On the other side it breaks iterating expansion statements without constexpr
keyword on range-decl over ranges where to find out the range size the range
needs to be accessed.  In some cases one will have to just add the constexpr
keyword, in other cases even that doesn't have to work (e.g. when the
iterator is constexpr except for operator* or some non-constexpr conversion
operator is involved).

Bootstrapped/regtested on x86_64-linux and i686-linux, ok for trunk?

2026-04-07  Jakub Jelinek  <jakub@redhat.com>

	* cp-tree.h: Implement C++26 CWG3140 - Allowing expansion over
	non-constant std::array.
	(cp_build_range_for_decls): Change last argument from bool to tree.
	(build_range_temp): Likewise and default it to NULL_TREE rather than
	false.
	* parser.cc (build_range_temp): Remove expansion_stmt_p
	argument, add expansion_stmt_decl.  Don't call cp_build_qualified_type
	if expansion_stmt_decl was not declared constexpr. 
	(cp_build_range_for_decls): Remove expansion_stmt_p argument, add
	expansion_stmt_decl.  Pass expansion_stmt_decl to build_range_temp.
	Don't set TREE_STATIC, TREE_PUBLIC, DECL_COMMON, DECL_INTERFACE_KNOWN,
	DECL_DECLARED_CONSTEXPR_P and TREE_READONLY on range_temp if
	expansion_stmt_decl was not declared constexpr.  Don't call
	cp_build_qualified_type on begin type nor set TREE_STATIC etc. on begin
	if expansion_stmt_decl was not declared constexpr.  If
	expansion_stmt_decl is non-NULL, don't build end at all, instead pass
	begin_expr and end_expr in end_p[0] and end_p[1] to the caller.
	(cp_convert_range_for): Adjust cp_build_range_for_decls caller.
	* pt.cc (finish_expansion_stmt): Likewise.  Use begin_expr and end_expr
	instead of begin and end variables from cp_build_range_for_decls,
	make them non-constant and avoid spurious -Wunused-result/nodiscard
	diagnostics.

	* g++.dg/cpp26/expansion-stmt11.C: Expect some extra diagnostics for
	C++11.
	* g++.dg/cpp26/expansion-stmt13.C: Likewise.  Make it dg-do compile
	test for c++11_only and dg-do run only for c++14 and above.
	* g++.dg/cpp26/expansion-stmt16.C: Adjust expected diagnostics.
	* g++.dg/cpp26/expansion-stmt19.C: Expect some extra diagnostics for
	C++11.  Make it dg-do compile test for c++11_only and dg-do run only
	for c++14 and above.
	* g++.dg/cpp26/expansion-stmt25.C (foo): Test both constexpr
	range-for-decl and non-constexpr, adjust expected diagnostics.
	* g++.dg/cpp26/expansion-stmt30.C: Adjust expected diagnostics.
	* g++.dg/cpp26/expansion-stmt35.C: New test.
	* g++.dg/cpp26/expansion-stmt36.C: New test.
	* g++.dg/cpp26/expansion-stmt37.C: New test.
	* g++.dg/cpp26/expansion-stmt38.C: New test.


	Jakub
  

Comments

Jason Merrill April 7, 2026, 3:56 p.m. UTC | #1
On 4/7/26 2:55 AM, Jakub Jelinek wrote:
> Hi!
> 
> The following patch implements CWG3140, which allows non-constexpr
> iterating expansion statements over non-constexpr ranges like std::array
> where the range size is still known at compile time.
> On the other side it breaks iterating expansion statements without constexpr
> keyword on range-decl over ranges where to find out the range size the range
> needs to be accessed.  In some cases one will have to just add the constexpr
> keyword, in other cases even that doesn't have to work (e.g. when the
> iterator is constexpr except for operator* or some non-constexpr conversion
> operator is involved).
> 
> Bootstrapped/regtested on x86_64-linux and i686-linux, ok for trunk?
> 
> 2026-04-07  Jakub Jelinek  <jakub@redhat.com>
> 
> 	* cp-tree.h: Implement C++26 CWG3140 - Allowing expansion over
> 	non-constant std::array.
> 	(cp_build_range_for_decls): Change last argument from bool to tree.
> 	(build_range_temp): Likewise and default it to NULL_TREE rather than
> 	false.
> 	* parser.cc (build_range_temp): Remove expansion_stmt_p
> 	argument, add expansion_stmt_decl.  Don't call cp_build_qualified_type
> 	if expansion_stmt_decl was not declared constexpr.
> 	(cp_build_range_for_decls): Remove expansion_stmt_p argument, add
> 	expansion_stmt_decl.  Pass expansion_stmt_decl to build_range_temp.
> 	Don't set TREE_STATIC, TREE_PUBLIC, DECL_COMMON, DECL_INTERFACE_KNOWN,
> 	DECL_DECLARED_CONSTEXPR_P and TREE_READONLY on range_temp if
> 	expansion_stmt_decl was not declared constexpr.  Don't call
> 	cp_build_qualified_type on begin type nor set TREE_STATIC etc. on begin
> 	if expansion_stmt_decl was not declared constexpr.  If
> 	expansion_stmt_decl is non-NULL, don't build end at all, instead pass
> 	begin_expr and end_expr in end_p[0] and end_p[1] to the caller.
> 	(cp_convert_range_for): Adjust cp_build_range_for_decls caller.
> 	* pt.cc (finish_expansion_stmt): Likewise.  Use begin_expr and end_expr
> 	instead of begin and end variables from cp_build_range_for_decls,
> 	make them non-constant and avoid spurious -Wunused-result/nodiscard
> 	diagnostics.
> 
> 	* g++.dg/cpp26/expansion-stmt11.C: Expect some extra diagnostics for
> 	C++11.
> 	* g++.dg/cpp26/expansion-stmt13.C: Likewise.  Make it dg-do compile
> 	test for c++11_only and dg-do run only for c++14 and above.
> 	* g++.dg/cpp26/expansion-stmt16.C: Adjust expected diagnostics.
> 	* g++.dg/cpp26/expansion-stmt19.C: Expect some extra diagnostics for
> 	C++11.  Make it dg-do compile test for c++11_only and dg-do run only
> 	for c++14 and above.
> 	* g++.dg/cpp26/expansion-stmt25.C (foo): Test both constexpr
> 	range-for-decl and non-constexpr, adjust expected diagnostics.
> 	* g++.dg/cpp26/expansion-stmt30.C: Adjust expected diagnostics.
> 	* g++.dg/cpp26/expansion-stmt35.C: New test.
> 	* g++.dg/cpp26/expansion-stmt36.C: New test.
> 	* g++.dg/cpp26/expansion-stmt37.C: New test.
> 	* g++.dg/cpp26/expansion-stmt38.C: New test.
> 
> --- gcc/cp/cp-tree.h.jj	2026-04-03 20:46:22.845328260 +0200
> +++ gcc/cp/cp-tree.h	2026-04-06 09:59:23.095379300 +0200
> @@ -8028,10 +8028,10 @@ extern tree clone_attrs				(tree);
>   extern bool maybe_clone_body			(tree);
>   
>   /* In parser.cc */
> -extern tree cp_build_range_for_decls (location_t, tree, tree *, bool);
> +extern tree cp_build_range_for_decls (location_t, tree, tree *, tree);
>   extern tree cp_convert_range_for (tree, tree, tree, cp_decomp *, bool,
>   				  tree, bool);
> -extern tree build_range_temp (tree, bool = false);
> +extern tree build_range_temp (tree, tree = NULL_TREE);
>   extern tree cp_perform_range_for_lookup	(tree, tree *, tree *,
>   					 tsubst_flags_t = tf_warning_or_error);
>   extern void cp_convert_omp_range_for (tree &, tree &, tree &,
> --- gcc/cp/parser.cc.jj	2026-04-04 11:32:27.440454554 +0200
> +++ gcc/cp/parser.cc	2026-04-06 09:59:23.100379216 +0200
> @@ -15854,16 +15854,17 @@ cp_parser_range_for (cp_parser *parser,
>      builds up the range temporary.  */
>   
>   tree
> -build_range_temp (tree range_expr, bool expansion_stmt_p /* = false */)
> +build_range_temp (tree range_expr, tree expansion_stmt_decl /* = NULL_TREE */)
>   {
>     tree range_type, auto_node;
>   
> -  if (expansion_stmt_p)
> +  if (expansion_stmt_decl)
>       {
>         /* Build const decltype(auto) __range = range_expr;
>   	 - range_expr provided by the caller already is (range_expr).  */
> -      auto_node = make_decltype_auto ();
> -      range_type = cp_build_qualified_type (auto_node, TYPE_QUAL_CONST);
> +      range_type = auto_node = make_decltype_auto ();
> +      if (DECL_DECLARED_CONSTEXPR_P (expansion_stmt_decl))
> +	range_type = cp_build_qualified_type (auto_node, TYPE_QUAL_CONST);
>       }
>     else
>       {
> @@ -16007,11 +16008,13 @@ warn_for_range_copy (tree decl, tree exp
>   
>   /* Helper function for cp_convert_range_for and finish_expansion_stmt.
>      Build the __range, __begin and __end declarations.  Return the
> -   __begin VAR_DECL, set *END_P to the __end VAR_DECL.  */
> +   __begin VAR_DECL, set *END_P to the __end VAR_DECL.  If
> +   EXPANSION_STMT_DECL, don't create __end and instead store
> +   begin_expr to END_P[0] and end_expr to END_P[1].  */
>   
>   tree
>   cp_build_range_for_decls (location_t loc, tree range_expr, tree *end_p,
> -			  bool expansion_stmt_p)
> +			  tree expansion_stmt_decl)
>   {
>     tree iter_type, begin_expr, end_expr;
>   
> @@ -16023,30 +16026,34 @@ cp_build_range_for_decls (location_t loc
>       {
>         tree range_temp;
>   
> -      if (!expansion_stmt_p
> +      if (!expansion_stmt_decl
>   	  && VAR_P (range_expr)
>   	  && array_of_runtime_bound_p (TREE_TYPE (range_expr)))
>   	/* Can't bind a reference to an array of runtime bound.  */
>   	range_temp = range_expr;
>         else
>   	{
> -	  if (expansion_stmt_p)
> +	  if (expansion_stmt_decl)
>   	    {
>   	      /* Build constexpr decltype(auto) __for_range = (range_expr);  */
>   	      location_t range_loc = cp_expr_loc_or_loc (range_expr, loc);
>   	      range_expr
>   		= finish_parenthesized_expr (cp_expr (range_expr, range_loc));
> -	      range_temp = build_range_temp (range_expr, true);
> +	      range_temp = build_range_temp (range_expr, expansion_stmt_decl);
>   
>   	      /* When P2686R4 is fully implemented, these 3 sets of TREE_STATIC
>   		 (on range_temp, begin and end) should be removed as per
> -		 CWG3044.  */
> -	      TREE_STATIC (range_temp) = 1;
> -	      TREE_PUBLIC (range_temp) = 0;
> -	      DECL_COMMON (range_temp) = 0;
> -	      DECL_INTERFACE_KNOWN (range_temp) = 1;
> -	      DECL_DECLARED_CONSTEXPR_P (range_temp) = 1;
> -	      TREE_READONLY (range_temp) = 1;
> +		 CWG3044.  If expansion_stmt_decl is not constexpr, we don't
> +		 need the static though.  */
> +	      if (DECL_DECLARED_CONSTEXPR_P (expansion_stmt_decl))
> +		{
> +		  TREE_STATIC (range_temp) = 1;
> +		  TREE_PUBLIC (range_temp) = 0;
> +		  DECL_COMMON (range_temp) = 0;
> +		  DECL_INTERFACE_KNOWN (range_temp) = 1;
> +		  DECL_DECLARED_CONSTEXPR_P (range_temp) = 1;
> +		  TREE_READONLY (range_temp) = 1;
> +		}
>   	    }
>   	  else
>   	    /* Build auto &&__for_range = range_expr;  */
> @@ -16063,12 +16070,14 @@ cp_build_range_for_decls (location_t loc
>       }
>   
>     /* The new for initialization statement.  */
> -  if (expansion_stmt_p && !TYPE_REF_P (iter_type))
> +  if (expansion_stmt_decl
> +      && DECL_DECLARED_CONSTEXPR_P (expansion_stmt_decl)
> +      && !TYPE_REF_P (iter_type))
>       iter_type = cp_build_qualified_type (iter_type, TYPE_QUAL_CONST);
>     tree begin = build_decl (loc, VAR_DECL, for_begin__identifier, iter_type);
>     TREE_USED (begin) = 1;
>     DECL_ARTIFICIAL (begin) = 1;
> -  if (expansion_stmt_p)
> +  if (expansion_stmt_decl && DECL_DECLARED_CONSTEXPR_P (expansion_stmt_decl))
>       {
>         TREE_STATIC (begin) = 1;
>         DECL_DECLARED_CONSTEXPR_P (begin) = 1;
> @@ -16079,21 +16088,18 @@ cp_build_range_for_decls (location_t loc
>   		  /*is_constant_init*/false, NULL_TREE,
>   		  LOOKUP_ONLYCONVERTING);
>   
> -  if (cxx_dialect >= cxx17)
> +  if (expansion_stmt_decl)
>       {
> -      iter_type = cv_unqualified (TREE_TYPE (end_expr));
> -      if (expansion_stmt_p && !TYPE_REF_P (iter_type))
> -	iter_type = cp_build_qualified_type (iter_type, TYPE_QUAL_CONST);
> +      end_p[0] = begin_expr;
> +      end_p[1] = end_expr;
> +      return begin;
>       }
> +
> +  if (cxx_dialect >= cxx17)
> +    iter_type = cv_unqualified (TREE_TYPE (end_expr));
>     tree end = build_decl (loc, VAR_DECL, for_end__identifier, iter_type);
>     TREE_USED (end) = 1;
>     DECL_ARTIFICIAL (end) = 1;
> -  if (expansion_stmt_p)
> -    {
> -      TREE_STATIC (end) = 1;
> -      DECL_DECLARED_CONSTEXPR_P (end) = 1;
> -      TREE_READONLY (end) = 1;
> -    }
>     pushdecl (end);
>     cp_finish_decl (end, end_expr,
>   		  /*is_constant_init*/false, NULL_TREE,
> @@ -16149,7 +16155,7 @@ cp_convert_range_for (tree statement, tr
>     if (range_decl == error_mark_node)
>       range_expr = error_mark_node;
>     tree begin
> -    = cp_build_range_for_decls (input_location, range_expr, &end, false);
> +    = cp_build_range_for_decls (input_location, range_expr, &end, NULL_TREE);
>   
>     finish_init_stmt (statement);
>   
> --- gcc/cp/pt.cc.jj	2026-04-04 11:07:32.000119351 +0200
> +++ gcc/cp/pt.cc	2026-04-06 10:59:16.537372085 +0200
> @@ -33329,34 +33329,46 @@ finish_expansion_stmt (tree expansion_st
>     if (kind == esk_iterating)
>       {
>         /* Iterating expansion statements.  */
> -      tree end;
> -      begin = cp_build_range_for_decls (loc, expansion_init, &end, true);
> -      if (!error_operand_p (begin) && !error_operand_p (end))
> +      tree exprs[2];
> +      begin = cp_build_range_for_decls (loc, expansion_init, exprs, range_decl);
> +      if (!error_operand_p (begin)
> +	  && !error_operand_p (exprs[0])
> +	  && !error_operand_p (exprs[1]))
>   	{
>   	  /* In the standard this is all evaluated inside of a consteval
>   	     lambda.  So, force in_immediate_context () around this.  */
>   	  in_consteval_if_p_temp_override icip;
>   	  in_consteval_if_p = true;
> -	  tree i
> -	    = build_target_expr_with_type (begin,
> -					   cv_unqualified (TREE_TYPE (begin)),
> -					   tf_warning_or_error);
> +	  tree b = exprs[0], e = exprs[1];
> +	  if (TREE_CODE (b) == TARGET_EXPR)
> +	    b = TARGET_EXPR_INITIAL (b);
> +	  if (TREE_CODE (e) == TARGET_EXPR)
> +	    e = TARGET_EXPR_INITIAL (e);
> +	  b = force_target_expr (cv_unqualified (TREE_TYPE (b)), b,
> +				 tf_warning_or_error);
> +	  e = force_target_expr (cv_unqualified (TREE_TYPE (e)), e,
> +				 tf_warning_or_error);

Please add a comment about why you strip and re-add TARGET_EXPR.

OK with that addition.

Jason
  

Patch

--- gcc/cp/cp-tree.h.jj	2026-04-03 20:46:22.845328260 +0200
+++ gcc/cp/cp-tree.h	2026-04-06 09:59:23.095379300 +0200
@@ -8028,10 +8028,10 @@  extern tree clone_attrs				(tree);
 extern bool maybe_clone_body			(tree);
 
 /* In parser.cc */
-extern tree cp_build_range_for_decls (location_t, tree, tree *, bool);
+extern tree cp_build_range_for_decls (location_t, tree, tree *, tree);
 extern tree cp_convert_range_for (tree, tree, tree, cp_decomp *, bool,
 				  tree, bool);
-extern tree build_range_temp (tree, bool = false);
+extern tree build_range_temp (tree, tree = NULL_TREE);
 extern tree cp_perform_range_for_lookup	(tree, tree *, tree *,
 					 tsubst_flags_t = tf_warning_or_error);
 extern void cp_convert_omp_range_for (tree &, tree &, tree &,
--- gcc/cp/parser.cc.jj	2026-04-04 11:32:27.440454554 +0200
+++ gcc/cp/parser.cc	2026-04-06 09:59:23.100379216 +0200
@@ -15854,16 +15854,17 @@  cp_parser_range_for (cp_parser *parser,
    builds up the range temporary.  */
 
 tree
-build_range_temp (tree range_expr, bool expansion_stmt_p /* = false */)
+build_range_temp (tree range_expr, tree expansion_stmt_decl /* = NULL_TREE */)
 {
   tree range_type, auto_node;
 
-  if (expansion_stmt_p)
+  if (expansion_stmt_decl)
     {
       /* Build const decltype(auto) __range = range_expr;
 	 - range_expr provided by the caller already is (range_expr).  */
-      auto_node = make_decltype_auto ();
-      range_type = cp_build_qualified_type (auto_node, TYPE_QUAL_CONST);
+      range_type = auto_node = make_decltype_auto ();
+      if (DECL_DECLARED_CONSTEXPR_P (expansion_stmt_decl))
+	range_type = cp_build_qualified_type (auto_node, TYPE_QUAL_CONST);
     }
   else
     {
@@ -16007,11 +16008,13 @@  warn_for_range_copy (tree decl, tree exp
 
 /* Helper function for cp_convert_range_for and finish_expansion_stmt.
    Build the __range, __begin and __end declarations.  Return the
-   __begin VAR_DECL, set *END_P to the __end VAR_DECL.  */
+   __begin VAR_DECL, set *END_P to the __end VAR_DECL.  If
+   EXPANSION_STMT_DECL, don't create __end and instead store
+   begin_expr to END_P[0] and end_expr to END_P[1].  */
 
 tree
 cp_build_range_for_decls (location_t loc, tree range_expr, tree *end_p,
-			  bool expansion_stmt_p)
+			  tree expansion_stmt_decl)
 {
   tree iter_type, begin_expr, end_expr;
 
@@ -16023,30 +16026,34 @@  cp_build_range_for_decls (location_t loc
     {
       tree range_temp;
 
-      if (!expansion_stmt_p
+      if (!expansion_stmt_decl
 	  && VAR_P (range_expr)
 	  && array_of_runtime_bound_p (TREE_TYPE (range_expr)))
 	/* Can't bind a reference to an array of runtime bound.  */
 	range_temp = range_expr;
       else
 	{
-	  if (expansion_stmt_p)
+	  if (expansion_stmt_decl)
 	    {
 	      /* Build constexpr decltype(auto) __for_range = (range_expr);  */
 	      location_t range_loc = cp_expr_loc_or_loc (range_expr, loc);
 	      range_expr
 		= finish_parenthesized_expr (cp_expr (range_expr, range_loc));
-	      range_temp = build_range_temp (range_expr, true);
+	      range_temp = build_range_temp (range_expr, expansion_stmt_decl);
 
 	      /* When P2686R4 is fully implemented, these 3 sets of TREE_STATIC
 		 (on range_temp, begin and end) should be removed as per
-		 CWG3044.  */
-	      TREE_STATIC (range_temp) = 1;
-	      TREE_PUBLIC (range_temp) = 0;
-	      DECL_COMMON (range_temp) = 0;
-	      DECL_INTERFACE_KNOWN (range_temp) = 1;
-	      DECL_DECLARED_CONSTEXPR_P (range_temp) = 1;
-	      TREE_READONLY (range_temp) = 1;
+		 CWG3044.  If expansion_stmt_decl is not constexpr, we don't
+		 need the static though.  */
+	      if (DECL_DECLARED_CONSTEXPR_P (expansion_stmt_decl))
+		{
+		  TREE_STATIC (range_temp) = 1;
+		  TREE_PUBLIC (range_temp) = 0;
+		  DECL_COMMON (range_temp) = 0;
+		  DECL_INTERFACE_KNOWN (range_temp) = 1;
+		  DECL_DECLARED_CONSTEXPR_P (range_temp) = 1;
+		  TREE_READONLY (range_temp) = 1;
+		}
 	    }
 	  else
 	    /* Build auto &&__for_range = range_expr;  */
@@ -16063,12 +16070,14 @@  cp_build_range_for_decls (location_t loc
     }
 
   /* The new for initialization statement.  */
-  if (expansion_stmt_p && !TYPE_REF_P (iter_type))
+  if (expansion_stmt_decl
+      && DECL_DECLARED_CONSTEXPR_P (expansion_stmt_decl)
+      && !TYPE_REF_P (iter_type))
     iter_type = cp_build_qualified_type (iter_type, TYPE_QUAL_CONST);
   tree begin = build_decl (loc, VAR_DECL, for_begin__identifier, iter_type);
   TREE_USED (begin) = 1;
   DECL_ARTIFICIAL (begin) = 1;
-  if (expansion_stmt_p)
+  if (expansion_stmt_decl && DECL_DECLARED_CONSTEXPR_P (expansion_stmt_decl))
     {
       TREE_STATIC (begin) = 1;
       DECL_DECLARED_CONSTEXPR_P (begin) = 1;
@@ -16079,21 +16088,18 @@  cp_build_range_for_decls (location_t loc
 		  /*is_constant_init*/false, NULL_TREE,
 		  LOOKUP_ONLYCONVERTING);
 
-  if (cxx_dialect >= cxx17)
+  if (expansion_stmt_decl)
     {
-      iter_type = cv_unqualified (TREE_TYPE (end_expr));
-      if (expansion_stmt_p && !TYPE_REF_P (iter_type))
-	iter_type = cp_build_qualified_type (iter_type, TYPE_QUAL_CONST);
+      end_p[0] = begin_expr;
+      end_p[1] = end_expr;
+      return begin;
     }
+
+  if (cxx_dialect >= cxx17)
+    iter_type = cv_unqualified (TREE_TYPE (end_expr));
   tree end = build_decl (loc, VAR_DECL, for_end__identifier, iter_type);
   TREE_USED (end) = 1;
   DECL_ARTIFICIAL (end) = 1;
-  if (expansion_stmt_p)
-    {
-      TREE_STATIC (end) = 1;
-      DECL_DECLARED_CONSTEXPR_P (end) = 1;
-      TREE_READONLY (end) = 1;
-    }
   pushdecl (end);
   cp_finish_decl (end, end_expr,
 		  /*is_constant_init*/false, NULL_TREE,
@@ -16149,7 +16155,7 @@  cp_convert_range_for (tree statement, tr
   if (range_decl == error_mark_node)
     range_expr = error_mark_node;
   tree begin
-    = cp_build_range_for_decls (input_location, range_expr, &end, false);
+    = cp_build_range_for_decls (input_location, range_expr, &end, NULL_TREE);
 
   finish_init_stmt (statement);
 
--- gcc/cp/pt.cc.jj	2026-04-04 11:07:32.000119351 +0200
+++ gcc/cp/pt.cc	2026-04-06 10:59:16.537372085 +0200
@@ -33329,34 +33329,46 @@  finish_expansion_stmt (tree expansion_st
   if (kind == esk_iterating)
     {
       /* Iterating expansion statements.  */
-      tree end;
-      begin = cp_build_range_for_decls (loc, expansion_init, &end, true);
-      if (!error_operand_p (begin) && !error_operand_p (end))
+      tree exprs[2];
+      begin = cp_build_range_for_decls (loc, expansion_init, exprs, range_decl);
+      if (!error_operand_p (begin)
+	  && !error_operand_p (exprs[0])
+	  && !error_operand_p (exprs[1]))
 	{
 	  /* In the standard this is all evaluated inside of a consteval
 	     lambda.  So, force in_immediate_context () around this.  */
 	  in_consteval_if_p_temp_override icip;
 	  in_consteval_if_p = true;
-	  tree i
-	    = build_target_expr_with_type (begin,
-					   cv_unqualified (TREE_TYPE (begin)),
-					   tf_warning_or_error);
+	  tree b = exprs[0], e = exprs[1];
+	  if (TREE_CODE (b) == TARGET_EXPR)
+	    b = TARGET_EXPR_INITIAL (b);
+	  if (TREE_CODE (e) == TARGET_EXPR)
+	    e = TARGET_EXPR_INITIAL (e);
+	  b = force_target_expr (cv_unqualified (TREE_TYPE (b)), b,
+				 tf_warning_or_error);
+	  e = force_target_expr (cv_unqualified (TREE_TYPE (e)), e,
+				 tf_warning_or_error);
 	  tree w = build_stmt (loc, WHILE_STMT, NULL_TREE, NULL_TREE,
 			       NULL_TREE, NULL_TREE, NULL_TREE);
 	  tree r = get_target_expr (build_zero_cst (ptrdiff_type_node));
-	  tree iinc = build_x_unary_op (loc, PREINCREMENT_EXPR,
-					TARGET_EXPR_SLOT (i), NULL_TREE,
+	  tree binc = build_x_unary_op (loc, PREINCREMENT_EXPR,
+					TARGET_EXPR_SLOT (b), NULL_TREE,
 					tf_warning_or_error);
 	  tree rinc = build2 (PREINCREMENT_EXPR, ptrdiff_type_node,
 			      TARGET_EXPR_SLOT (r),
 			      build_int_cst (ptrdiff_type_node, 1));
-	  WHILE_BODY (w) = build_compound_expr (loc, iinc, rinc);
-	  WHILE_COND (w) = build_x_binary_op (loc, NE_EXPR, i, ERROR_MARK,
-					      end, ERROR_MARK, NULL_TREE, NULL,
+	  WHILE_BODY (w) = build_compound_expr (loc, binc, rinc);
+	  WHILE_COND (w) = build_x_binary_op (loc, NE_EXPR, b, ERROR_MARK,
+					      e, ERROR_MARK, NULL_TREE, NULL,
 					      tf_warning_or_error);
-	  tree e = build_compound_expr (loc, r, i);
-	  e = build_compound_expr (loc, e, w);
-	  e = build_compound_expr (loc, e, TARGET_EXPR_SLOT (r));
+	  {
+	    warning_sentinel wur (warn_unused_result);
+	    e = build_compound_expr (loc, b, e);
+	    e = build_compound_expr (loc, r, e);
+	    e = build_compound_expr (loc, e, w);
+	    e = build_compound_expr (loc, e, TARGET_EXPR_SLOT (r));
+	  }
+	  e = fold_build_cleanup_point_expr (TREE_TYPE (e), e);
 	  e = cxx_constant_value (e);
 	  if (tree_fits_uhwi_p (e))
 	    n = tree_to_uhwi (e);
@@ -33481,13 +33493,18 @@  finish_expansion_stmt (tree expansion_st
 				 tf_warning_or_error);
 	  auto_node = make_auto ();
 	  iter_type = do_auto_deduction (auto_node, iter_init, auto_node);
-	  if (!TYPE_REF_P (iter_type))
+	  if (DECL_DECLARED_CONSTEXPR_P (range_decl)
+	      && !TYPE_REF_P (iter_type))
 	    iter_type = cp_build_qualified_type (iter_type, TYPE_QUAL_CONST);
 	  iter = build_decl (loc, VAR_DECL, NULL_TREE, iter_type);
 	  TREE_USED (iter) = 1;
 	  DECL_ARTIFICIAL (iter) = 1;
-	  TREE_STATIC (iter) = 1;
-	  DECL_DECLARED_CONSTEXPR_P (iter) = 1;
+	  if (DECL_DECLARED_CONSTEXPR_P (range_decl))
+	    {
+	      TREE_STATIC (iter) = 1;
+	      DECL_DECLARED_CONSTEXPR_P (iter) = 1;
+	      TREE_READONLY (iter) = 1;
+	    }
 	  pushdecl (iter);
 	  cp_finish_decl (iter, iter_init, /*is_constant_init*/false,
 			  NULL_TREE, LOOKUP_ONLYCONVERTING);
--- gcc/testsuite/g++.dg/cpp26/expansion-stmt11.C.jj	2026-03-27 10:17:15.653305952 +0100
+++ gcc/testsuite/g++.dg/cpp26/expansion-stmt11.C	2026-04-06 11:10:33.407077674 +0200
@@ -9,7 +9,7 @@  struct T { using type = T; int s; };
 T d = { 8 };
 struct U {
   constexpr const S *begin () const { return &c[0]; }
-  constexpr const S *end () const { return &c[s]; }
+  constexpr const S *end () const { return &c[s]; }	// { dg-error "is not usable in a constant expression" "" { target c++11_down } }
   int s;
 };
 struct V { int a; long b; double c; };
@@ -29,7 +29,7 @@  foo ()
   template for (auto g : u)		// { dg-warning "'template for' only available with" "" { target c++23_down } }
     {
       decltype(g)::type h = g;
-    }
+    }					// { dg-message "was not declared 'constexpr'" "" { target c++11_down } }
   V v = { 9, 10L, 11.0 };
   template for (auto g : v)		// { dg-warning "'template for' only available with" "" { target c++23_down } }
     {
--- gcc/testsuite/g++.dg/cpp26/expansion-stmt13.C.jj	2026-03-27 10:17:15.653305952 +0100
+++ gcc/testsuite/g++.dg/cpp26/expansion-stmt13.C	2026-04-07 08:21:54.478390430 +0200
@@ -1,5 +1,6 @@ 
 // C++26 P1306R5 - Expansion statements
-// { dg-do run { target c++11 } }
+// { dg-do run { target c++14 } }
+// { dg-do compile { target c++11_only } }
 // { dg-options "" }
 
 namespace std {
@@ -11,7 +12,7 @@  struct S { int s; };
 constexpr S c[] = { { 3 }, { 4 }, { 5 }, { 6 }, { 7 } };
 struct U {
   constexpr const S *begin () const { return &c[0]; }
-  constexpr const S *end () const { return &c[s]; }
+  constexpr const S *end () const { return &c[s]; }	// { dg-error "is not usable in a constant expression" "" { target c++11_down } }
   int s;
 };
 constexpr U u1 = { 3 }, u2 = { 0 };
@@ -45,7 +46,7 @@  foo ()
   template for (auto h = 2; constexpr auto g : u1)	// { dg-warning "'template for' only available with" "" { target c++23_down } }
     r += g.s + h;
   template for (long long h = ++r; auto g : u2)		// { dg-warning "'template for' only available with" "" { target c++23_down } }
-    __builtin_abort ();
+    __builtin_abort ();					// { dg-message "was not declared 'constexpr'" "" { target c++11_down } }
   return r;
 }
 
--- gcc/testsuite/g++.dg/cpp26/expansion-stmt16.C.jj	2026-03-27 10:17:15.653305952 +0100
+++ gcc/testsuite/g++.dg/cpp26/expansion-stmt16.C	2026-04-06 11:18:26.642182557 +0200
@@ -49,12 +49,11 @@  foo ()
   template for (constexpr auto g : d)	// { dg-warning "'template for' only available with" "" { target c++23_down } }
     ;					// { dg-error "'d' is not a constant expression" "" { target c++14 } .-1 }
 					// { dg-error "call to non-'constexpr' function 'const A\\\* C::begin\\\(\\\) const'" "" { target c++11_down } .-1 }
-					// { dg-error "call to non-'constexpr' function 'const A\\\* C::end\\\(\\\) const'" "" { target c++11_down } .-2 }
-					// { dg-error "the type 'const C' of 'constexpr' variable '__for_range ' is not literal" "" { target c++11_down } .-3 }
+					// { dg-error "the type 'const C' of 'constexpr' variable '__for_range ' is not literal" "" { target c++11_down } .-2 }
   constexpr D e = { 3 };
   template for (constexpr auto g : e)	// { dg-warning "'template for' only available with" "" { target c++23_down } }
     ;					// { dg-error "'e' is not a constant expression" "" { target c++14 } .-1 }
-					// { dg-error "call to non-'constexpr' function 'const A\\\* D::end\\\(\\\) const'" "" { target *-*-* } .-1 }
+					// { dg-error "call to non-'constexpr' function 'const A\\\* D::end\\\(\\\) const'" "" { target c++11_down } .-1 }
   constexpr E f = { 3 };
   template for (constexpr auto g : f)	// { dg-warning "'template for' only available with" "" { target c++23_down } }
     ;					// { dg-error "'f' is not a constant expression" "" { target c++14 } .-1 }
--- gcc/testsuite/g++.dg/cpp26/expansion-stmt19.C.jj	2026-03-27 10:17:15.653305952 +0100
+++ gcc/testsuite/g++.dg/cpp26/expansion-stmt19.C	2026-04-07 08:22:10.880110548 +0200
@@ -1,5 +1,6 @@ 
 // C++26 P1306R5 - Expansion statements
-// { dg-do run { target c++11 } }
+// { dg-do run { target c++14 } }
+// { dg-do compile { target c++11_only } }
 // { dg-options "" }
 
 namespace std {
@@ -18,7 +19,7 @@  template<int I> struct std::tuple_elemen
 constexpr V c[] = { { 3, 4 }, { 4, 5 }, { 5, 6 }, { 6, 7 }, { 7, 8 } };
 struct U {
   constexpr const V *begin () const { return &c[0]; }
-  constexpr const V *end () const { return &c[s]; }
+  constexpr const V *end () const { return &c[s]; }	// { dg-error "is not usable in a constant expression" "" { target c++11_down } }
   int s;
 };
 constexpr U u1 = { 3 }, u2 = { 0 };
@@ -51,6 +52,7 @@  foo ()
   template for (long long h = ++r; auto [...i, j] : u2)	// { dg-warning "'template for' only available with" "" { target c++23_down } }
     __builtin_abort ();					// { dg-warning "structured bindings only available with" "" { target c++14_down } .-1 }
 							// { dg-warning "structured binding packs only available with" "" { target { c++17 && c++23_down } } .-2 }
+							// { dg-message "was not declared 'constexpr'" "" { target c++11_down } .-3 }
   return r;
 }
 
--- gcc/testsuite/g++.dg/cpp26/expansion-stmt25.C.jj	2026-04-01 19:09:34.867401760 +0200
+++ gcc/testsuite/g++.dg/cpp26/expansion-stmt25.C	2026-04-06 09:59:23.103559220 +0200
@@ -22,6 +22,8 @@  namespace N
 void
 foo ()
 {
-  template for (auto i : N::B {})				// { dg-warning "'template for' only available with" "" { target c++23_down } }
+  template for (constexpr auto i : N::B {})			// { dg-warning "'template for' only available with" "" { target c++23_down } }
     ;								// { dg-error "no match for 'operator-' in '__for_begin  - __for_begin ' \\\(operand types are 'const A' and 'const A'\\\)" "" { target *-*-* } .-1 }
+  template for (auto i : N::B {})				// { dg-warning "'template for' only available with" "" { target c++23_down } }
+    ;								// { dg-error "no match for 'operator-' in '__for_begin  - __for_begin ' \\\(operand types are 'A' and 'A'\\\)" "" { target *-*-* } .-1 }
 }
--- gcc/testsuite/g++.dg/cpp26/expansion-stmt30.C.jj	2026-03-27 10:17:15.654305935 +0100
+++ gcc/testsuite/g++.dg/cpp26/expansion-stmt30.C	2026-04-06 11:12:43.266911186 +0200
@@ -30,7 +30,7 @@  foo ()
   for (auto i : N::S {})		// { dg-error "call of overloaded 'begin\\\(N::S\\\&\\\)' is ambiguous" }
     ;
   template for (auto i : N::S {})
-    ;					// { dg-error "call of overloaded 'begin\\\(const N::S\\\&\\\)' is ambiguous" }
+    ;					// { dg-error "call of overloaded 'begin\\\(N::S\\\&\\\)' is ambiguous" }
   template for (auto i : V {})
     ;					// { dg-error "cannot be used as a function" }
   template for (auto i : O::B {})
--- gcc/testsuite/g++.dg/cpp26/expansion-stmt35.C.jj	2026-04-06 11:37:50.160784900 +0200
+++ gcc/testsuite/g++.dg/cpp26/expansion-stmt35.C	2026-04-06 11:53:22.034260816 +0200
@@ -0,0 +1,60 @@ 
+// CWG3140 - Allowing expansion over non-constant std::array
+// { dg-do compile { target c++14 } }
+// { dg-options "" }
+
+struct A
+{
+  int x;
+  constexpr explicit A (int v) : x(v) {}
+  constexpr A &operator ++ () { ++x; return *this; }
+  constexpr int operator * () const { return x; }
+  constexpr bool operator != (const A &o) const { return x != o.x; }
+  constexpr A operator + (int o) const { A r (x + o); return r; }
+  constexpr int operator - (const A &o) const { return x - o.x; }
+};
+
+namespace N
+{
+  struct B { constexpr B (int n) : b (n) {} int b; };
+  constexpr A begin (const B &) { return A (0); }
+  constexpr A end (const B &x) { return A (x.b); }	// { dg-error "is not usable in a constant expression" }
+  struct C { };
+  constexpr A begin (const C &) { return A (0); }
+  constexpr A end (const C &x) { return A (6); }
+}
+
+int
+foo ()
+{
+  int r = 0;
+  template for (constexpr auto m : N::B (3))	// { dg-warning "'template for' only available with" "" { target c++23_down } }
+    r += m;
+  return r;
+}
+
+int
+bar ()
+{
+  int r = 0;
+  template for (auto m : N::B (3))		// { dg-warning "'template for' only available with" "" { target c++23_down } }
+    r += m;					// { dg-message "was not declared 'constexpr'" }
+  return r;
+}
+
+int
+baz ()
+{
+  int r = 0;
+  template for (constexpr auto m : N::C ())	// { dg-warning "'template for' only available with" "" { target c++23_down } }
+    r += m;
+  return r;
+}
+
+int
+qux ()
+{
+  int r = 0;
+  template for (auto m : N::C ())		// { dg-warning "'template for' only available with" "" { target c++23_down } }
+    r += m;
+  return r;
+}
--- gcc/testsuite/g++.dg/cpp26/expansion-stmt36.C.jj	2026-04-06 11:37:57.111669031 +0200
+++ gcc/testsuite/g++.dg/cpp26/expansion-stmt36.C	2026-04-06 11:55:13.017411979 +0200
@@ -0,0 +1,21 @@ 
+// CWG3140 - Allowing expansion over non-constant std::array
+// { dg-do run { target c++17 } }
+// { dg-options "" }
+
+#include <array>
+
+std::array <int, 5>
+foo ()
+{
+  return { 1, 2, 3, 4, 5 };
+}
+
+int
+main ()
+{
+  int r = 0;
+  template for (auto n : foo ())	// { dg-warning "'template for' only available with" "" { target c++23_down } }
+    r += n;
+  if (r != 15)
+    __builtin_abort ();
+}
--- gcc/testsuite/g++.dg/cpp26/expansion-stmt37.C.jj	2026-04-06 11:39:45.982855213 +0200
+++ gcc/testsuite/g++.dg/cpp26/expansion-stmt37.C	2026-04-06 12:00:39.720970271 +0200
@@ -0,0 +1,19 @@ 
+// CWG3140 - Allowing expansion over non-constant std::array
+// { dg-do compile { target c++17 } }
+// { dg-options "" }
+
+#include <array>
+
+std::array <int, 5>
+foo ()
+{
+  return { 1, 2, 3, 4, 5 };
+}
+
+void
+bar ()
+{
+  int r = 0;
+  template for (constexpr auto n : foo ())	// { dg-warning "'template for' only available with" "" { target c++23_down } }
+    r += n;					// { dg-error " call to non-'constexpr' function" "" { target *-*-* } .-1 }
+}
--- gcc/testsuite/g++.dg/cpp26/expansion-stmt38.C.jj	2026-04-06 11:40:59.562629467 +0200
+++ gcc/testsuite/g++.dg/cpp26/expansion-stmt38.C	2026-04-06 11:59:16.769351934 +0200
@@ -0,0 +1,36 @@ 
+// CWG3140 - Allowing expansion over non-constant std::array
+// { dg-do run { target c++17 } }
+// { dg-options "" }
+// { dg-additional-options "-frange-for-ext-temps" { target c++20_down } }
+
+#include <array>
+
+struct A {
+  A () { a++; }
+  A (const A &) { a++; }
+  ~A () { a--; }
+  static int a;
+};
+int A::a = 0;
+
+std::array <int, 5>
+foo (A, A, A)
+{
+  return { 1, 2, 3, 4, 5 };
+}
+
+int
+main ()
+{
+  int r = 0;
+  if (A::a != 0)
+    __builtin_abort ();
+  template for (auto n : foo (A (), A (), A ()))	// { dg-warning "'template for' only available with" "" { target c++23_down } }
+    {
+      if (A::a != 3)
+	__builtin_abort ();
+      r += n;
+    }
+  if (r != 15 || A::a != 0)
+    __builtin_abort ();
+}