https://gcc.gnu.org/g:b2d4d8ddd91c6087420facef74841099b49e91ad
commit r16-8502-gb2d4d8ddd91c6087420facef74841099b49e91ad Author: Jakub Jelinek <[email protected]> Date: Tue Apr 7 18:13:55 2026 +0200 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 <[email protected]> * 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 <[email protected]> Diff: --- gcc/cp/cp-tree.h | 4 +- gcc/cp/parser.cc | 64 +++++++++++++++------------ gcc/cp/pt.cc | 59 ++++++++++++++++-------- gcc/testsuite/g++.dg/cpp26/expansion-stmt11.C | 4 +- gcc/testsuite/g++.dg/cpp26/expansion-stmt13.C | 7 +-- gcc/testsuite/g++.dg/cpp26/expansion-stmt16.C | 5 +-- gcc/testsuite/g++.dg/cpp26/expansion-stmt19.C | 6 ++- gcc/testsuite/g++.dg/cpp26/expansion-stmt25.C | 4 +- gcc/testsuite/g++.dg/cpp26/expansion-stmt30.C | 2 +- gcc/testsuite/g++.dg/cpp26/expansion-stmt35.C | 60 +++++++++++++++++++++++++ gcc/testsuite/g++.dg/cpp26/expansion-stmt36.C | 21 +++++++++ gcc/testsuite/g++.dg/cpp26/expansion-stmt37.C | 19 ++++++++ gcc/testsuite/g++.dg/cpp26/expansion-stmt38.C | 36 +++++++++++++++ 13 files changed, 230 insertions(+), 61 deletions(-) diff --git a/gcc/cp/cp-tree.h b/gcc/cp/cp-tree.h index c0822da82835..1ea3319c37b8 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 3a8d56dc1465..d854d0f40c84 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 673da9a19b40..d8d9348b7cf6 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 0156806a4bc0..523bec1d191a 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 087c70754d54..fd9509659127 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 7222f8aa1d8b..fea141f3e639 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 59983d120cdf..0b4b5319779b 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<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; } diff --git a/gcc/testsuite/g++.dg/cpp26/expansion-stmt25.C b/gcc/testsuite/g++.dg/cpp26/expansion-stmt25.C index 5152096a2326..6f019c89bf8f 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 bc9017a13c63..2a72ade5d1ea 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 000000000000..a413b151d761 --- /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 000000000000..bab5f3bc4ba5 --- /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 <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 index 000000000000..346854ea723a --- /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 <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 index 000000000000..3cb00942390e --- /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 <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 (); +}
