From: Jakub Jelinek Date: Tue, 7 Apr 2026 16:13:55 +0000 (+0200) Subject: c++: Implement CWG3140 - Allowing expansion over non-constant std::array X-Git-Tag: basepoints/gcc-17~302 X-Git-Url: http://git.ipfire.org/gitweb.cgi?a=commitdiff_plain;h=b2d4d8ddd91c6087420facef74841099b49e91ad;p=thirdparty%2Fgcc.git c++: Implement CWG3140 - Allowing expansion over non-constant std::array 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 * 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 --- diff --git a/gcc/cp/cp-tree.h b/gcc/cp/cp-tree.h index c0822da8283..1ea3319c37b 100644 --- a/gcc/cp/cp-tree.h +++ b/gcc/cp/cp-tree.h @@ -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 &, diff --git a/gcc/cp/parser.cc b/gcc/cp/parser.cc index 3a8d56dc146..d854d0f40c8 100644 --- a/gcc/cp/parser.cc +++ b/gcc/cp/parser.cc @@ -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); diff --git a/gcc/cp/pt.cc b/gcc/cp/pt.cc index 673da9a19b4..d8d9348b7cf 100644 --- a/gcc/cp/pt.cc +++ b/gcc/cp/pt.cc @@ -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); diff --git a/gcc/testsuite/g++.dg/cpp26/expansion-stmt11.C b/gcc/testsuite/g++.dg/cpp26/expansion-stmt11.C index 0156806a4bc..523bec1d191 100644 --- a/gcc/testsuite/g++.dg/cpp26/expansion-stmt11.C +++ b/gcc/testsuite/g++.dg/cpp26/expansion-stmt11.C @@ -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 } } { diff --git a/gcc/testsuite/g++.dg/cpp26/expansion-stmt13.C b/gcc/testsuite/g++.dg/cpp26/expansion-stmt13.C index 087c70754d5..fd950965912 100644 --- a/gcc/testsuite/g++.dg/cpp26/expansion-stmt13.C +++ b/gcc/testsuite/g++.dg/cpp26/expansion-stmt13.C @@ -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; } diff --git a/gcc/testsuite/g++.dg/cpp26/expansion-stmt16.C b/gcc/testsuite/g++.dg/cpp26/expansion-stmt16.C index 7222f8aa1d8..fea141f3e63 100644 --- a/gcc/testsuite/g++.dg/cpp26/expansion-stmt16.C +++ b/gcc/testsuite/g++.dg/cpp26/expansion-stmt16.C @@ -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 } diff --git a/gcc/testsuite/g++.dg/cpp26/expansion-stmt19.C b/gcc/testsuite/g++.dg/cpp26/expansion-stmt19.C index 59983d120cd..0b4b5319779 100644 --- a/gcc/testsuite/g++.dg/cpp26/expansion-stmt19.C +++ b/gcc/testsuite/g++.dg/cpp26/expansion-stmt19.C @@ -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 struct std::tuple_element { 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; } diff --git a/gcc/testsuite/g++.dg/cpp26/expansion-stmt25.C b/gcc/testsuite/g++.dg/cpp26/expansion-stmt25.C index 5152096a232..6f019c89bf8 100644 --- a/gcc/testsuite/g++.dg/cpp26/expansion-stmt25.C +++ b/gcc/testsuite/g++.dg/cpp26/expansion-stmt25.C @@ -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 } } diff --git a/gcc/testsuite/g++.dg/cpp26/expansion-stmt30.C b/gcc/testsuite/g++.dg/cpp26/expansion-stmt30.C index bc9017a13c6..2a72ade5d1e 100644 --- a/gcc/testsuite/g++.dg/cpp26/expansion-stmt30.C +++ b/gcc/testsuite/g++.dg/cpp26/expansion-stmt30.C @@ -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 index 00000000000..a413b151d76 --- /dev/null +++ b/gcc/testsuite/g++.dg/cpp26/expansion-stmt35.C @@ -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 index 00000000000..bab5f3bc4ba --- /dev/null +++ b/gcc/testsuite/g++.dg/cpp26/expansion-stmt36.C @@ -0,0 +1,21 @@ +// CWG3140 - Allowing expansion over non-constant std::array +// { dg-do run { target c++17 } } +// { dg-options "" } + +#include + +std::array +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 index 00000000000..346854ea723 --- /dev/null +++ b/gcc/testsuite/g++.dg/cpp26/expansion-stmt37.C @@ -0,0 +1,19 @@ +// CWG3140 - Allowing expansion over non-constant std::array +// { dg-do compile { target c++17 } } +// { dg-options "" } + +#include + +std::array +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 index 00000000000..3cb00942390 --- /dev/null +++ b/gcc/testsuite/g++.dg/cpp26/expansion-stmt38.C @@ -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 + +struct A { + A () { a++; } + A (const A &) { a++; } + ~A () { a--; } + static int a; +}; +int A::a = 0; + +std::array +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 (); +}