diff mbox series

c++: Implement C++23 P2718R0 - Wording for P2644R1 Fix for Range-based for Loop [PR107637]

Message ID ZrZon3HDqKICv7Vf@tucnak
State New
Headers show
Series c++: Implement C++23 P2718R0 - Wording for P2644R1 Fix for Range-based for Loop [PR107637] | expand

Commit Message

Jakub Jelinek Aug. 9, 2024, 7:06 p.m. UTC
Hi!

The following patch implements the C++23 P2718R0 paper
- Wording for P2644R1 Fix for Range-based for Loop.
As all the temporaries from __for_range initialization should have life
extended until the end of __for_range scope, this patch disables (for C++23
and later only and if !processing_template_decl) CLEANUP_POINT_EXPR wrapping
of the __for_range declaration, also disables -Wdangling-reference warning
as well as the rest of extend_ref_init_temps (we know the __for_range temporary
is not TREE_STATIC and as all the temporaries from the initializer will be life
extended, we shouldn't try to handle temporaries referenced by references any
differently) and adds an extra push_stmt_list/pop_stmt_list before
cp_finish_decl of __for_range and after end of the for body and wraps all
that into CLEANUP_POINT_EXPR.
I had to repeat that also for OpenMP range loops because those are handled
differently.

Bootstrapped/regtested on x86_64-linux and i686-linux, ok for trunk?

2024-08-09  Jakub Jelinek  <jakub@redhat.com>

	PR c++/107637
gcc/
	* omp-general.cc (find_combined_omp_for, find_nested_loop_xform):
	Handle CLEANUP_POINT_EXPR like TRY_FINALLY_EXPR.
gcc/c-family/
	* c-cppbuiltin.cc (c_cpp_builtins): Change __cpp_range_based_for
	value for C++23 and newer from 201603L to 202212L.
	* c-omp.cc (c_find_nested_loop_xform_r): Handle CLEANUP_POINT_EXPR
	like TRY_FINALLY_EXPR.
gcc/cp/
	* parser.cc: Implement C++23 P2718R0 - Wording for P2644R1 Fix for
	Range-based for Loop.
	(cp_convert_range_for): For C++23 call push_stmt_list () before
	cp_finish_decl for range_temp and save it temporarily to
	FOR_INIT_STMT.
	(cp_convert_omp_range_for): Remember DECL_NAME of range_temp and
	for cp_finish_decl call restore it before clearing it again.
	(cp_parser_omp_loop_nest): For C++23 range for add CLEANUP_POINT_EXPR
	around sl.
	* decl.cc (initialize_local_var): For C++23 temporarily clear
	stmts_are_full_exprs_p rather than set for for_range__identifier
	decls.
	* call.cc (extend_ref_init_temps): For C++23 return init early
	for for_range__identifier decls.
	* semantics.cc (finish_for_stmt): For C++23 if cp_convert_range_for
	set FOR_INIT_STMT, pop_stmt_list it and wrap into CLEANUP_POINT_EXPR.
	* pt.cc (tsubst_stmt) <case OMP_FOR>: For C++23 if there are any
	range fors in the loop nest, add push_stmt_list starting before the
	initializations, pop_stmt_list it after the body and wrap into
	CLEANUP_POINT_EXPR.
gcc/testsuite/
	* g++.dg/cpp23/range-for1.C: New test.
	* g++.dg/cpp23/range-for2.C: New test.
	* g++.dg/cpp23/feat-cxx2b.C (__cpp_range_based_for): Check for
	202212L rather than 201603L.
	* g++.dg/cpp26/feat-cxx26.C (__cpp_range_based_for): Likewise.
	* g++.dg/warn/Wdangling-reference4.C: Don't expect warning for C++23
	or newer.
libgomp/
	* testsuite/libgomp.c++/range-for-1.C: New test.
	* testsuite/libgomp.c++/range-for-2.C: New test.


	Jakub

Comments

Jason Merrill Sept. 23, 2024, 3:32 p.m. UTC | #1
On 8/9/24 9:06 PM, Jakub Jelinek wrote:
> Hi!
> 
> The following patch implements the C++23 P2718R0 paper
> - Wording for P2644R1 Fix for Range-based for Loop.
> As all the temporaries from __for_range initialization should have life
> extended until the end of __for_range scope, this patch disables (for C++23
> and later only and if !processing_template_decl) CLEANUP_POINT_EXPR wrapping
> of the __for_range declaration, also disables -Wdangling-reference warning
> as well as the rest of extend_ref_init_temps (we know the __for_range temporary
> is not TREE_STATIC and as all the temporaries from the initializer will be life
> extended, we shouldn't try to handle temporaries referenced by references any
> differently) and adds an extra push_stmt_list/pop_stmt_list before
> cp_finish_decl of __for_range and after end of the for body and wraps all
> that into CLEANUP_POINT_EXPR.
> I had to repeat that also for OpenMP range loops because those are handled
> differently.

