https://gcc.gnu.org/bugzilla/show_bug.cgi?id=107637
Bug ID: 107637 Summary: C++23: Implement P2644R1 - Final Fix of Broken Range‐based for Loop Product: gcc Version: 13.0 Status: UNCONFIRMED Severity: normal Priority: P3 Component: c++ Assignee: unassigned at gcc dot gnu.org Reporter: jakub at gcc dot gnu.org Target Milestone: --- 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...