https://gcc.gnu.org/g:ef8af34e0d173723a607789cf7cabc61366babbf
commit r16-7132-gef8af34e0d173723a607789cf7cabc61366babbf Author: Jakub Jelinek <[email protected]> Date: Thu Jan 29 09:49:51 2026 +0100 c++: Implement CWG3131 - Value categories and types for the range in iterable expansion statements For the https://gcc.gnu.org/pipermail/gcc/2025-November/246977.html issues I've filed https://github.com/cplusplus/CWG/issues/805 which resulted in two CWG issues, https://cplusplus.github.io/CWG/issues/3131.html and https://cplusplus.github.io/CWG/issues/3140.html This patch implements the former, changing the iterating expansion statement http://eel.is/c++draft/stmt.expand#5.2 line from constexpr auto&& range = expansion-initializer; to constexpr decltype(auto) range = (expansion-initializer); (for our partly pre-CWG3044 implementation with static before it). 2026-01-29 Jakub Jelinek <[email protected]> * cp-tree.h (build_range_temp): Implement CWG3131 - Value categories and types for the range in iterable expansion statements. Add bool argument defaulted to false. * parser.cc (build_range_temp): Add expansion_stmt_p argument, if true build const decltype(auto) __for_range = range_expr instead of auto &&__for_range = range_expr. (cp_build_range_for_decls): For expansion stmts build __for_range as static constexpr decltype(auto) __for_range = (range_expr); rather than static constexpr auto &&__for_range = range_expr;. * g++.dg/cpp26/expansion-stmt1.C (N::begin, N::end, O::begin, O::end): Change argument type from B & to const B & or from D & to const D &. * g++.dg/cpp26/expansion-stmt2.C (N::begin, N::end, O::begin, O::end): Likewise. * g++.dg/cpp26/expansion-stmt3.C (N::begin, N::end, O::begin, O::end): Likewise. * g++.dg/cpp26/expansion-stmt16.C: Expect different diagnostics for C++11. * g++.dg/cpp26/expansion-stmt18.C (N::begin, N::end): Change argument type from B & to const B &. * g++.dg/cpp26/expansion-stmt25.C (N::begin, N::end): Likewise. * g++.dg/cpp26/expansion-stmt26.C: New test. * g++.dg/reflect/p3491-2.C (baz): Move workaround to a new function garply, use the previously #if 0 guarded implementation. (garply): New function. Diff: --- gcc/cp/cp-tree.h | 2 +- gcc/cp/parser.cc | 40 ++++++++++++++++++++------- gcc/testsuite/g++.dg/cpp26/expansion-stmt1.C | 8 +++--- gcc/testsuite/g++.dg/cpp26/expansion-stmt16.C | 13 +++++---- gcc/testsuite/g++.dg/cpp26/expansion-stmt18.C | 4 +-- gcc/testsuite/g++.dg/cpp26/expansion-stmt2.C | 8 +++--- gcc/testsuite/g++.dg/cpp26/expansion-stmt25.C | 4 +-- gcc/testsuite/g++.dg/cpp26/expansion-stmt26.C | 18 ++++++++++++ gcc/testsuite/g++.dg/cpp26/expansion-stmt3.C | 8 +++--- gcc/testsuite/g++.dg/reflect/p3491-2.C | 19 +++++++------ 10 files changed, 83 insertions(+), 41 deletions(-) diff --git a/gcc/cp/cp-tree.h b/gcc/cp/cp-tree.h index 19a426bfc2c0..006ce23960c7 100644 --- a/gcc/cp/cp-tree.h +++ b/gcc/cp/cp-tree.h @@ -7995,7 +7995,7 @@ extern bool maybe_clone_body (tree); extern tree cp_build_range_for_decls (location_t, tree, tree *, bool); extern tree cp_convert_range_for (tree, tree, tree, cp_decomp *, bool, tree, bool); -extern tree build_range_temp (tree); +extern tree build_range_temp (tree, bool = false); 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 2891856098c0..392d1f90ac81 100644 --- a/gcc/cp/parser.cc +++ b/gcc/cp/parser.cc @@ -15855,12 +15855,24 @@ 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) +build_range_temp (tree range_expr, bool expansion_stmt_p /* = false */) { - /* Find out the type deduced by the declaration - `auto &&__range = range_expr'. */ - tree auto_node = make_auto (); - tree range_type = cp_build_reference_type (auto_node, true); + tree range_type, auto_node; + + if (expansion_stmt_p) + { + /* 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); + } + else + { + /* Find out the type deduced by the declaration + `auto &&__range = range_expr'. */ + auto_node = make_auto (); + range_type = cp_build_reference_type (auto_node, true); + } range_type = do_auto_deduction (range_type, range_expr, auto_node); /* Create the __range variable. */ @@ -16019,13 +16031,17 @@ cp_build_range_for_decls (location_t loc, tree range_expr, tree *end_p, range_temp = range_expr; else { - range_temp = build_range_temp (range_expr); if (expansion_stmt_p) { - /* Depending on CWG3044 resolution, we might want to remove - these 3 sets of TREE_STATIC (on range_temp, begin and end). - Although it can only be done when P2686R4 is fully - implemented. */ + /* 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); + + /* 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; @@ -16033,6 +16049,10 @@ cp_build_range_for_decls (location_t loc, tree range_expr, tree *end_p, DECL_DECLARED_CONSTEXPR_P (range_temp) = 1; TREE_READONLY (range_temp) = 1; } + else + /* Build auto &&__for_range = range_expr; */ + range_temp = build_range_temp (range_expr); + pushdecl (range_temp); cp_finish_decl (range_temp, range_expr, /*is_constant_init*/false, NULL_TREE, diff --git a/gcc/testsuite/g++.dg/cpp26/expansion-stmt1.C b/gcc/testsuite/g++.dg/cpp26/expansion-stmt1.C index 7cc01f1d97f3..9d6f3e64cadc 100644 --- a/gcc/testsuite/g++.dg/cpp26/expansion-stmt1.C +++ b/gcc/testsuite/g++.dg/cpp26/expansion-stmt1.C @@ -40,15 +40,15 @@ struct C namespace N { struct B { constexpr B () {} }; - constexpr A begin (B &) { return A (0); } - constexpr A end (B &) { return A (6); } + constexpr A begin (const B &) { return A (0); } + constexpr A end (const B &) { return A (6); } } namespace O { struct D { constexpr D () {} }; - constexpr C begin (D &) { return C (0, 42, 5); } - constexpr C end (D &) { return C (6, 36, 11); } + constexpr C begin (const D &) { return C (0, 42, 5); } + constexpr C end (const D &) { return C (6, 36, 11); } } long long diff --git a/gcc/testsuite/g++.dg/cpp26/expansion-stmt16.C b/gcc/testsuite/g++.dg/cpp26/expansion-stmt16.C index b573d9d4b5ec..7222f8aa1d8b 100644 --- a/gcc/testsuite/g++.dg/cpp26/expansion-stmt16.C +++ b/gcc/testsuite/g++.dg/cpp26/expansion-stmt16.C @@ -43,23 +43,26 @@ foo () { B c = { 3 }; template for (constexpr auto g : c) // { dg-warning "'template for' only available with" "" { target c++23_down } } - ; // { dg-error "'c' is not a constant expression" "" { target *-*-* } .-1 } + ; // { dg-error "'c' is not a constant expression" "" { target c++14 } .-1 } + // { dg-error "the value of 'c' is not usable in a constant expression" "" { target c++11_down } .-1 } C d = { 3 }; 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 *-*-* } .-1 } + ; // { 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 } 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 *-*-* } .-1 } + ; // { 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 } 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 *-*-* } .-1 } + ; // { dg-error "'f' is not a constant expression" "" { target c++14 } .-1 } // { dg-error "call to non-'constexpr' function 'const A\\\* E::begin\\\(\\\) const'" "" { target *-*-* } .-1 } constexpr G h = { 3 }; template for (constexpr auto g : h) // { dg-warning "'template for' only available with" "" { target c++23_down } } - ; // { dg-error "'h' is not a constant expression" "" { target *-*-* } .-1 } + ; // { dg-error "'h' is not a constant expression" "" { target c++14 } .-1 } + // { dg-error "the type 'const F' of 'constexpr' variable 'g' is not literal" "" { target c++11_down } .-2 } template for (constexpr auto g : { 1, 2, F { 3 }, 4L }) // { dg-warning "'template for' only available with" "" { target c++23_down } } ; // { dg-error "the type 'const F' of 'constexpr' variable 'g' is not literal" "" { target *-*-* } .-1 } template for (constexpr auto g : H {})// { dg-warning "'template for' only available with" "" { target c++23_down } } diff --git a/gcc/testsuite/g++.dg/cpp26/expansion-stmt18.C b/gcc/testsuite/g++.dg/cpp26/expansion-stmt18.C index c49c5f2f9ff8..a4dbdf392158 100644 --- a/gcc/testsuite/g++.dg/cpp26/expansion-stmt18.C +++ b/gcc/testsuite/g++.dg/cpp26/expansion-stmt18.C @@ -17,8 +17,8 @@ struct A namespace N { struct B { constexpr B () {} }; - constexpr A begin (B &) { return A (0); } - constexpr A end (B &) { return A (6); } + constexpr A begin (const B &) { return A (0); } + constexpr A end (const B &) { return A (6); } } void diff --git a/gcc/testsuite/g++.dg/cpp26/expansion-stmt2.C b/gcc/testsuite/g++.dg/cpp26/expansion-stmt2.C index b1ab50c1cea7..fa6d7727fe6f 100644 --- a/gcc/testsuite/g++.dg/cpp26/expansion-stmt2.C +++ b/gcc/testsuite/g++.dg/cpp26/expansion-stmt2.C @@ -40,15 +40,15 @@ struct C namespace N { struct B { constexpr B () {} }; - constexpr A begin (B &) { return A (0); } - constexpr A end (B &) { return A (6); } + constexpr A begin (const B &) { return A (0); } + constexpr A end (const B &) { return A (6); } } namespace O { struct D { constexpr D () {} }; - constexpr C begin (D &) { return C (0, 42, 5); } - constexpr C end (D &) { return C (6, 36, 11); } + constexpr C begin (const D &) { return C (0, 42, 5); } + constexpr C end (const D &) { return C (6, 36, 11); } } #if __cpp_nontype_template_parameter_class >= 201806L diff --git a/gcc/testsuite/g++.dg/cpp26/expansion-stmt25.C b/gcc/testsuite/g++.dg/cpp26/expansion-stmt25.C index 0d6d3f2d4615..5152096a2326 100644 --- a/gcc/testsuite/g++.dg/cpp26/expansion-stmt25.C +++ b/gcc/testsuite/g++.dg/cpp26/expansion-stmt25.C @@ -15,8 +15,8 @@ struct A namespace N { struct B { constexpr B () {} }; - constexpr A begin (B &) { return A (0); } - constexpr A end (B &) { return A (6); } + constexpr A begin (const B &) { return A (0); } + constexpr A end (const B &) { return A (6); } } void diff --git a/gcc/testsuite/g++.dg/cpp26/expansion-stmt26.C b/gcc/testsuite/g++.dg/cpp26/expansion-stmt26.C new file mode 100644 index 000000000000..2a56c7c04184 --- /dev/null +++ b/gcc/testsuite/g++.dg/cpp26/expansion-stmt26.C @@ -0,0 +1,18 @@ +// C++26 P1306R5 - Expansion statements +// { dg-do run { target c++23 } } +// { dg-options "" } + +#include <span> + +constexpr int arr[3] = { 1, 2, 3 }; +consteval std::span <const int> foo () { return std::span <const int> (arr); } + +int +main () +{ + int r = 0; + template for (constexpr auto m : foo ()) // { dg-warning "'template for' only available with" "" { target c++23_down } } + r += m; + if (r != 6) + __builtin_abort (); +} diff --git a/gcc/testsuite/g++.dg/cpp26/expansion-stmt3.C b/gcc/testsuite/g++.dg/cpp26/expansion-stmt3.C index c8a3879d5d66..cb01d7270a32 100644 --- a/gcc/testsuite/g++.dg/cpp26/expansion-stmt3.C +++ b/gcc/testsuite/g++.dg/cpp26/expansion-stmt3.C @@ -40,15 +40,15 @@ struct C namespace N { struct B { constexpr B () {} }; - constexpr A begin (B &) { return A (0); } - constexpr A end (B &) { return A (6); } + constexpr A begin (const B &) { return A (0); } + constexpr A end (const B &) { return A (6); } } namespace O { struct D { constexpr D () {} }; - constexpr C begin (D &) { return C (0, 42, 5); } - constexpr C end (D &) { return C (6, 36, 11); } + constexpr C begin (const D &) { return C (0, 42, 5); } + constexpr C end (const D &) { return C (6, 36, 11); } } template <int N> diff --git a/gcc/testsuite/g++.dg/reflect/p3491-2.C b/gcc/testsuite/g++.dg/reflect/p3491-2.C index b0de7a90a525..b760cd59b738 100644 --- a/gcc/testsuite/g++.dg/reflect/p3491-2.C +++ b/gcc/testsuite/g++.dg/reflect/p3491-2.C @@ -12,22 +12,14 @@ constexpr auto foo() -> std::vector<int> { return {1, 2, 3}; } consteval void bar() { template for (constexpr int I : foo()) { // doesn't work - } // { dg-error "modification of '<temporary>' from outside current evaluation is not a constant expression" } + } // { dg-error "'foo\\\(\\\)' is not a constant expression because it refers to a result of 'operator new'" } } consteval int baz() { int r = 0; -#if 0 - // TODO: This doesn't work yet. template for (constexpr int I : std::define_static_array(foo())) { r += I; } -#else - // Ugly workaround for that. - template for (constexpr int I : (const std::span<const int>)std::define_static_array(foo())) { - r += I; - } -#endif return r; } @@ -45,6 +37,15 @@ consteval int fred() { return (... + m); } +consteval int garply() { + int r = 0; + template for (constexpr int I : (const std::span<const int>)std::define_static_array(foo())) { + r += I; + } + return r; +} + static_assert (baz() == 6); static_assert (qux() == 6); static_assert (fred<int>() == 6); +static_assert (garply() == 6);