Let's add a flag for this, not just control it with cxx_dialect.  We 
might want to consider enabling it by default in earlier modes when not 
being strictly conforming?

> @@ -44600,11 +44609,14 @@ cp_convert_omp_range_for (tree &this_pre
>         else
>   	{
>   	  range_temp = build_range_temp (init);
> +	  tree name = DECL_NAME (range_temp);
>   	  DECL_NAME (range_temp) = NULL_TREE;
>   	  pushdecl (range_temp);
> +	  DECL_NAME (range_temp) = name;
>   	  cp_finish_decl (range_temp, init,
>   			  /*is_constant_init*/false, NULL_TREE,
>   			  LOOKUP_ONLYCONVERTING);
> +	  DECL_NAME (range_temp) = NULL_TREE;

This messing with the name needs a rationale.  What wants it to be null?

Jason
Jakub Jelinek Sept. 23, 2024, 7:24 p.m. UTC | #2
On Mon, Sep 23, 2024 at 11:32:59AM -0400, Jason Merrill wrote:
> On 8/9/24 9:06 PM, Jakub Jelinek wrote:
> > Hi!
> > 
> > The following patch implements the C++23 P2718R0 paper
> > - Wording for P2644R1 Fix for Range-based for Loop.
> > As all the temporaries from __for_range initialization should have life
> > extended until the end of __for_range scope, this patch disables (for C++23
> > and later only and if !processing_template_decl) CLEANUP_POINT_EXPR wrapping
> > of the __for_range declaration, also disables -Wdangling-reference warning
> > as well as the rest of extend_ref_init_temps (we know the __for_range temporary
> > is not TREE_STATIC and as all the temporaries from the initializer will be life
> > extended, we shouldn't try to handle temporaries referenced by references any
> > differently) and adds an extra push_stmt_list/pop_stmt_list before
> > cp_finish_decl of __for_range and after end of the for body and wraps all
> > that into CLEANUP_POINT_EXPR.
> > I had to repeat that also for OpenMP range loops because those are handled
> > differently.
> 
> Let's add a flag for this, not just control it with cxx_dialect.  We might
> want to consider enabling it by default in earlier modes when not being
> strictly conforming?

-frange-based-for-ext-temps
or do you have better suggestion?

Shall we allow also disabling it in C++23 or later modes, or override
user choice unconditionally for C++23+ and only allow users to
enable/disable it in C++11-C++20?

What about the __cpp_range_based_for predefined macro?
Shall it be defined to the C++23 202211L value if the switch is on?
While that could be done in theory for C++17 and later code, for C++11/14
__cpp_range_based_for is 200907L and doesn't include the C++17
201603L step.  Or keep the macro only for C++23 and later?
And if one can override -std=c++23 -fno-range-based-for-ext-temps,
shall it use the C++17 version?

> > @@ -44600,11 +44609,14 @@ cp_convert_omp_range_for (tree &this_pre
> >         else
> >   	{
> >   	  range_temp = build_range_temp (init);
> > +	  tree name = DECL_NAME (range_temp);
> >   	  DECL_NAME (range_temp) = NULL_TREE;
> >   	  pushdecl (range_temp);
> > +	  DECL_NAME (range_temp) = name;
> >   	  cp_finish_decl (range_temp, init,
> >   			  /*is_constant_init*/false, NULL_TREE,
> >   			  LOOKUP_ONLYCONVERTING);
> > +	  DECL_NAME (range_temp) = NULL_TREE;
> 
> This messing with the name needs a rationale.  What wants it to be null?

I'll add comments.  The first = NULL_TREE; is needed so that pushdecl
doesn't register the temporary for name lookup, the = name now is so that
cp_finish_decl recognizes the temporary as range based for temporary
for the lifetime extension, and the last one is just to preserve previous
behavior, not have it visible in debug info etc.

	Jakub
Jason Merrill Sept. 23, 2024, 7:46 p.m. UTC | #3
On 9/23/24 9:24 PM, Jakub Jelinek wrote:
> On Mon, Sep 23, 2024 at 11:32:59AM -0400, Jason Merrill wrote:
>> On 8/9/24 9:06 PM, Jakub Jelinek wrote:
>>> Hi!
>>>
>>> The following patch implements the C++23 P2718R0 paper
>>> - Wording for P2644R1 Fix for Range-based for Loop.
>>> As all the temporaries from __for_range initialization should have life
>>> extended until the end of __for_range scope, this patch disables (for C++23
>>> and later only and if !processing_template_decl) CLEANUP_POINT_EXPR wrapping
>>> of the __for_range declaration, also disables -Wdangling-reference warning
>>> as well as the rest of extend_ref_init_temps (we know the __for_range temporary
>>> is not TREE_STATIC and as all the temporaries from the initializer will be life
>>> extended, we shouldn't try to handle temporaries referenced by references any
>>> differently) and adds an extra push_stmt_list/pop_stmt_list before
>>> cp_finish_decl of __for_range and after end of the for body and wraps all
>>> that into CLEANUP_POINT_EXPR.
>>> I had to repeat that also for OpenMP range loops because those are handled
>>> differently.
>>
>> Let's add a flag for this, not just control it with cxx_dialect.  We might
>> want to consider enabling it by default in earlier modes when not being
>> strictly conforming?
> 
> -frange-based-for-ext-temps
> or do you have better suggestion?

I'd probably drop "based", "range-for" seems enough.

> Shall we allow also disabling it in C++23 or later modes, or override
> user choice unconditionally for C++23+ and only allow users to
> enable/disable it in C++11-C++20?

Hmm, I think the latter.

> What about the __cpp_range_based_for predefined macro?
> Shall it be defined to the C++23 202211L value if the switch is on?
> While that could be done in theory for C++17 and later code, for C++11/14
> __cpp_range_based_for is 200907L and doesn't include the C++17
> 201603L step.  Or keep the macro only for C++23 and later?

I think update the macro for 17 and later.

>>> @@ -44600,11 +44609,14 @@ cp_convert_omp_range_for (tree &this_pre
>>>          else
>>>    	{
>>>    	  range_temp = build_range_temp (init);
>>> +	  tree name = DECL_NAME (range_temp);
>>>    	  DECL_NAME (range_temp) = NULL_TREE;
>>>    	  pushdecl (range_temp);
>>> +	  DECL_NAME (range_temp) = name;
>>>    	  cp_finish_decl (range_temp, init,
>>>    			  /*is_constant_init*/false, NULL_TREE,
>>>    			  LOOKUP_ONLYCONVERTING);
>>> +	  DECL_NAME (range_temp) = NULL_TREE;
>>
>> This messing with the name needs a rationale.  What wants it to be null?
> 
> I'll add comments.  The first = NULL_TREE; is needed so that pushdecl
> doesn't register the temporary for name lookup, the = name now is so that
> cp_finish_decl recognizes the temporary as range based for temporary
> for the lifetime extension, and the last one is just to preserve previous
> behavior, not have it visible in debug info etc.

But cp_convert_range_for doesn't ever set the name to NULL_TREE, why 
should the OMP variant be different?

Having it visible to name lookup in the debugger seems beneficial. 
Having it visible to the code seems less useful, but not important to 
prevent.

Jason
diff mbox series

Patch

--- gcc/omp-general.cc.jj	2024-06-05 19:09:54.052616928 +0200
+++ gcc/omp-general.cc	2024-08-09 17:01:01.641036347 +0200
@@ -972,6 +972,7 @@  find_combined_omp_for (tree *tp, int *wa
       *walk_subtrees = 1;
       break;
     case TRY_FINALLY_EXPR:
+    case CLEANUP_POINT_EXPR:
       pdata[0] = tp;
       *walk_subtrees = 1;
       break;
@@ -4105,6 +4106,7 @@  find_nested_loop_xform (tree *tp, int *w
       *walk_subtrees = 1;
       break;
     case TRY_FINALLY_EXPR:
+    case CLEANUP_POINT_EXPR:
       pdata[0] = tp;
       *walk_subtrees = 1;
       break;
--- gcc/c-family/c-cppbuiltin.cc.jj	2024-07-12 14:03:23.453732902 +0200
+++ gcc/c-family/c-cppbuiltin.cc	2024-08-08 20:03:17.503775789 +0200
@@ -1034,7 +1034,8 @@  c_cpp_builtins (cpp_reader *pfile)
 	  cpp_define (pfile, "__cpp_fold_expressions=201603L");
 	  if (cxx_dialect <= cxx17)
 	    cpp_define (pfile, "__cpp_nontype_template_args=201411L");
-	  cpp_define (pfile, "__cpp_range_based_for=201603L");
+	  if (cxx_dialect <= cxx20)
+	    cpp_define (pfile, "__cpp_range_based_for=201603L");
 	  if (cxx_dialect <= cxx17)
 	    cpp_define (pfile, "__cpp_constexpr=201603L");
 	  cpp_define (pfile, "__cpp_if_constexpr=201606L");
@@ -1087,6 +1088,7 @@  c_cpp_builtins (cpp_reader *pfile)
 	  cpp_define (pfile, "__cpp_static_call_operator=202207L");
 	  cpp_define (pfile, "__cpp_implicit_move=202207L");
 	  cpp_define (pfile, "__cpp_explicit_this_parameter=202110L");
+	  cpp_define (pfile, "__cpp_range_based_for=202211L");
 	}
       if (cxx_dialect > cxx23)
 	{
--- gcc/c-family/c-omp.cc.jj	2024-06-05 19:09:54.054616902 +0200
+++ gcc/c-family/c-omp.cc	2024-08-09 17:13:40.653767553 +0200
@@ -1617,6 +1617,7 @@  c_find_nested_loop_xform_r (tree *tp, in
       *walk_subtrees = 1;
       break;
     case TRY_FINALLY_EXPR:
+    case CLEANUP_POINT_EXPR:
       *walk_subtrees = 1;
       break;
     default:
--- gcc/cp/parser.cc.jj	2024-08-07 09:38:00.642810039 +0200
+++ gcc/cp/parser.cc	2024-08-09 17:11:36.756252734 +0200
@@ -14440,6 +14440,15 @@  cp_convert_range_for (tree statement, tr
 	{
 	  range_temp = build_range_temp (range_expr);
 	  pushdecl (range_temp);
+	  if (cxx_dialect >= cxx23)
+	    {
+	      /* P2718R0 - put the range_temp declaration and everything
+		 until end of the range for body into an extra STATEMENT_LIST
+		 which will have CLEANUP_POINT_EXPR around it, so that all
+		 temporaries are destroyed at the end of it.  */
+	      gcc_assert (FOR_INIT_STMT (statement) == NULL_TREE);
+	      FOR_INIT_STMT (statement) = push_stmt_list ();
+	    }
 	  cp_finish_decl (range_temp, range_expr,
 			  /*is_constant_init*/false, NULL_TREE,
 			  LOOKUP_ONLYCONVERTING);
@@ -44600,11 +44609,14 @@  cp_convert_omp_range_for (tree &this_pre
       else
 	{
 	  range_temp = build_range_temp (init);
+	  tree name = DECL_NAME (range_temp);
 	  DECL_NAME (range_temp) = NULL_TREE;
 	  pushdecl (range_temp);
+	  DECL_NAME (range_temp) = name;
 	  cp_finish_decl (range_temp, init,
 			  /*is_constant_init*/false, NULL_TREE,
 			  LOOKUP_ONLYCONVERTING);
+	  DECL_NAME (range_temp) = NULL_TREE;
 	  range_temp_decl = range_temp;
 	  range_temp = convert_from_reference (range_temp);
 	}
@@ -45538,7 +45550,15 @@  cp_parser_omp_loop_nest (cp_parser *pars
 
   /* Pop and remember the init block.  */
   if (sl)
-    add_stmt (pop_stmt_list (sl));
+    {
+      sl = pop_stmt_list (sl);
+      /* P2718R0 - Add CLEANUP_POINT_EXPR so that temporaries in
+	 for-range-initializer whose lifetime is extended are destructed
+	 here.  */
+      if (cxx_dialect >= cxx23 && is_range_for && !processing_template_decl)
+	sl = maybe_cleanup_point_expr_void (sl);
+      add_stmt (sl);
+    }
   finish_compound_stmt (init_scope);
   init_block = pop_stmt_list (init_block);
   omp_for_parse_state->init_blockv[depth] = init_block;
--- gcc/cp/decl.cc.jj	2024-08-07 11:58:11.275368835 +0200
+++ gcc/cp/decl.cc	2024-08-08 19:08:07.393522430 +0200
@@ -8111,7 +8111,13 @@  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;
+	  /* P2718R0 - avoid CLEANUP_POINT_EXPR for range-for-initializer,
+	     temporaries from there should have lifetime extended.  */
+	  if (DECL_NAME (decl) == for_range__identifier
+	      && cxx_dialect >= cxx23)
+	    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/call.cc.jj	2024-08-06 11:05:29.098470099 +0200
+++ gcc/cp/call.cc	2024-08-08 19:02:29.619882199 +0200
@@ -14514,6 +14514,12 @@  extend_ref_init_temps (tree decl, tree i
   if (processing_template_decl)
     return init;
 
+  /* P2718R0 - ignore temporaries in C++23 for-range-initializer, those
+     have all extended lifetime.  */
+  if (DECL_NAME (decl) == for_range__identifier
+      && cxx_dialect >= cxx23)
+    return init;
+
   maybe_warn_dangling_reference (decl, init);
 
   if (TYPE_REF_P (type))
--- gcc/cp/semantics.cc.jj	2024-08-06 11:05:29.211468580 +0200
+++ gcc/cp/semantics.cc	2024-08-09 11:29:12.553445065 +0200
@@ -1601,6 +1601,20 @@  finish_for_stmt (tree for_stmt)
 	}
     }
 
+  /* P2718R0 - Add CLEANUP_POINT_EXPR so that temporaries in
+     for-range-initializer whose lifetime is extended are destructed
+     here.  */
+  if (cxx_dialect >= cxx23
+      && range_for_decl[0]
+      && FOR_INIT_STMT (for_stmt))
+    {
+      tree stmt = pop_stmt_list (FOR_INIT_STMT (for_stmt));
+      FOR_INIT_STMT (for_stmt) = NULL_TREE;
+      stmt = build_stmt (EXPR_LOCATION (for_stmt), EXPR_STMT, stmt);
+      stmt = maybe_cleanup_point_expr_void (stmt);
+      add_stmt (stmt);
+    }
+
   add_stmt (do_poplevel (scope));
 
   /* If we're being called from build_vec_init, don't mess with the names of
--- gcc/cp/pt.cc.jj	2024-08-07 09:47:59.912769436 +0200
+++ gcc/cp/pt.cc	2024-08-09 17:11:03.082656385 +0200
@@ -19099,6 +19099,18 @@  tsubst_stmt (tree t, tree args, tsubst_f
 	RECUR (OMP_FOR_PRE_BODY (t));
 	pre_body = pop_stmt_list (pre_body);
 
+	tree sl = NULL_TREE;
+	if (cxx_dialect >= cxx23
+	    && OMP_FOR_INIT (t) != NULL_TREE
+	    && !processing_template_decl)
+	  for (i = 0; i < TREE_VEC_LENGTH (OMP_FOR_INIT (t)); i++)
+	    if (TREE_VEC_ELT (OMP_FOR_INIT (t), i)
+		&& TREE_VEC_ELT (OMP_FOR_COND (t), i) == global_namespace)
+	      {
+		sl = push_stmt_list ();
+		break;
+	      }
+
 	if (OMP_FOR_INIT (t) != NULL_TREE)
 	  for (i = 0; i < TREE_VEC_LENGTH (OMP_FOR_INIT (t)); i++)
 	    {
@@ -19154,6 +19166,16 @@  tsubst_stmt (tree t, tree args, tsubst_f
 	    add_stmt (t);
 	  }
 
+	if (sl)
+	  {
+	    /* P2718R0 - Add CLEANUP_POINT_EXPR so that temporaries in
+	       for-range-initializer whose lifetime is extended are destructed
+	       here.  */
+	    sl = pop_stmt_list (sl);
+	    sl = maybe_cleanup_point_expr_void (sl);
+	    add_stmt (sl);
+	  }
+
 	add_stmt (finish_omp_for_block (finish_omp_structured_block (stmt),
 					t));
 	pop_omp_privatization_clauses (r);
--- gcc/testsuite/g++.dg/cpp23/range-for1.C.jj	2024-08-09 11:15:18.162167370 +0200
+++ gcc/testsuite/g++.dg/cpp23/range-for1.C	2024-08-09 20:29:14.084033001 +0200
@@ -0,0 +1,206 @@ 
+// P2718R0 - Wording for P2644R1 Fix for Range-based for Loop
+// { dg-do run { target c++11 } }
+
+extern "C" void abort ();
+void check (bool);
+
+struct S
+{
+  S () { ++s; }
+  S (const S &) { ++s; }
+  ~S () { check (true); --s; }
+  static int s;
+};
+
+int S::s = -1;
+S sv;
+
+struct T
+{
+  T (const S &, const S &) { ++t; }
+  T (const T &) { ++t; }
+  ~T () { check (false); --t; }
+  static int t;
+};
+
+int T::t = -1;
+T tv (sv, sv);
+int a[4];
+int c;
+
+void
+check (bool is_s)
+{
+  if (c)
+    {
+      if (is_s)
+	{
+	  if (T::t != (c == 1))
+	    abort ();
+	}
+      else
+	{
+	  if (S::s != (c == 1 ? 0 : 2))
+	    abort ();
+	}
+    }
+}
+
+template <typename T>
+int *
+begin (const T &)
+{
+  return &a[0];
+}
+
+template <typename T>
+int *
+end (const T &)
+{
+  return &a[4];
+}
+
+const S &
+foo (const S &)
+{
+  return sv;
+}
+
+const T &
+foo (const T &)
+{
+  return tv;
+}
+
+void
+bar ()
+{
+  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 ();
+  c = 1 + (__cpp_range_based_for >= 202211L);
+  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 ();
+  c = 2;
+  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 ();
+  c = 0;
+}
+
+template <int N>
+void
+baz ()
+{
+  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 ();
+  c = 1 + (__cpp_range_based_for >= 202211L);
+  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 ();
+  c = 2;
+  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 ();
+  c = 0;
+}
+
+template <typename S, typename T>
+void
+qux ()
+{
+  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 ();
+  c = 1 + (__cpp_range_based_for >= 202211L);
+  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 ();
+  c = 2;
+  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 ();
+  c = 0;
+}
+
+int
+main ()
+{
+  bar ();
+  baz <0> ();
+  qux <S, T> ();
+}
--- gcc/testsuite/g++.dg/cpp23/range-for2.C.jj	2024-08-09 14:08:11.850733241 +0200
+++ gcc/testsuite/g++.dg/cpp23/range-for2.C	2024-08-09 14:07:41.638122308 +0200
@@ -0,0 +1,230 @@ 
+// P2718R0 - Wording for P2644R1 Fix for Range-based for Loop
+// { dg-do run { target c++11 } }
+
+extern "C" void abort ();
+
+int a[4];
+
+template <typename T>
+int *
+begin (const T &)
+{
+  return &a[0];
+}
+
+template <typename T>
+int *
+end (const T &)
+{
+  return &a[4];
+}
+
+struct S
+{
+  S () { ++s; }
+  S (const S &) { ++s; }
+  ~S () { --s; }
+  static int s;
+};
+
+int S::s;
+
+template <typename T>
+struct U
+{
+  T t;
+  U () { ++u; }
+  U (const U &) { ++u; }
+  ~U () { --u; }
+  const int *begin () const { return ::begin (t); }
+  const int *end () const { return ::end (t); }
+  U &foo () { return *this; }
+  U bar () { return U (); }
+  static int u;
+};
+
+template <typename T>
+int U<T>::u;
+
+template <typename T>
+U<T>
+foo ()
+{
+  return U<T> {};
+}
+
+template <typename T>
+T
+fred (const T &, const T & = T{}, const T & = T{})
+{
+  return T {};
+}
+
+void
+bar ()
+{
+  int a[10] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
+  if (S::s != 0)
+    abort ();
+  for (auto x : S (), a)
+    {
+      if (S::s != (__cpp_range_based_for >= 202211L))
+	abort ();
+    }
+  if (S::s != 0)
+    abort ();
+  for (auto x : (void) S (), a)
+    {
+      if (S::s != (__cpp_range_based_for >= 202211L))
+	abort ();
+    }
+  if (S::s != 0)
+    abort ();
+  for (auto x : static_cast<void> (S ()), a)
+    {
+      if (S::s != (__cpp_range_based_for >= 202211L))
+	abort ();
+    }
+  if (S::s != 0)
+    abort ();
+  for (auto x : foo <S> ().foo ().bar ().foo ().bar ().foo ().bar ())
+    {
+      if (S::s != 1 + 3 * (__cpp_range_based_for >= 202211L))
+	abort ();
+      if (U<S>::u != S::s)
+	abort ();
+    }
+  if (S::s != 0 || U<S>::u != 0)
+    abort ();
+  for (auto x : fred (S {}))
+    {
+      if (S::s != 1 + 3 * (__cpp_range_based_for >= 202211L))
+	abort ();
+    }
+  if (S::s != 0)
+    abort ();
+  for (auto x : fred (fred (S {}, S {})))
+    {
+      __builtin_printf ("%d\n", S::s);
+      if (S::s != 1 + 6 * (__cpp_range_based_for >= 202211L))
+	abort ();
+    }
+  if (S::s != 0)
+    abort ();
+}
+
+template <int N>
+void
+baz ()
+{
+  int a[10] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
+  if (S::s != 0)
+    abort ();
+  for (auto x : S (), a)
+    {
+      if (S::s != (__cpp_range_based_for >= 202211L))
+	abort ();
+    }
+  if (S::s != 0)
+    abort ();
+  for (auto x : (void) S (), a)
+    {
+      if (S::s != (__cpp_range_based_for >= 202211L))
+	abort ();
+    }
+  if (S::s != 0)
+    abort ();
+  for (auto x : static_cast<void> (S ()), a)
+    {
+      if (S::s != (__cpp_range_based_for >= 202211L))
+	abort ();
+    }
+  if (S::s != 0)
+    abort ();
+  for (auto x : foo <S> ().foo ().bar ().foo ().bar ().foo ().bar ())
+    {
+      if (S::s != 1 + 3 * (__cpp_range_based_for >= 202211L))
+	abort ();
+      if (U<S>::u != S::s)
+	abort ();
+    }
+  if (S::s != 0 || U<S>::u != 0)
+    abort ();
+  for (auto x : fred (S {}))
+    {
+      if (S::s != 1 + 3 * (__cpp_range_based_for >= 202211L))
+	abort ();
+    }
+  if (S::s != 0)
+    abort ();
+  for (auto x : fred (fred (S {}, S {})))
+    {
+      __builtin_printf ("%d\n", S::s);
+      if (S::s != 1 + 6 * (__cpp_range_based_for >= 202211L))
+	abort ();
+    }
+  if (S::s != 0)
+    abort ();
+}
+
+template <typename S>
+void
+qux ()
+{
+  int a[10] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
+  if (S::s != 0)
+    abort ();
+  for (auto x : S (), a)
+    {
+      if (S::s != (__cpp_range_based_for >= 202211L))
+	abort ();
+    }
+  if (S::s != 0)
+    abort ();
+  for (auto x : (void) S (), a)
+    {
+      if (S::s != (__cpp_range_based_for >= 202211L))
+	abort ();
+    }
+  if (S::s != 0)
+    abort ();
+  for (auto x : static_cast<void> (S ()), a)
+    {
+      if (S::s != (__cpp_range_based_for >= 202211L))
+	abort ();
+    }
+  if (S::s != 0)
+    abort ();
+  for (auto x : foo <S> ().foo ().bar ().foo ().bar ().foo ().bar ())
+    {
+      if (S::s != 1 + 3 * (__cpp_range_based_for >= 202211L))
+	abort ();
+      if (U<S>::u != S::s)
+	abort ();
+    }
+  if (S::s != 0 || U<S>::u != 0)
+    abort ();
+  for (auto x : fred (S {}))
+    {
+      if (S::s != 1 + 3 * (__cpp_range_based_for >= 202211L))
+	abort ();
+    }
+  if (S::s != 0)
+    abort ();
+  for (auto x : fred (fred (S {}, S {})))
+    {
+      __builtin_printf ("%d\n", S::s);
+      if (S::s != 1 + 6 * (__cpp_range_based_for >= 202211L))
+	abort ();
+    }
+  if (S::s != 0)
+    abort ();
+}
+
+int
+main ()
+{
+  bar ();
+  baz <0> ();
+  qux <S> ();
+}
--- gcc/testsuite/g++.dg/cpp23/feat-cxx2b.C.jj	2024-01-10 12:19:08.249673372 +0100
+++ gcc/testsuite/g++.dg/cpp23/feat-cxx2b.C	2024-08-09 11:37:30.071130077 +0200
@@ -42,8 +42,8 @@ 
 
 #ifndef __cpp_range_based_for
 #  error "__cpp_range_based_for"
-#elif __cpp_range_based_for != 201603
-#  error "__cpp_range_based_for != 201603"
+#elif __cpp_range_based_for != 202211
+#  error "__cpp_range_based_for != 202211"
 #endif
 
 #ifndef __cpp_decltype
--- gcc/testsuite/g++.dg/cpp26/feat-cxx26.C.jj	2024-07-03 14:47:27.948553918 +0200
+++ gcc/testsuite/g++.dg/cpp26/feat-cxx26.C	2024-08-09 11:37:52.637843641 +0200
@@ -42,8 +42,8 @@ 
 
 #ifndef __cpp_range_based_for
 #  error "__cpp_range_based_for"
-#elif __cpp_range_based_for != 201603
-#  error "__cpp_range_based_for != 201603"
+#elif __cpp_range_based_for != 202211
+#  error "__cpp_range_based_for != 202211"
 #endif
 
 #ifndef __cpp_decltype
--- gcc/testsuite/g++.dg/warn/Wdangling-reference4.C.jj	2022-10-31 08:57:35.914172738 +0100
+++ gcc/testsuite/g++.dg/warn/Wdangling-reference4.C	2024-08-09 16:19:09.613948149 +0200
@@ -10,5 +10,5 @@  auto f() -> std::optional<std::string>;
 void
 g ()
 {
-  for (char c : f().value()) { (void) c; } // { dg-warning "dangling reference" }
+  for (char c : f().value()) { (void) c; } // { dg-warning "dangling reference" "" { target c++20_down } }
 }
--- libgomp/testsuite/libgomp.c++/range-for-1.C.jj	2024-08-09 12:25:19.879101383 +0200
+++ libgomp/testsuite/libgomp.c++/range-for-1.C	2024-08-09 20:32:53.907321362 +0200
@@ -0,0 +1,246 @@ 
+// P2718R0 - Wording for P2644R1 Fix for Range-based for Loop
+// { dg-do run }
+// { dg-additional-options "-std=c++17" }
+// { dg-require-effective-target tls_runtime }
+
+extern "C" void abort ();
+void check (bool);
+
+struct S
+{
+  S () { ++s; }
+  S (const S &) { ++s; }
+  ~S () { check (true); --s; }
+  [[omp::decl (threadprivate)]] static int s;
+};
+int S::s;
+S sv;
+struct T
+{
+  T (const S &, const S &) { ++t; }
+  T (const T &) { ++t; }
+  ~T () { check (false); --t; }
+  [[omp::decl (threadprivate)]] static int t;
+};
+int T::t;
+T tv (sv, sv);
+int a[4];
+int c;
+
+void
+check (bool is_s)
+{
+  if (c)
+    {
+      if (is_s)
+	{
+	  if (T::t != (c == 1))
+	    abort ();
+	}
+      else
+	{
+	  if (S::s != (c == 1 ? 0 : 2))
+	    abort ();
+	}
+    }
+}
+
+template <typename T>
+int *
+begin (const T &)
+{
+  return &a[0];
+}
+
+template <typename T>
+int *
+end (const T &)
+{
+  return &a[4];
+}
+
+const S &
+foo (const S &)
+{
+  return sv;
+}
+
+const T &
+foo (const T &)
+{
+  return tv;
+}
+
+void
+bar ()
+{
+  #pragma omp parallel num_threads (4)
+  {
+    if (S::s != 0)
+      abort ();
+    #pragma omp for
+    for (auto x : S ())
+      {
+	if (S::s != 1)
+	  abort ();
+      }
+    if (S::s != 0)
+      abort ();
+    #pragma omp for
+    for (auto x : foo (S ()))
+      {
+	if (S::s != (__cpp_range_based_for >= 202211L))
+	  abort ();
+      }
+    if (S::s != 0)
+      abort ();
+    if (T::t != 0)
+      abort ();
+  }
+  c = 1 + (__cpp_range_based_for >= 202211L);
+  #pragma omp parallel num_threads (4)
+  {
+    #pragma omp for
+    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 ();
+  }
+  c = 2;
+  #pragma omp parallel num_threads (4)
+  {
+    #pragma omp for
+    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 ();
+  }
+  c = 0;
+}
+
+template <int N>
+void
+baz ()
+{
+  #pragma omp parallel num_threads (4)
+  {
+    if (S::s != 0)
+      abort ();
+    #pragma omp for
+    for (auto x : S ())
+      {
+	if (S::s != 1)
+	  abort ();
+      }
+    if (S::s != 0)
+      abort ();
+    #pragma omp for
+    for (auto x : foo (S ()))
+      {
+	if (S::s != (__cpp_range_based_for >= 202211L))
+	  abort ();
+      }
+    if (S::s != 0)
+      abort ();
+    if (T::t != 0)
+      abort ();
+  }
+  c = 1 + (__cpp_range_based_for >= 202211L);
+  #pragma omp parallel num_threads (4)
+  {
+    #pragma omp for
+    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 ();
+  }
+  c = 2;
+  #pragma omp parallel num_threads (4)
+  {
+    #pragma omp for
+    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 ();
+  }
+  c = 0;
+}
+
+template <typename S, typename T>
+void
+qux ()
+{
+  #pragma omp parallel num_threads (4)
+  {
+    if (S::s != 0)
+      abort ();
+    #pragma omp for
+    for (auto x : S ())
+      {
+	if (S::s != 1)
+	  abort ();
+      }
+    if (S::s != 0)
+      abort ();
+    #pragma omp for
+    for (auto x : foo (S ()))
+      {
+	if (S::s != (__cpp_range_based_for >= 202211L))
+	  abort ();
+      }
+    if (S::s != 0)
+      abort ();
+    if (T::t != 0)
+      abort ();
+  }
+  c = 1 + (__cpp_range_based_for >= 202211L);
+  #pragma omp parallel num_threads (4)
+  {
+    #pragma omp for
+    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 ();
+  }
+  c = 2;
+  #pragma omp parallel num_threads (4)
+  {
+    #pragma omp for
+    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 ();
+  }
+  c = 0;
+}
+
+int
+main ()
+{
+  S::s--;
+  T::t--;
+  bar ();
+  baz <0> ();
+  qux <S, T> ();
+}
--- libgomp/testsuite/libgomp.c++/range-for-2.C.jj	2024-08-09 12:26:22.017300176 +0200
+++ libgomp/testsuite/libgomp.c++/range-for-2.C	2024-08-09 12:26:38.217091298 +0200
@@ -0,0 +1,6 @@ 
+// P2718R0 - Wording for P2644R1 Fix for Range-based for Loop
+// { dg-do run }
+// { dg-additional-options "-std=c++23" }
+// { dg-require-effective-target tls_runtime }
+
+#include "range-for-1.C"