]> git.ipfire.org Git - thirdparty/gcc.git/commitdiff
c++: Implement CWG3140 - Allowing expansion over non-constant std::array
authorJakub Jelinek <jakub@redhat.com>
Tue, 7 Apr 2026 16:13:55 +0000 (18:13 +0200)
committerJakub Jelinek <jakub@gcc.gnu.org>
Tue, 7 Apr 2026 16:13:55 +0000 (18:13 +0200)
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).

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.

Reviewed-by: Jason Merrill <jason@redhat.com>
13 files changed:
gcc/cp/cp-tree.h
gcc/cp/parser.cc
gcc/cp/pt.cc
gcc/testsuite/g++.dg/cpp26/expansion-stmt11.C
gcc/testsuite/g++.dg/cpp26/expansion-stmt13.C
gcc/testsuite/g++.dg/cpp26/expansion-stmt16.C
gcc/testsuite/g++.dg/cpp26/expansion-stmt19.C
gcc/testsuite/g++.dg/cpp26/expansion-stmt25.C
gcc/testsuite/g++.dg/cpp26/expansion-stmt30.C
gcc/testsuite/g++.dg/cpp26/expansion-stmt35.C [new file with mode: 0644]
gcc/testsuite/g++.dg/cpp26/expansion-stmt36.C [new file with mode: 0644]
gcc/testsuite/g++.dg/cpp26/expansion-stmt37.C [new file with mode: 0644]
gcc/testsuite/g++.dg/cpp26/expansion-stmt38.C [new file with mode: 0644]

index c0822da82835bc4505ad539f4cacff46ec572af2..1ea3319c37b898382bf2bf1ec63076d452b07617 100644 (file)
@@ -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 &,
index 3a8d56dc14650ce819709a7acee524ab9d0762d8..d854d0f40c849b8357392a54b426620735274f98 100644 (file)
@@ -15854,16 +15854,17 @@ cp_parser_range_for (cp_parser *parser, tree scope, tree init, tree range_decl,
    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 expr)
 
 /* 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_expr, tree *end_p,
     {
       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, tree range_expr, tree *end_p,
     }
 
   /* 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, tree range_expr, tree *end_p,
                  /*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, tree range_decl, tree range_expr,
   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);
 
index 673da9a19b40692d77e1c49d866b073dc942573d..d8d9348b7cf6272f6b05215c4263bebe22a8bc82 100644 (file)
@@ -33334,34 +33334,52 @@ finish_expansion_stmt (tree expansion_stmt, tree args,
   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];
+         /* The begin-expr and end-expr expressions will be usually wrapped
+            in TARGET_EXPR if they return a class iterator.  The b
+            and e artificial variables need to have cv-unqualified type
+            so that e.g. b can be incremented, so unwrap the TARGET_EXPRs
+            and force TARGET_EXPR with the cv-unqualified type which is
+            a hack replacement for a VAR_DECL in a lambda.  */
+         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);
@@ -33486,13 +33504,18 @@ finish_expansion_stmt (tree expansion_stmt, tree args,
                                 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);
index 0156806a4bc0ce2709f172a39ce031e73c5c8f69..523bec1d191a1279eb028b459f48161798999b0b 100644 (file)
@@ -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 } }
     {
index 087c70754d54ebbfcea5b1bea83ef8b99fbb2ea8..fd9509659127184af8cae14c9a7f10d880a5d8e1 100644 (file)
@@ -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;
 }
 
index 7222f8aa1d8b20fb54b7657b4fc978837bccb8ec..fea141f3e639bafcf70d8a210d1d56e9a379767b 100644 (file)
@@ -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 }
index 59983d120cdf429e3e5380aa8866f4d22676d62d..0b4b5319779b2e3f3aa2facf3ecd6b29f2db9144 100644 (file)
@@ -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_element <I, V> { using type = int; };
 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;
 }
 
index 5152096a232683cb15320f147e0bc7ca687c9b55..6f019c89bf8fe756ccf56fb0bfc4db1ed4945faa 100644 (file)
@@ -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 }
 }
index bc9017a13c63d559f46197b4c608f84982052a9a..2a72ade5d1ea8d4f2bb0aa8729697a36b45bec31 100644 (file)
@@ -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 {})
diff --git a/gcc/testsuite/g++.dg/cpp26/expansion-stmt35.C b/gcc/testsuite/g++.dg/cpp26/expansion-stmt35.C
new file mode 100644 (file)
index 0000000..a413b15
--- /dev/null
@@ -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;
+}
diff --git a/gcc/testsuite/g++.dg/cpp26/expansion-stmt36.C b/gcc/testsuite/g++.dg/cpp26/expansion-stmt36.C
new file mode 100644 (file)
index 0000000..bab5f3b
--- /dev/null
@@ -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 ();
+}
diff --git a/gcc/testsuite/g++.dg/cpp26/expansion-stmt37.C b/gcc/testsuite/g++.dg/cpp26/expansion-stmt37.C
new file mode 100644 (file)
index 0000000..346854e
--- /dev/null
@@ -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 }
+}
diff --git a/gcc/testsuite/g++.dg/cpp26/expansion-stmt38.C b/gcc/testsuite/g++.dg/cpp26/expansion-stmt38.C
new file mode 100644 (file)
index 0000000..3cb0094
--- /dev/null
@@ -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 ();
+}