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);

Reply via email to