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

Reply via email to