Bug 107637

Summary: [C++23] P2718R0 - Final Fix of Broken Range‐based for Loop
Product: gcc Reporter: Jakub Jelinek <jakub>
Component: c++Assignee: Not yet assigned to anyone <unassigned>
Status: NEW ---    
Severity: normal CC: mpolacek, webrown.cpp
Priority: P3    
Version: 13.0   
Target Milestone: ---   
Host: Target:
Build: Known to work:
Known to fail: Last reconfirmed: 2022-11-14 00:00:00
Bug Depends on:    
Bug Blocks: 98940    

Description Jakub Jelinek 2022-11-11 14:59:42 UTC
Testcase:
// P2644R1 - Final Fix of Broken Range‐based for Loop
// { dg-do run { target c++11 } }

extern "C" void abort ();
struct S
{
  S () { ++s; }
  S (const S &) { ++s; }
  ~S () { --s; }
  static int s;
};
int S::s;
struct T
{
  T (const S &, const S &) { ++t; }
  T (const T &) { ++t; }
  ~T () { --t; }
  static int t;
};
int T::t;
int a[4];

int *
begin (const S &)
{
  return &a[0];
}

int *
end (const S &)
{
  return &a[4];
}

int *
begin (const T &)
{
  return &a[0];
}

int *
end (const T &)
{
  return &a[4];
}

const S &
foo (const S &x)
{
  return x;
}

const T &
foo (const T &x)
{
  return x;
}

int
main ()
{
  if (S::s != 0)
    abort ();
  for (auto x : S ())
    {
      if (S::s != 1)
	abort ();
    }
  if (S::s != 0)
    abort ();
  for (auto x : foo (S ()))
    {
      if (S::s != (__cpp_range_based_for >= 202211L))
	abort ();
    }
  if (S::s != 0)
    abort ();
  if (T::t != 0)
    abort ();
  for (auto x : T (S (), S ()))
    {
      if (S::s != 2 * (__cpp_range_based_for >= 202211L) || T::t != 1)
	abort ();
    }
  if (S::s != 0 || T::t != 0)
    abort ();
  for (auto x : foo (T (S (), S ())))
    {
      if (S::s != 2 * (__cpp_range_based_for >= 202211L)
	  || T::t != (__cpp_range_based_for >= 202211L))
	abort ();
    }
  if (S::s != 0 || T::t != 0)
    abort ();
}

if I understand the paper well.
Tried to play with it, but:
--- gcc/cp/decl.cc.jj	2022-11-11 08:43:28.296462815 +0100
+++ gcc/cp/decl.cc	2022-11-11 13:53:19.071246170 +0100
@@ -7809,7 +7809,14 @@ initialize_local_var (tree decl, tree in
 
 	  gcc_assert (building_stmt_list_p ());
 	  saved_stmts_are_full_exprs_p = stmts_are_full_exprs_p ();
-	  current_stmt_tree ()->stmts_are_full_exprs_p = 1;
+	  // P2644R1 - for-range-initializer in C++23 should have temporaries
+	  // destructed only at the end of the whole range for loop.
+	  if (cxx_dialect >= cxx23
+	      && DECL_ARTIFICIAL (decl)
+	      && DECL_NAME (decl) == for_range__identifier)
+	    current_stmt_tree ()->stmts_are_full_exprs_p = 0;
+	  else
+	    current_stmt_tree ()->stmts_are_full_exprs_p = 1;
 	  finish_expr_stmt (init);
 	  current_stmt_tree ()->stmts_are_full_exprs_p =
 	    saved_stmts_are_full_exprs_p;
--- gcc/cp/semantics.cc.jj	2022-11-09 11:22:42.612628127 +0100
+++ gcc/cp/semantics.cc	2022-11-11 15:49:30.569832414 +0100
@@ -1408,7 +1408,10 @@ finish_for_stmt (tree for_stmt)
 	}
     }
 
-  add_stmt (do_poplevel (scope));
+  tree bind = do_poplevel (scope);
+  if (range_for_decl[0] && cxx_dialect >= cxx23)
+    bind = maybe_cleanup_point_expr_void (bind);
+  add_stmt (bind);
 
   /* If we're being called from build_vec_init, don't mess with the names of
      the variables for an enclosing range-for.  */

ICEs, not sure why the outer CLEANUP_POINT_EXPR doesn't catch those TARGET_EXPR cleanups.
But, I think it could interact badly with the cleanups for extended lifetime references.
So shall something walk init in cp_finish_decl of for_range__identifier decls,
look similarly to wrap_cleanups init and look for cleanups on TARGET_EXPRs not nested inside of CLEANUP_POINT_EXPRs and somehow extend their lifetime (perhaps move them out of the TARGET_EXPRs just into normal cleanups)?
Giving up on this...
Comment 1 Jakub Jelinek 2022-11-14 17:46:51 UTC
I think the actual wording is in P2718R0 now, so will show up in
https://wg21.link/p2718r0 at some point.