Hi! For the https://gcc.gnu.org/pipermail/gcc/2025-November/246977.html issues I've filed https://github.com/cplusplus/CWG/issues/805 and got there some responses. One possibility is to change 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).
The following patch on top of the 2 earlier expansion-stmt patches attempts to implement it, though not sure if it should be committed until at least a CWG is filed for it with some proposed resolution. It does affect some of our tests because they weren't using const for the begin/end functions but bet real-world C++ code doesn't suffer from that. Bootstrapped/regtested on x86_64-linux and i686-linux. BTW, I'd still like to understand which of the compilers is right for static constexpr const auto && var = foo (); where foo () is non-const prvalue like std::span<const int>, whether the lifetime extended temporary is static std::span<const int> in that case or static constexpr std::span<const int>, i.e. whether what var refers to can be then used in constant expressions or not. I think from reading [class.temp] and [conv.rval] I lean towards gcc being right, but maybe I'm missing something important. 2025-11-15 Jakub Jelinek <[email protected]> * parser.cc (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; as per https://github.com/cplusplus/CWG/issues/805. * 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. --- gcc/cp/parser.cc.jj 2025-11-14 16:40:33.882192102 +0100 +++ gcc/cp/parser.cc 2025-11-14 17:56:20.854087295 +0100 @@ -15205,9 +15205,24 @@ cp_build_range_for_decls (location_t loc range_temp = range_expr; else { - range_temp = build_range_temp (range_expr); if (expansion_stmt_p) { + /* 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)); + tree auto_node = make_decltype_auto (); + tree range_type + = cp_build_qualified_type (auto_node, TYPE_QUAL_CONST); + range_type = do_auto_deduction (range_type, range_expr, + auto_node); + + /* Create the __range variable. */ + range_temp = build_decl (input_location, VAR_DECL, + for_range__identifier, range_type); + TREE_USED (range_temp) = 1; + DECL_ARTIFICIAL (range_temp) = 1; + /* 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 @@ -15219,6 +15234,9 @@ cp_build_range_for_decls (location_t loc DECL_DECLARED_CONSTEXPR_P (range_temp) = 1; TREE_READONLY (range_temp) = 1; } + else + range_temp = build_range_temp (range_expr); + pushdecl (range_temp); cp_finish_decl (range_temp, range_expr, /*is_constant_init*/false, NULL_TREE, --- gcc/testsuite/g++.dg/cpp26/expansion-stmt1.C.jj 2025-11-14 16:41:22.740057791 +0100 +++ gcc/testsuite/g++.dg/cpp26/expansion-stmt1.C 2025-11-14 17:48:49.739357883 +0100 @@ -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 --- gcc/testsuite/g++.dg/cpp26/expansion-stmt2.C.jj 2025-11-14 16:41:45.869193857 +0100 +++ gcc/testsuite/g++.dg/cpp26/expansion-stmt2.C 2025-11-14 17:49:06.045130905 +0100 @@ -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 --- gcc/testsuite/g++.dg/cpp26/expansion-stmt3.C.jj 2025-11-14 16:42:00.935339109 +0100 +++ gcc/testsuite/g++.dg/cpp26/expansion-stmt3.C 2025-11-14 17:49:27.954825922 +0100 @@ -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> --- gcc/testsuite/g++.dg/cpp26/expansion-stmt16.C.jj 2025-08-23 15:00:04.780781107 +0200 +++ gcc/testsuite/g++.dg/cpp26/expansion-stmt16.C 2025-11-14 18:12:39.663478808 +0100 @@ -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 } } --- gcc/testsuite/g++.dg/cpp26/expansion-stmt18.C.jj 2025-11-14 16:41:34.239702362 +0100 +++ gcc/testsuite/g++.dg/cpp26/expansion-stmt18.C 2025-11-14 17:51:13.391360347 +0100 @@ -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 --- gcc/testsuite/g++.dg/cpp26/expansion-stmt25.C.jj 2025-11-14 17:00:29.841604570 +0100 +++ gcc/testsuite/g++.dg/cpp26/expansion-stmt25.C 2025-11-14 17:51:30.104128077 +0100 @@ -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 --- gcc/testsuite/g++.dg/cpp26/expansion-stmt26.C.jj 2025-11-14 17:21:54.956780676 +0100 +++ gcc/testsuite/g++.dg/cpp26/expansion-stmt26.C 2025-11-14 17:23:25.419529469 +0100 @@ -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 (); +} Jakub
