public inbox for gcc-patches@gcc.gnu.org
 help / color / mirror / Atom feed
* [PATCH] c++: Implement C++23 P2266R1, Simpler implicit move [PR101165]
@ 2022-09-03 16:42 Marek Polacek
  2022-09-07  2:38 ` Jason Merrill
  0 siblings, 1 reply; 10+ messages in thread
From: Marek Polacek @ 2022-09-03 16:42 UTC (permalink / raw)
  To: Jason Merrill, GCC Patches

This patch implements https://wg21.link/p2266, which, once again,
changes the implicit move rules.  Here's a brief summary of various
changes in this area:

r125211: Introduced moving from certain lvalues when returning them
r171071: CWG 1148, enable move from value parameter on return
r212099: CWG 1579, it's OK to call a converting ctor taking an rvalue
r251035: CWG 1579, do maybe-rvalue overload resolution twice
r11-2411: Avoid calling const copy ctor on implicit move
r11-2412: C++20 implicit move changes, remove the fallback overload
          resolution, allow move on throw of parameters and implicit
	  move of rvalue references

P2266 enables the implicit move for functions that return references.  This
was a one-line change: check TYPE_REF_P.  That is, we will now perform
a move in

  X&& foo (X&& x) {
    return x;
  }

P2266 also removes the fallback overload resolution, but this was
resolved by r11-2412: we only do convert_for_initialization with
LOOKUP_PREFER_RVALUE in C++17 and older.
P2266 also says that a returned move-eligible id-expression is always an
xvalue.  This required some further short, but nontrivial changes,
especially when it comes to deduction, because we have to pay attention
to whether we have auto, auto&& (which is like T&&), or decltype(auto)
with (un)parenthesized argument.  In C++23,

  decltype(auto) f(int&& x) { return (x); }
  auto&& f(int x) { return x; }

both should deduce to 'int&&' but

  decltype(auto) f(int x) { return x; }

should deduce to 'int'.  A cornucopia of tests attached.  I've also
verified that we behave like clang++.

xvalue_p seemed to be broken: since the introduction of clk_implicit_rval,
it cannot use '==' when checking for clk_rvalueref.

Since this change breaks code, it's only enabled in C++23.  In
particular, this code will not compile in C++23:

  int& g(int&& x) { return x; }

because x is now treated as an rvalue, and you can't bind a non-const lvalue
reference to an rvalue.

There's one FIXME in elision1.C:five, which we should compile but reject
with "passing 'Mutt' as 'this' argument discards qualifiers".  That
looks bogus to me, I think I'll open a PR for it.

Bootstrapped/regtested on x86_64-pc-linux-gnu, ok for trunk?

	PR c++/101165

gcc/c-family/ChangeLog:

	* c-cppbuiltin.cc (c_cpp_builtins): Define __cpp_implicit_move.

gcc/cp/ChangeLog:

	* call.cc (reference_binding): Check clk_implicit_rval in C++20 only.
	* cp-tree.h (unparenthesized_id_or_class_member_access_p): Declare.
	* pt.cc (unparenthesized_id_or_class_member_access_p): New function,
	broken out of...
	(do_auto_deduction): ...here.  Use it.
	* tree.cc (xvalue_p): Check & clk_rvalueref, not == clk_rvalueref.
	* typeck.cc (check_return_expr): In C++23, maybe call
	treat_lvalue_as_rvalue_p before do_auto_deduction.  Allow implicit
	move for functions returning a reference as well.

gcc/testsuite/ChangeLog:

	* g++.dg/conversion/pr41426.C: Add dg-error for C++23.
	* g++.dg/cpp0x/elision_weak.C: Likewise.
	* g++.dg/cpp0x/move-return3.C: Only link in c++20_down.
	* g++.dg/cpp1y/decltype-auto2.C: Add dg-error for C++23.
	* g++.dg/cpp1y/lambda-generic-89419.C: Likewise.
	* g++.dg/cpp23/feat-cxx2b.C: Test __cpp_implicit_move.
	* g++.dg/gomp/pr56217.C: Only compile in c++20_down.
	* g++.dg/warn/Wno-return-local-addr.C: Add dg-error for C++23.
	* g++.dg/warn/Wreturn-local-addr.C: Adjust dg-error.
	* g++.old-deja/g++.brendan/crash55.C: Add dg-error for C++23.
	* g++.old-deja/g++.jason/temporary2.C: Likewise.
	* g++.old-deja/g++.mike/p2846b.C: Only run in c++20_down.
	* g++.dg/cpp1y/decltype-auto6.C: New test.
	* g++.dg/cpp23/decltype1.C: New test.
	* g++.dg/cpp23/decltype2.C: New test.
	* g++.dg/cpp23/elision1.C: New test.
	* g++.dg/cpp23/elision2.C: New test.
	* g++.dg/cpp23/elision3.C: New test.
	* g++.dg/cpp23/elision4.C: New test.
	* g++.dg/cpp23/elision5.C: New test.
	* g++.dg/cpp23/elision6.C: New test.
	* g++.dg/cpp23/elision7.C: New test.
---
 gcc/c-family/c-cppbuiltin.cc                  |   1 +
 gcc/cp/call.cc                                |   2 +-
 gcc/cp/cp-tree.h                              |   1 +
 gcc/cp/pt.cc                                  |  33 +++--
 gcc/cp/tree.cc                                |   2 +-
 gcc/cp/typeck.cc                              |  28 ++++-
 gcc/testsuite/g++.dg/conversion/pr41426.C     |  10 +-
 gcc/testsuite/g++.dg/cpp0x/elision_weak.C     |   4 +-
 gcc/testsuite/g++.dg/cpp0x/move-return3.C     |   3 +-
 gcc/testsuite/g++.dg/cpp1y/decltype-auto2.C   |   2 +-
 gcc/testsuite/g++.dg/cpp1y/decltype-auto6.C   |  19 +++
 .../g++.dg/cpp1y/lambda-generic-89419.C       |   6 +-
 gcc/testsuite/g++.dg/cpp23/decltype1.C        | 113 ++++++++++++++++++
 gcc/testsuite/g++.dg/cpp23/decltype2.C        |  49 ++++++++
 gcc/testsuite/g++.dg/cpp23/elision1.C         | 109 +++++++++++++++++
 gcc/testsuite/g++.dg/cpp23/elision2.C         |  46 +++++++
 gcc/testsuite/g++.dg/cpp23/elision3.C         |  16 +++
 gcc/testsuite/g++.dg/cpp23/elision4.C         |  38 ++++++
 gcc/testsuite/g++.dg/cpp23/elision5.C         |  53 ++++++++
 gcc/testsuite/g++.dg/cpp23/elision6.C         |  20 ++++
 gcc/testsuite/g++.dg/cpp23/elision7.C         |  72 +++++++++++
 gcc/testsuite/g++.dg/cpp23/feat-cxx2b.C       |   6 +
 gcc/testsuite/g++.dg/gomp/pr56217.C           |   4 +-
 .../g++.dg/warn/Wno-return-local-addr.C       |   2 +-
 .../g++.dg/warn/Wreturn-local-addr.C          |   2 +-
 .../g++.old-deja/g++.brendan/crash55.C        |   3 +-
 .../g++.old-deja/g++.jason/temporary2.C       |   2 +-
 gcc/testsuite/g++.old-deja/g++.mike/p2846b.C  |   4 +-
 28 files changed, 613 insertions(+), 37 deletions(-)
 create mode 100644 gcc/testsuite/g++.dg/cpp1y/decltype-auto6.C
 create mode 100644 gcc/testsuite/g++.dg/cpp23/decltype1.C
 create mode 100644 gcc/testsuite/g++.dg/cpp23/decltype2.C
 create mode 100644 gcc/testsuite/g++.dg/cpp23/elision1.C
 create mode 100644 gcc/testsuite/g++.dg/cpp23/elision2.C
 create mode 100644 gcc/testsuite/g++.dg/cpp23/elision3.C
 create mode 100644 gcc/testsuite/g++.dg/cpp23/elision4.C
 create mode 100644 gcc/testsuite/g++.dg/cpp23/elision5.C
 create mode 100644 gcc/testsuite/g++.dg/cpp23/elision6.C
 create mode 100644 gcc/testsuite/g++.dg/cpp23/elision7.C

diff --git a/gcc/c-family/c-cppbuiltin.cc b/gcc/c-family/c-cppbuiltin.cc
index a1557eb23d5..dd8b20d0c34 100644
--- a/gcc/c-family/c-cppbuiltin.cc
+++ b/gcc/c-family/c-cppbuiltin.cc
@@ -1081,6 +1081,7 @@ c_cpp_builtins (cpp_reader *pfile)
 	  cpp_define (pfile, "__cpp_constexpr=202110L");
 	  cpp_define (pfile, "__cpp_multidimensional_subscript=202110L");
 	  cpp_define (pfile, "__cpp_named_character_escapes=202207L");
+	  cpp_define (pfile, "__cpp_implicit_move=202207L");
 	}
       if (flag_concepts)
         {
diff --git a/gcc/cp/call.cc b/gcc/cp/call.cc
index d107a2814dc..cb4ae9ecf9d 100644
--- a/gcc/cp/call.cc
+++ b/gcc/cp/call.cc
@@ -1865,7 +1865,7 @@ reference_binding (tree rto, tree rfrom, tree expr, bool c_cast_p, int flags,
       /* Nor the reverse.  */
       if (!is_lvalue && !TYPE_REF_IS_RVALUE (rto)
 	  /* Unless it's really an lvalue.  */
-	  && !(cxx_dialect >= cxx20
+	  && !(cxx_dialect == cxx20
 	       && (gl_kind & clk_implicit_rval))
 	  && (!CP_TYPE_CONST_NON_VOLATILE_P (to)
 	      || (flags & LOOKUP_NO_RVAL_BIND))
diff --git a/gcc/cp/cp-tree.h b/gcc/cp/cp-tree.h
index 7b28405c3ac..4cdbbc34cb3 100644
--- a/gcc/cp/cp-tree.h
+++ b/gcc/cp/cp-tree.h
@@ -7278,6 +7278,7 @@ extern tree make_constrained_decltype_auto	(tree, tree);
 extern tree make_template_placeholder		(tree);
 extern bool template_placeholder_p		(tree);
 extern bool ctad_template_p			(tree);
+extern bool unparenthesized_id_or_class_member_access_p (tree);
 extern tree do_auto_deduction                   (tree, tree, tree,
                                                  tsubst_flags_t
 						 = tf_warning_or_error,
diff --git a/gcc/cp/pt.cc b/gcc/cp/pt.cc
index cd0d8920ed0..b347a9a78fc 100644
--- a/gcc/cp/pt.cc
+++ b/gcc/cp/pt.cc
@@ -30405,6 +30405,26 @@ do_class_deduction (tree ptype, tree tmpl, tree init,
 				  cp_type_quals (ptype));
 }
 
+/* Return true if INIT is an unparenthesized id-expression or an
+   unparenthesized class member access.  Used for the argument of
+   decltype(auto).  */
+
+bool
+unparenthesized_id_or_class_member_access_p (tree init)
+{
+  STRIP_ANY_LOCATION_WRAPPER (init);
+
+  /* We need to be able to tell '(r)' and 'r' apart (when it's of
+     reference type).  Only the latter is an id-expression.  */
+  if (REFERENCE_REF_P (init)
+      && !REF_PARENTHESIZED_P (init))
+    init = TREE_OPERAND (init, 0);
+  return (DECL_P (init)
+	  || ((TREE_CODE (init) == COMPONENT_REF
+	       || TREE_CODE (init) == SCOPE_REF)
+	      && !REF_PARENTHESIZED_P (init)));
+}
+
 /* Replace occurrences of 'auto' in TYPE with the appropriate type deduced
    from INIT.  AUTO_NODE is the TEMPLATE_TYPE_PARM used for 'auto' in TYPE.
    The CONTEXT determines the context in which auto deduction is performed
@@ -30501,18 +30521,7 @@ do_auto_deduction (tree type, tree init, tree auto_node,
     }
   else if (AUTO_IS_DECLTYPE (auto_node))
     {
-      /* Figure out if INIT is an unparenthesized id-expression or an
-	 unparenthesized class member access.  */
-      tree stripped_init = tree_strip_any_location_wrapper (init);
-      /* We need to be able to tell '(r)' and 'r' apart (when it's of
-	 reference type).  Only the latter is an id-expression.  */
-      if (REFERENCE_REF_P (stripped_init)
-	  && !REF_PARENTHESIZED_P (stripped_init))
-	stripped_init = TREE_OPERAND (stripped_init, 0);
-      const bool id = (DECL_P (stripped_init)
-		       || ((TREE_CODE (stripped_init) == COMPONENT_REF
-			    || TREE_CODE (stripped_init) == SCOPE_REF)
-			   && !REF_PARENTHESIZED_P (stripped_init)));
+      const bool id = unparenthesized_id_or_class_member_access_p (init);
       tree deduced = finish_decltype_type (init, id, complain);
       deduced = canonicalize_type_argument (deduced, complain);
       if (deduced == error_mark_node)
diff --git a/gcc/cp/tree.cc b/gcc/cp/tree.cc
index c678e3b9c4c..226ffd48519 100644
--- a/gcc/cp/tree.cc
+++ b/gcc/cp/tree.cc
@@ -382,7 +382,7 @@ obvalue_p (const_tree ref)
 bool
 xvalue_p (const_tree ref)
 {
-  return (lvalue_kind (ref) == clk_rvalueref);
+  return (lvalue_kind (ref) & clk_rvalueref);
 }
 
 /* True if REF is a bit-field.  */
diff --git a/gcc/cp/typeck.cc b/gcc/cp/typeck.cc
index b99947c10fd..fe88c1a7b84 100644
--- a/gcc/cp/typeck.cc
+++ b/gcc/cp/typeck.cc
@@ -10679,10 +10679,27 @@ check_return_expr (tree retval, bool *no_warning)
 	}
       else
 	{
-	  if (!retval)
-	    retval = void_node;
 	  auto_node = type_uses_auto (pattern);
-	  type = do_auto_deduction (pattern, retval, auto_node,
+	  tree r;
+	  if (!retval)
+	    r = void_node;
+	  /* In C++23, we must deduce the type to int&& for code like
+	       decltype(auto) f(int&& x) { return (x); }
+	     or
+	       auto&& f(int x) { return x; }
+	     so we use treat_lvalue_as_rvalue_p earlier.  But don't do it for
+	       decltype(auto) f(int x) { return x; }
+	     where we should deduce 'int' rather than 'int&&'; transmogrifying
+	     RETVAL to an rvalue would break that.  */
+	  else if (cxx_dialect >= cxx23
+		   && (!AUTO_IS_DECLTYPE (auto_node)
+		       || !unparenthesized_id_or_class_member_access_p (retval))
+		   && (r = treat_lvalue_as_rvalue_p
+		       (maybe_undo_parenthesized_ref (retval), /*return*/true)))
+	    /* Use it.  */;
+	  else
+	    r = retval;
+	  type = do_auto_deduction (pattern, r, auto_node,
 				    tf_warning_or_error, adc_return_type);
 	}
 
@@ -10876,8 +10893,9 @@ check_return_expr (tree retval, bool *no_warning)
 	 the conditions for the named return value optimization.  */
       bool converted = false;
       tree moved;
-      /* This is only interesting for class type.  */
-      if (CLASS_TYPE_P (functype)
+      /* Until C++23, this was only interesting for class type.  */
+      if ((CLASS_TYPE_P (functype)
+	   || (cxx_dialect >= cxx23 && TYPE_REF_P (functype)))
 	  && (moved = treat_lvalue_as_rvalue_p (retval, /*return*/true)))
 	{
 	  if (cxx_dialect < cxx20)
diff --git a/gcc/testsuite/g++.dg/conversion/pr41426.C b/gcc/testsuite/g++.dg/conversion/pr41426.C
index 5493a91ecfa..b4ecbca5f3a 100644
--- a/gcc/testsuite/g++.dg/conversion/pr41426.C
+++ b/gcc/testsuite/g++.dg/conversion/pr41426.C
@@ -11,19 +11,20 @@ struct A
 A<float> g1()
 {
    float f[] = {1.1f, 2.3f};
-   return f;
+   return f; // { dg-error "cannot bind non-const" "" { target c++23 } }
 }
 
 const A<float> &g3()
 {
    float f[] = {1.1f, 2.3f};
-   return f; // { dg-warning "returning reference to temporary" }
+   return f; // { dg-warning "returning reference to temporary" "" { target c++20_down } }
+// { dg-error "non-const lvalue|invalid user-defined conversion" "" { target c++23 } .-1 }
 }
 
 A<float> &g4()
 {
    float f[] = {1.1f, 2.3f};
-   return f; // { dg-error "cannot bind non-const lvalue ref" }
+   return f; // { dg-error "cannot bind non-const lvalue ref|invalid user-defined conversion" }
 }
 
 struct B
@@ -35,6 +36,5 @@ struct B
 B g2()
 {
    int c[10];
-   return c;
+   return c; // { dg-error "non-const lvalue" "" { target c++23 } }
 }
-
diff --git a/gcc/testsuite/g++.dg/cpp0x/elision_weak.C b/gcc/testsuite/g++.dg/cpp0x/elision_weak.C
index e8ba7551d84..ddd12743130 100644
--- a/gcc/testsuite/g++.dg/cpp0x/elision_weak.C
+++ b/gcc/testsuite/g++.dg/cpp0x/elision_weak.C
@@ -9,11 +9,11 @@ struct S
 S f()
 {
   S s;
-  return s;
+  return s; // { dg-error "cannot bind non-const lvalue reference" "" { target c++23 } }
 }
 
 void g()
 {
   S s;
-  throw s;
+  throw s; // { dg-error "cannot bind non-const lvalue reference" "" { target c++23 } }
 }
diff --git a/gcc/testsuite/g++.dg/cpp0x/move-return3.C b/gcc/testsuite/g++.dg/cpp0x/move-return3.C
index c79f0591936..30a936fb35a 100644
--- a/gcc/testsuite/g++.dg/cpp0x/move-return3.C
+++ b/gcc/testsuite/g++.dg/cpp0x/move-return3.C
@@ -1,6 +1,7 @@
 // PR c++/91212
 // Test that C++11 implicit move semantics don't call the const copy.
-// { dg-do link }
+// In C++23, we call #2.
+// { dg-do link { target c++20_down } }
 
 struct T { int i; };
 
diff --git a/gcc/testsuite/g++.dg/cpp1y/decltype-auto2.C b/gcc/testsuite/g++.dg/cpp1y/decltype-auto2.C
index 56e011e36f4..24b32edfacf 100644
--- a/gcc/testsuite/g++.dg/cpp1y/decltype-auto2.C
+++ b/gcc/testsuite/g++.dg/cpp1y/decltype-auto2.C
@@ -8,5 +8,5 @@ auto constexpr RtoL1(T&& r) -> decltype(auto) {
 int main() {
     int t;
     int x{3};
-    decltype (RtoL1(x+0)) y = t;
+    decltype (RtoL1(x+0)) y = t; // { dg-error "cannot bind rvalue reference" "" { target c++23 } }
 }
diff --git a/gcc/testsuite/g++.dg/cpp1y/decltype-auto6.C b/gcc/testsuite/g++.dg/cpp1y/decltype-auto6.C
new file mode 100644
index 00000000000..da53278645c
--- /dev/null
+++ b/gcc/testsuite/g++.dg/cpp1y/decltype-auto6.C
@@ -0,0 +1,19 @@
+// PR c++/101165 - P2266R1 - Simpler implicit move
+// { dg-do compile { target c++14 } }
+// A variant of cxx23/elision1.C:eight, just with ().
+
+struct Widget {
+  Widget(Widget&&);
+};
+
+Widget val();
+
+decltype(auto)
+foo ()
+{
+  decltype(auto) x = val();  // OK, x is Widget
+  // We deduce the return type to int&&, therefore we're doing something
+  // we ought not to be doing -- returning a reference to a local variable!
+  // In C++20, we deduce to int&, but that has the same problem!
+  return (x); // { dg-warning "reference to local variable" }
+}
diff --git a/gcc/testsuite/g++.dg/cpp1y/lambda-generic-89419.C b/gcc/testsuite/g++.dg/cpp1y/lambda-generic-89419.C
index 46ce909f3b8..8e64d4e64ab 100644
--- a/gcc/testsuite/g++.dg/cpp1y/lambda-generic-89419.C
+++ b/gcc/testsuite/g++.dg/cpp1y/lambda-generic-89419.C
@@ -2,7 +2,7 @@
 // { dg-do compile { target c++14 } }
 
 struct A;
-struct B {
+struct B { // { dg-error "cannot bind" "" { target c++23 } }
   struct C { C (); C (C &); } b;
 };
 struct D { A operator* (); };
@@ -13,12 +13,12 @@ struct E {
   auto bar () { return e; }
   D e;
 };
-struct F { B f; int g; };
+struct F { B f; int g; }; // { dg-error "use of deleted function" "" { target c++23 } }
 
 int
 main ()
 {
   E e;
   auto f = *e.bar ();
-  auto i = [&] { F g; g.g = 1; auto h = [&](auto) { g.g = 0; }; f.foo (h); return g; };
+  auto i = [&] { F g; g.g = 1; auto h = [&](auto) { g.g = 0; }; f.foo (h); return g; }; // { dg-error "use of deleted function" "" { target c++23 } }
 }
diff --git a/gcc/testsuite/g++.dg/cpp23/decltype1.C b/gcc/testsuite/g++.dg/cpp23/decltype1.C
new file mode 100644
index 00000000000..6f3cd0d45d5
--- /dev/null
+++ b/gcc/testsuite/g++.dg/cpp23/decltype1.C
@@ -0,0 +1,113 @@
+// PR c++/101165 - P2266R1 - Simpler implicit move
+// { dg-do compile { target c++23 } }
+// Tests from P2266R1, decltype-related changes in
+// $ 3.2.1. Interaction with decltype and decltype(auto)
+
+template<typename T, typename U>
+struct same_type { static const bool value = false; };
+
+template<typename T>
+struct same_type<T, T> { static const bool value = true; };
+
+auto f1(int x) -> decltype(x) { return (x); }
+static_assert(same_type<decltype(f1), int (int)>::value);
+auto f2(int x) -> decltype((x)) { return (x); }   // { dg-error "cannot bind" }
+static_assert(same_type<decltype(f2), int& (int)>::value);
+auto f3(int x) -> decltype(auto) { return (x); }  // { dg-warning "reference to local variable" }
+static_assert(same_type<decltype(f3), int&& (int)>::value);
+auto g1(int x) -> decltype(x) { return x; }
+static_assert(same_type<decltype(g1), int (int)>::value);
+auto g2(int x) -> decltype((x)) { return x; }	  // { dg-error "cannot bind" }
+static_assert(same_type<decltype(g2), int& (int)>::value);
+auto g3(int x) -> decltype(auto) { return x; }
+static_assert(same_type<decltype(g3), int (int)>::value);
+
+// Note that f2 and g2 are well-formed in C++20, but we propose to make
+// f2 and g2 ill-formed, because they attempt to bind an lvalue reference
+// to a move-eligible xvalue expression.
+
+struct X { };
+
+auto
+f4 (X x)
+{
+  return x;
+}
+static_assert(same_type<decltype(f4), X(X)>::value);
+
+auto&
+f5 (X x)
+{
+  return x; // { dg-error "cannot bind non-const lvalue reference" }
+}
+static_assert(same_type<decltype(f5), X&(X)>::value);
+
+auto&&
+f6 (X x)
+{
+  return x; // { dg-warning "reference to local variable" }
+}
+static_assert(same_type<decltype(f6), X&&(X)>::value);
+
+auto
+f7 (X x)
+{
+  return (x);
+}
+static_assert(same_type<decltype(f7), X(X)>::value);
+
+auto&
+f8 (X x)
+{
+  return (x); // { dg-error "cannot bind non-const lvalue reference" }
+}
+static_assert(same_type<decltype(f8), X&(X)>::value);
+
+auto&&
+f9 (X x)
+{
+  return (x); // { dg-warning "reference to local variable" }
+}
+static_assert(same_type<decltype(f9), X&&(X)>::value);
+
+decltype(auto)
+f10 (X x)
+{
+  return x;
+}
+static_assert(same_type<decltype(f10), X(X)>::value);
+
+decltype(auto)
+f11 (X x)
+{
+  return (x); // { dg-warning "reference to local variable" }
+}
+static_assert(same_type<decltype(f11), X&&(X)>::value);
+
+decltype(auto)
+f12 (X& x)
+{
+  return x;
+}
+static_assert(same_type<decltype(f12), X&(X&)>::value);
+
+decltype(auto)
+f13 (X& x)
+{
+  return (x);
+}
+static_assert(same_type<decltype(f13), X&(X&)>::value);
+
+decltype(auto)
+f14 (X&& x)
+{
+  return x;
+}
+static_assert(same_type<decltype(f14), X&&(X&&)>::value);
+
+decltype(auto)
+f15 (X&& x)
+{
+  return (x);
+}
+static_assert(same_type<decltype(f15), X&&(X&&)>::value);
diff --git a/gcc/testsuite/g++.dg/cpp23/decltype2.C b/gcc/testsuite/g++.dg/cpp23/decltype2.C
new file mode 100644
index 00000000000..84679c48f82
--- /dev/null
+++ b/gcc/testsuite/g++.dg/cpp23/decltype2.C
@@ -0,0 +1,49 @@
+// PR c++/101165 - P2266R1 - Simpler implicit move
+// { dg-do compile { target c++23 } }
+// Test decltype(auto) more.
+
+template<typename T, typename U>
+struct same_type { static const bool value = false; };
+
+template<typename T>
+struct same_type<T, T> { static const bool value = true; };
+
+struct Widget {
+  int x;
+};
+
+Widget wg;
+
+decltype(auto) fn0(Widget&& x) {
+    return (::wg);
+}
+static_assert(same_type<decltype(fn0), Widget& (Widget&&)>::value);
+
+decltype(auto) fn1(Widget&& x) {
+    return ::wg;
+}
+static_assert(same_type<decltype(fn1), Widget (Widget&&)>::value);
+
+decltype(auto) fn2() {
+    Widget w;
+    return w;
+}
+static_assert(same_type<decltype(fn2), Widget ()>::value);
+
+decltype(auto) fn3() {
+    Widget w;
+    return (w); // { dg-warning "reference to local variable" }
+}
+static_assert(same_type<decltype(fn3), Widget&& ()>::value);
+
+decltype(auto) fn4() {
+    Widget w;
+    return w.x;
+}
+static_assert(same_type<decltype(fn4), int ()>::value);
+
+decltype(auto) fn5() {
+    Widget w;
+    return (w.x); // { dg-warning "reference to local variable" }
+}
+static_assert(same_type<decltype(fn5), int& ()>::value);
diff --git a/gcc/testsuite/g++.dg/cpp23/elision1.C b/gcc/testsuite/g++.dg/cpp23/elision1.C
new file mode 100644
index 00000000000..8115739b7f9
--- /dev/null
+++ b/gcc/testsuite/g++.dg/cpp23/elision1.C
@@ -0,0 +1,109 @@
+// PR c++/101165 - P2266R1 - Simpler implicit move
+// { dg-do compile { target c++23 } }
+// Tests from P2266R1.
+
+namespace std {
+  template<typename _Tp>
+    struct remove_reference
+    { typedef _Tp   type; };
+
+  template<typename _Tp>
+    struct remove_reference<_Tp&>
+    { typedef _Tp   type; };
+
+  template<typename _Tp>
+    struct remove_reference<_Tp&&>
+    { typedef _Tp   type; };
+
+  template<typename _Tp>
+    constexpr typename std::remove_reference<_Tp>::type&&
+    move(_Tp&& __t) noexcept
+    { return static_cast<typename std::remove_reference<_Tp>::type&&>(__t); }
+}
+
+template<typename T, typename U>
+struct same_type { static const bool value = false; };
+
+template<typename T>
+struct same_type<T, T> { static const bool value = true; };
+
+struct Widget {
+    Widget(Widget&&);
+};
+
+struct RRefTaker {
+    RRefTaker(Widget&&);
+};
+
+struct Mutt {
+    operator int*() &&;
+};
+
+struct Jeff {
+    operator int&() &&;
+};
+
+Widget one(Widget w) {
+    return w;  // OK since C++11
+}
+
+RRefTaker two(Widget w) {
+    return w;  // OK since C++11 + CWG1579
+}
+
+RRefTaker three(Widget&& w) {
+    return w;  // OK since C++20 because P0527
+}
+
+// Tests that implicit move applies even to functions that return references.
+Widget&& four(Widget&& w) {
+    return w;  // OK since C++23
+}
+
+// FIXME This is supposed to work but we reject it with
+// error: passing 'Mutt' as 'this' argument discards qualifiers
+#if 0
+int* five(Mutt x) {
+    return x;  // OK since C++20 because P1155
+}
+#endif
+
+int& six(Jeff x) {
+    return x;
+}
+
+template<class T>
+T&& seven(T&& x) { return x; }
+
+void test_seven(Widget w) {
+    Widget& r = seven(w);
+    Widget&& rr = seven(std::move(w));
+}
+
+Widget val();
+Widget& lref();
+Widget&& rref();
+
+decltype(auto) eight() {
+    decltype(auto) x = val();  // OK, x is Widget
+    return x;  // OK, return type is Widget, we get copy elision
+}
+
+decltype(auto) nine() {
+    decltype(auto) x = lref();  // OK, x is Widget&
+    return x;  // OK, return type is Widget&
+}
+
+decltype(auto) ten() {
+  decltype(auto) x = rref();  // OK, x is Widget&&
+  // This was an error: return type is Widget&&, cannot bind to x.
+  // But in C++23, x is treated as an rvalue.
+  return x;
+}
+
+// Now returns Widget&&, not Widget&.
+// This is from $ 3.2.1. Interaction with decltype and decltype(auto).
+decltype(auto) eleven(Widget&& x) {
+    return (x);
+}
+static_assert(same_type<decltype(eleven), Widget&& (Widget&&)>::value);
diff --git a/gcc/testsuite/g++.dg/cpp23/elision2.C b/gcc/testsuite/g++.dg/cpp23/elision2.C
new file mode 100644
index 00000000000..ce2c7aeef66
--- /dev/null
+++ b/gcc/testsuite/g++.dg/cpp23/elision2.C
@@ -0,0 +1,46 @@
+// PR c++/101165 - P2266R1 - Simpler implicit move
+// { dg-do compile { target c++20 } }
+// Test from P2266R1, $ 3.3. Two overload resolutions are overly confusing.
+
+struct Widget {
+    Widget();
+    Widget(Widget&&);
+};
+
+struct Frodo {
+    Frodo(Widget&);
+    Frodo(Widget&&) = delete;
+};
+
+struct Sam {
+    Sam(Widget&) = delete; // #1
+    Sam(const Widget&);  // #2
+};
+
+Sam twelve() {
+    Widget w;
+    // This is supposed to calls #2 since C++20 because P1155.
+    // But we actually choose #1 since r11-2411 (in C++20 only).
+    return w; // { dg-error "deleted" "" { target c++20_only } }
+}
+
+Frodo thirteen() {
+    Widget w;
+    // This is a correct error in both C++20 and C++23.
+    return w;  // { dg-error "use of deleted function" }
+}
+
+struct Merry {};
+struct Pippin {};
+struct Together : Merry, Pippin {};
+struct Quest {
+    Quest(Merry&&);
+    Quest(Pippin&&);
+    Quest(Together&);
+};
+
+Quest fourteen() {
+  Together t;
+  // C++20: calls Quest(Together&).  Proposed: ill-formed.
+  return t; // { dg-error "ambiguous" "" { target c++23 } }
+}
diff --git a/gcc/testsuite/g++.dg/cpp23/elision3.C b/gcc/testsuite/g++.dg/cpp23/elision3.C
new file mode 100644
index 00000000000..246342e64d3
--- /dev/null
+++ b/gcc/testsuite/g++.dg/cpp23/elision3.C
@@ -0,0 +1,16 @@
+// PR c++/101165 - P2266R1 - Simpler implicit move
+// { dg-do compile { target c++23 } }
+// Test from P2266R1, $ 3.4. A specific case involving reference_wrapper.
+
+#include <functional>
+
+struct Widget {
+    Widget();
+    Widget(Widget&&);
+};
+
+std::reference_wrapper<Widget> fifteen() {
+    Widget w;
+    // OK until CWG1579; OK after LWG2993.  Proposed: ill-formed
+    return w;  // { dg-error "could not convert" }
+}
diff --git a/gcc/testsuite/g++.dg/cpp23/elision4.C b/gcc/testsuite/g++.dg/cpp23/elision4.C
new file mode 100644
index 00000000000..c19b86b8b5f
--- /dev/null
+++ b/gcc/testsuite/g++.dg/cpp23/elision4.C
@@ -0,0 +1,38 @@
+// PR c++/101165 - P2266R1 - Simpler implicit move
+// { dg-do compile { target c++23 } }
+// Test from P2266R1, $ 5.2. LibreOffice OString constructor.
+
+struct X {
+    X(auto&);
+};
+
+// The following compiles in C++20 (deducing X(char (&)[10])) but not
+// after P2266 (because the returned expression now has type char (&&)[10],
+// which cannot bind to auto&).
+X f() {
+    char a[10];
+    return a; // { dg-error "cannot bind non-const lvalue reference" }
+}
+
+// The solution was to change it by making the return convert explicitly
+// rather than implicitly:
+X fixed() {
+    char a[10];
+    return X(a);
+}
+
+// $ 5.3. LibreOffice o3tl::temporary
+
+template<class T>
+T& temporary1(T&& x) { return x; } // { dg-error "cannot bind non-const lvalue reference" }
+
+// Fixed by:
+template<class T>
+T& temporary2(T&& x) { return static_cast<T&>(x); }
+
+void
+test ()
+{
+  int& r1 = temporary1 (42);
+  int& r2 = temporary2 (42);
+}
diff --git a/gcc/testsuite/g++.dg/cpp23/elision5.C b/gcc/testsuite/g++.dg/cpp23/elision5.C
new file mode 100644
index 00000000000..a7d3e7c27c4
--- /dev/null
+++ b/gcc/testsuite/g++.dg/cpp23/elision5.C
@@ -0,0 +1,53 @@
+// PR c++/101165 - P2266R1 - Simpler implicit move
+// { dg-do compile { target c++23 } }
+// Test from [class.copy.elision]/4.
+
+class Thing {
+public:
+  Thing();
+  ~Thing();
+  Thing(Thing&&);
+private:
+  Thing(const Thing&);
+};
+
+Thing f(bool b) {
+  Thing t;
+  if (b)
+    throw t;            // OK, Thing(Thing&&) used (or elided) to throw t
+  return t;             // OK, Thing(Thing&&) used (or elided) to return t
+}
+
+Thing t2 = f(false);    // OK, no extra copy/move performed, t2 constructed by call to f
+
+struct Weird {
+  Weird();
+  Weird(Weird&);
+};
+
+Weird g(bool b) {
+  static Weird w1;
+  Weird w2;
+  if (b) {
+    return w1;  // OK: Weird(Weird&)
+  } else {
+    return w2;  // { dg-error "cannot bind non-const lvalue reference" }
+  }
+}
+
+int& h(bool b, int i) {
+  static int s;
+  if (b)
+    return s;   // OK
+  else
+    return i;   // { dg-error "cannot bind non-const lvalue reference" }
+}
+
+decltype(auto) h2(Thing t) {
+  return t;     // OK, t is an xvalue and h2's return type is Thing
+}
+
+decltype(auto) h3(Thing t) {
+  // OK, (t) is an xvalue and h3's return type is Thing&&
+  return (t); // { dg-warning "reference to local variable" }
+}
diff --git a/gcc/testsuite/g++.dg/cpp23/elision6.C b/gcc/testsuite/g++.dg/cpp23/elision6.C
new file mode 100644
index 00000000000..5d58da9e577
--- /dev/null
+++ b/gcc/testsuite/g++.dg/cpp23/elision6.C
@@ -0,0 +1,20 @@
+// PR c++/101165 - P2266R1 - Simpler implicit move
+// { dg-do compile { target c++23 } }
+// From [diff.cpp20.expr].
+
+template<typename T, typename U>
+struct same_type { static const bool value = false; };
+
+template<typename T>
+struct same_type<T, T> { static const bool value = true; };
+
+// In C++23, returns int&&; previously returned int&.
+decltype(auto) f(int&& x) { return (x); }
+static_assert(same_type<decltype(f), int&& (int&&)>::value);
+
+// This used to work in C++20.
+int& g(int&& x) { return x; } // { dg-error "cannot bind non-const lvalue reference" }
+
+template<typename T>
+decltype(auto) h(T&& x) { return (x); }
+static_assert(same_type<decltype(h(42)), int&&>::value);
diff --git a/gcc/testsuite/g++.dg/cpp23/elision7.C b/gcc/testsuite/g++.dg/cpp23/elision7.C
new file mode 100644
index 00000000000..19fa89ae133
--- /dev/null
+++ b/gcc/testsuite/g++.dg/cpp23/elision7.C
@@ -0,0 +1,72 @@
+// PR c++/101165 - P2266R1 - Simpler implicit move
+// { dg-do compile { target c++23 } }
+
+struct X {
+  X ();
+  X(X&&);
+};
+
+X&& rref ();
+
+X&&
+f1 (X&& x)
+{
+  return x;
+}
+
+template<typename T> T&&
+f2 (T&& x)
+{
+  return x;
+}
+template X& f2<X&>(X&);
+template X&& f2<X>(X&&);
+
+X&&
+f3 ()
+{
+  X&& x = rref ();
+  return x;
+}
+
+void
+f4 ()
+try {
+  X x;
+  throw x;
+} catch (...) { }
+
+void
+f5 ()
+{
+  auto l1 = [](auto x) -> auto { return x; };
+  auto &&x1 = l1(X{});
+  auto l2 = [](auto x) -> auto& { return x; }; // { dg-error "cannot bind non-const lvalue reference" }
+  auto &&x2 = l2(X{});
+  auto l3 = [](auto x) -> auto&& { return x; }; // { dg-warning "reference to local" }
+  auto &&x3 = l3(X{});
+}
+
+constexpr int &
+f6 (int &&n)
+{
+  return n; // { dg-error "cannot bind non-const lvalue reference" }
+}
+
+void
+do_f6 ()
+{
+  auto x = f6 (42);
+}
+
+template<typename T> auto &
+f7 (T &&t)
+{
+  return t; // { dg-error "cannot bind non-const lvalue reference" }
+}
+
+void
+do_f7 ()
+{
+  const int &x = f7 (0);
+}
diff --git a/gcc/testsuite/g++.dg/cpp23/feat-cxx2b.C b/gcc/testsuite/g++.dg/cpp23/feat-cxx2b.C
index d3e40724085..20453bb7b14 100644
--- a/gcc/testsuite/g++.dg/cpp23/feat-cxx2b.C
+++ b/gcc/testsuite/g++.dg/cpp23/feat-cxx2b.C
@@ -563,3 +563,9 @@
 #elif __cpp_named_character_escapes != 202207
 #  error "__cpp_named_character_escapes != 202207"
 #endif
+
+#ifndef __cpp_implicit_move
+#  error "__cpp_implicit_move"
+#elif __cpp_implicit_move != 202207
+#  error "__cpp_implicit_move != 202207"
+#endif
diff --git a/gcc/testsuite/g++.dg/gomp/pr56217.C b/gcc/testsuite/g++.dg/gomp/pr56217.C
index 03dfc5f180b..731c0c08811 100644
--- a/gcc/testsuite/g++.dg/gomp/pr56217.C
+++ b/gcc/testsuite/g++.dg/gomp/pr56217.C
@@ -1,5 +1,5 @@
 // PR middle-end/56217
-// { dg-do compile }
+// { dg-do compile { target c++20_down } }
 // { dg-options "-fopenmp" }
 
 struct S { int *p; S (); S (S &); };
@@ -10,5 +10,7 @@ foo ()
   S s;
   #pragma omp task shared (s)
     s.p = 0;
+  // This fails in C++23, because "cannot bind non-const lvalue reference of
+  // type 'S&' to an rvalue of type 'S'".
   return s;
 }
diff --git a/gcc/testsuite/g++.dg/warn/Wno-return-local-addr.C b/gcc/testsuite/g++.dg/warn/Wno-return-local-addr.C
index e15bfa24f54..cc9bb59770e 100644
--- a/gcc/testsuite/g++.dg/warn/Wno-return-local-addr.C
+++ b/gcc/testsuite/g++.dg/warn/Wno-return-local-addr.C
@@ -4,7 +4,7 @@
 int& bad1()
 {
   int x = 0;
-  return x;
+  return x; // { dg-error "cannot bind non-const lvalue reference" "" { target c++23 } }
 }
 
 int* bad2()
diff --git a/gcc/testsuite/g++.dg/warn/Wreturn-local-addr.C b/gcc/testsuite/g++.dg/warn/Wreturn-local-addr.C
index 642a5767e84..4c18c2f06a0 100644
--- a/gcc/testsuite/g++.dg/warn/Wreturn-local-addr.C
+++ b/gcc/testsuite/g++.dg/warn/Wreturn-local-addr.C
@@ -5,7 +5,7 @@
 int& bad1()
 {
   int x = 0;
-  return x;		// { dg-error "reference to local variable" }
+  return x;		// { dg-error "reference to local variable|cannot bind non-const lvalue reference" }
 }
 
 int* bad2()
diff --git a/gcc/testsuite/g++.old-deja/g++.brendan/crash55.C b/gcc/testsuite/g++.old-deja/g++.brendan/crash55.C
index fd4d4b65edb..b93e6e0c695 100644
--- a/gcc/testsuite/g++.old-deja/g++.brendan/crash55.C
+++ b/gcc/testsuite/g++.old-deja/g++.brendan/crash55.C
@@ -8,5 +8,6 @@
 
           local = x+2;
       
-          return local; // { dg-warning "reference to local" }
+          return local; // { dg-warning "reference to local" "" { target c++20_down } }
+// { dg-error "non-const lvalue" "" { target c++23 } .-1 }
       }
diff --git a/gcc/testsuite/g++.old-deja/g++.jason/temporary2.C b/gcc/testsuite/g++.old-deja/g++.jason/temporary2.C
index c855f8f4a07..2709b50e7f1 100644
--- a/gcc/testsuite/g++.old-deja/g++.jason/temporary2.C
+++ b/gcc/testsuite/g++.old-deja/g++.jason/temporary2.C
@@ -8,7 +8,7 @@ public:
   int i;
 };
 
-X foo() { X x; return x; }
+X foo() { X x; return x; } // { dg-error "cannot bind non-const lvalue reference" "" { target c++23 } }
 
 int main() 
 {
diff --git a/gcc/testsuite/g++.old-deja/g++.mike/p2846b.C b/gcc/testsuite/g++.old-deja/g++.mike/p2846b.C
index 57422fe64df..68826649cfc 100644
--- a/gcc/testsuite/g++.old-deja/g++.mike/p2846b.C
+++ b/gcc/testsuite/g++.old-deja/g++.mike/p2846b.C
@@ -1,4 +1,4 @@
-// { dg-do run  }
+// { dg-do run { target c++20_down } }
 // Shows that problem of initializing one object's secondary base from
 // another object via a user defined copy constructor for that base,
 // the pointer for the secondary vtable is not set after implicit
@@ -11,6 +11,8 @@
 
 // prms-id: 2846
 
+// This test fails in C++23 due to P2266.
+
 extern "C" int printf(const char *, ...);
 extern "C" void exit(int);
 

base-commit: 08de065293f8b08158e1089fbacce9dbaba95077
-- 
2.37.2


^ permalink raw reply	[flat|nested] 10+ messages in thread

* Re: [PATCH] c++: Implement C++23 P2266R1, Simpler implicit move [PR101165]
  2022-09-03 16:42 [PATCH] c++: Implement C++23 P2266R1, Simpler implicit move [PR101165] Marek Polacek
@ 2022-09-07  2:38 ` Jason Merrill
  2022-09-08 22:54   ` Marek Polacek
  2022-09-20 18:19   ` [PATCH v2] " Marek Polacek
  0 siblings, 2 replies; 10+ messages in thread
From: Jason Merrill @ 2022-09-07  2:38 UTC (permalink / raw)
  To: Marek Polacek, GCC Patches

On 9/3/22 12:42, Marek Polacek wrote:
> This patch implements https://wg21.link/p2266, which, once again,
> changes the implicit move rules.  Here's a brief summary of various
> changes in this area:
> 
> r125211: Introduced moving from certain lvalues when returning them
> r171071: CWG 1148, enable move from value parameter on return
> r212099: CWG 1579, it's OK to call a converting ctor taking an rvalue
> r251035: CWG 1579, do maybe-rvalue overload resolution twice
> r11-2411: Avoid calling const copy ctor on implicit move
> r11-2412: C++20 implicit move changes, remove the fallback overload
>            resolution, allow move on throw of parameters and implicit
> 	  move of rvalue references
> 
> P2266 enables the implicit move for functions that return references.  This
> was a one-line change: check TYPE_REF_P.  That is, we will now perform
> a move in
> 
>    X&& foo (X&& x) {
>      return x;
>    }
> 
> P2266 also removes the fallback overload resolution, but this was
> resolved by r11-2412: we only do convert_for_initialization with
> LOOKUP_PREFER_RVALUE in C++17 and older.

I wonder if we want to extend the current C++20 handling to the older 
modes for GCC 13?  Not in this patch, but as a followup.

> P2266 also says that a returned move-eligible id-expression is always an
> xvalue.  This required some further short, but nontrivial changes,
> especially when it comes to deduction, because we have to pay attention
> to whether we have auto, auto&& (which is like T&&), or decltype(auto)
> with (un)parenthesized argument.  In C++23,
> 
>    decltype(auto) f(int&& x) { return (x); }
>    auto&& f(int x) { return x; }
> 
> both should deduce to 'int&&' but
> 
>    decltype(auto) f(int x) { return x; }
> 
> should deduce to 'int'.  A cornucopia of tests attached.  I've also
> verified that we behave like clang++.
> 
> xvalue_p seemed to be broken: since the introduction of clk_implicit_rval,
> it cannot use '==' when checking for clk_rvalueref.
> 
> Since this change breaks code, it's only enabled in C++23.  In
> particular, this code will not compile in C++23:
> 
>    int& g(int&& x) { return x; }

Nice that the C++20 compatibility is so simple!

> because x is now treated as an rvalue, and you can't bind a non-const lvalue
> reference to an rvalue.
> 
> There's one FIXME in elision1.C:five, which we should compile but reject
> with "passing 'Mutt' as 'this' argument discards qualifiers".  That
> looks bogus to me, I think I'll open a PR for it.

Let's fix that now, I think.

> Bootstrapped/regtested on x86_64-pc-linux-gnu, ok for trunk?
> 
> 	PR c++/101165
> 
> gcc/c-family/ChangeLog:
> 
> 	* c-cppbuiltin.cc (c_cpp_builtins): Define __cpp_implicit_move.
> 
> gcc/cp/ChangeLog:
> 
> 	* call.cc (reference_binding): Check clk_implicit_rval in C++20 only.
> 	* cp-tree.h (unparenthesized_id_or_class_member_access_p): Declare.
> 	* pt.cc (unparenthesized_id_or_class_member_access_p): New function,
> 	broken out of...
> 	(do_auto_deduction): ...here.  Use it.
> 	* tree.cc (xvalue_p): Check & clk_rvalueref, not == clk_rvalueref.
> 	* typeck.cc (check_return_expr): In C++23, maybe call
> 	treat_lvalue_as_rvalue_p before do_auto_deduction.  Allow implicit
> 	move for functions returning a reference as well.
> 
> gcc/testsuite/ChangeLog:
> 
> 	* g++.dg/conversion/pr41426.C: Add dg-error for C++23.
> 	* g++.dg/cpp0x/elision_weak.C: Likewise.
> 	* g++.dg/cpp0x/move-return3.C: Only link in c++20_down.
> 	* g++.dg/cpp1y/decltype-auto2.C: Add dg-error for C++23.
> 	* g++.dg/cpp1y/lambda-generic-89419.C: Likewise.
> 	* g++.dg/cpp23/feat-cxx2b.C: Test __cpp_implicit_move.
> 	* g++.dg/gomp/pr56217.C: Only compile in c++20_down.
> 	* g++.dg/warn/Wno-return-local-addr.C: Add dg-error for C++23.
> 	* g++.dg/warn/Wreturn-local-addr.C: Adjust dg-error.
> 	* g++.old-deja/g++.brendan/crash55.C: Add dg-error for C++23.
> 	* g++.old-deja/g++.jason/temporary2.C: Likewise.
> 	* g++.old-deja/g++.mike/p2846b.C: Only run in c++20_down.
> 	* g++.dg/cpp1y/decltype-auto6.C: New test.
> 	* g++.dg/cpp23/decltype1.C: New test.
> 	* g++.dg/cpp23/decltype2.C: New test.
> 	* g++.dg/cpp23/elision1.C: New test.
> 	* g++.dg/cpp23/elision2.C: New test.
> 	* g++.dg/cpp23/elision3.C: New test.
> 	* g++.dg/cpp23/elision4.C: New test.
> 	* g++.dg/cpp23/elision5.C: New test.
> 	* g++.dg/cpp23/elision6.C: New test.
> 	* g++.dg/cpp23/elision7.C: New test.
> ---
>   gcc/c-family/c-cppbuiltin.cc                  |   1 +
>   gcc/cp/call.cc                                |   2 +-
>   gcc/cp/cp-tree.h                              |   1 +
>   gcc/cp/pt.cc                                  |  33 +++--
>   gcc/cp/tree.cc                                |   2 +-
>   gcc/cp/typeck.cc                              |  28 ++++-
>   gcc/testsuite/g++.dg/conversion/pr41426.C     |  10 +-
>   gcc/testsuite/g++.dg/cpp0x/elision_weak.C     |   4 +-
>   gcc/testsuite/g++.dg/cpp0x/move-return3.C     |   3 +-
>   gcc/testsuite/g++.dg/cpp1y/decltype-auto2.C   |   2 +-
>   gcc/testsuite/g++.dg/cpp1y/decltype-auto6.C   |  19 +++
>   .../g++.dg/cpp1y/lambda-generic-89419.C       |   6 +-
>   gcc/testsuite/g++.dg/cpp23/decltype1.C        | 113 ++++++++++++++++++
>   gcc/testsuite/g++.dg/cpp23/decltype2.C        |  49 ++++++++
>   gcc/testsuite/g++.dg/cpp23/elision1.C         | 109 +++++++++++++++++
>   gcc/testsuite/g++.dg/cpp23/elision2.C         |  46 +++++++
>   gcc/testsuite/g++.dg/cpp23/elision3.C         |  16 +++
>   gcc/testsuite/g++.dg/cpp23/elision4.C         |  38 ++++++
>   gcc/testsuite/g++.dg/cpp23/elision5.C         |  53 ++++++++
>   gcc/testsuite/g++.dg/cpp23/elision6.C         |  20 ++++
>   gcc/testsuite/g++.dg/cpp23/elision7.C         |  72 +++++++++++
>   gcc/testsuite/g++.dg/cpp23/feat-cxx2b.C       |   6 +
>   gcc/testsuite/g++.dg/gomp/pr56217.C           |   4 +-
>   .../g++.dg/warn/Wno-return-local-addr.C       |   2 +-
>   .../g++.dg/warn/Wreturn-local-addr.C          |   2 +-
>   .../g++.old-deja/g++.brendan/crash55.C        |   3 +-
>   .../g++.old-deja/g++.jason/temporary2.C       |   2 +-
>   gcc/testsuite/g++.old-deja/g++.mike/p2846b.C  |   4 +-
>   28 files changed, 613 insertions(+), 37 deletions(-)
>   create mode 100644 gcc/testsuite/g++.dg/cpp1y/decltype-auto6.C
>   create mode 100644 gcc/testsuite/g++.dg/cpp23/decltype1.C
>   create mode 100644 gcc/testsuite/g++.dg/cpp23/decltype2.C
>   create mode 100644 gcc/testsuite/g++.dg/cpp23/elision1.C
>   create mode 100644 gcc/testsuite/g++.dg/cpp23/elision2.C
>   create mode 100644 gcc/testsuite/g++.dg/cpp23/elision3.C
>   create mode 100644 gcc/testsuite/g++.dg/cpp23/elision4.C
>   create mode 100644 gcc/testsuite/g++.dg/cpp23/elision5.C
>   create mode 100644 gcc/testsuite/g++.dg/cpp23/elision6.C
>   create mode 100644 gcc/testsuite/g++.dg/cpp23/elision7.C
> 
> diff --git a/gcc/c-family/c-cppbuiltin.cc b/gcc/c-family/c-cppbuiltin.cc
> index a1557eb23d5..dd8b20d0c34 100644
> --- a/gcc/c-family/c-cppbuiltin.cc
> +++ b/gcc/c-family/c-cppbuiltin.cc
> @@ -1081,6 +1081,7 @@ c_cpp_builtins (cpp_reader *pfile)
>   	  cpp_define (pfile, "__cpp_constexpr=202110L");
>   	  cpp_define (pfile, "__cpp_multidimensional_subscript=202110L");
>   	  cpp_define (pfile, "__cpp_named_character_escapes=202207L");
> +	  cpp_define (pfile, "__cpp_implicit_move=202207L");
>   	}
>         if (flag_concepts)
>           {
> diff --git a/gcc/cp/call.cc b/gcc/cp/call.cc
> index d107a2814dc..cb4ae9ecf9d 100644
> --- a/gcc/cp/call.cc
> +++ b/gcc/cp/call.cc
> @@ -1865,7 +1865,7 @@ reference_binding (tree rto, tree rfrom, tree expr, bool c_cast_p, int flags,
>         /* Nor the reverse.  */
>         if (!is_lvalue && !TYPE_REF_IS_RVALUE (rto)
>   	  /* Unless it's really an lvalue.  */

Please add a bit about C++20 vs C++23 in this comment.

> -	  && !(cxx_dialect >= cxx20
> +	  && !(cxx_dialect == cxx20
>   	       && (gl_kind & clk_implicit_rval))
>   	  && (!CP_TYPE_CONST_NON_VOLATILE_P (to)
>   	      || (flags & LOOKUP_NO_RVAL_BIND))
> diff --git a/gcc/cp/cp-tree.h b/gcc/cp/cp-tree.h
> index 7b28405c3ac..4cdbbc34cb3 100644
> --- a/gcc/cp/cp-tree.h
> +++ b/gcc/cp/cp-tree.h
> @@ -7278,6 +7278,7 @@ extern tree make_constrained_decltype_auto	(tree, tree);
>   extern tree make_template_placeholder		(tree);
>   extern bool template_placeholder_p		(tree);
>   extern bool ctad_template_p			(tree);
> +extern bool unparenthesized_id_or_class_member_access_p (tree);
>   extern tree do_auto_deduction                   (tree, tree, tree,
>                                                    tsubst_flags_t
>   						 = tf_warning_or_error,
> diff --git a/gcc/cp/pt.cc b/gcc/cp/pt.cc
> index cd0d8920ed0..b347a9a78fc 100644
> --- a/gcc/cp/pt.cc
> +++ b/gcc/cp/pt.cc
> @@ -30405,6 +30405,26 @@ do_class_deduction (tree ptype, tree tmpl, tree init,
>   				  cp_type_quals (ptype));
>   }
>   
> +/* Return true if INIT is an unparenthesized id-expression or an
> +   unparenthesized class member access.  Used for the argument of
> +   decltype(auto).  */
> +
> +bool
> +unparenthesized_id_or_class_member_access_p (tree init)
> +{
> +  STRIP_ANY_LOCATION_WRAPPER (init);
> +

I wonder about looking through IMPLICIT_RVALUE_P here...

> +  /* We need to be able to tell '(r)' and 'r' apart (when it's of
> +     reference type).  Only the latter is an id-expression.  */
> +  if (REFERENCE_REF_P (init)
> +      && !REF_PARENTHESIZED_P (init))
> +    init = TREE_OPERAND (init, 0);
> +  return (DECL_P (init)
> +	  || ((TREE_CODE (init) == COMPONENT_REF
> +	       || TREE_CODE (init) == SCOPE_REF)
> +	      && !REF_PARENTHESIZED_P (init)));
> +}
> +
>   /* Replace occurrences of 'auto' in TYPE with the appropriate type deduced
>      from INIT.  AUTO_NODE is the TEMPLATE_TYPE_PARM used for 'auto' in TYPE.
>      The CONTEXT determines the context in which auto deduction is performed
> @@ -30501,18 +30521,7 @@ do_auto_deduction (tree type, tree init, tree auto_node,
>       }
>     else if (AUTO_IS_DECLTYPE (auto_node))
>       {
> -      /* Figure out if INIT is an unparenthesized id-expression or an
> -	 unparenthesized class member access.  */
> -      tree stripped_init = tree_strip_any_location_wrapper (init);
> -      /* We need to be able to tell '(r)' and 'r' apart (when it's of
> -	 reference type).  Only the latter is an id-expression.  */
> -      if (REFERENCE_REF_P (stripped_init)
> -	  && !REF_PARENTHESIZED_P (stripped_init))
> -	stripped_init = TREE_OPERAND (stripped_init, 0);
> -      const bool id = (DECL_P (stripped_init)
> -		       || ((TREE_CODE (stripped_init) == COMPONENT_REF
> -			    || TREE_CODE (stripped_init) == SCOPE_REF)
> -			   && !REF_PARENTHESIZED_P (stripped_init)));
> +      const bool id = unparenthesized_id_or_class_member_access_p (init);
>         tree deduced = finish_decltype_type (init, id, complain);
>         deduced = canonicalize_type_argument (deduced, complain);
>         if (deduced == error_mark_node)
> diff --git a/gcc/cp/tree.cc b/gcc/cp/tree.cc
> index c678e3b9c4c..226ffd48519 100644
> --- a/gcc/cp/tree.cc
> +++ b/gcc/cp/tree.cc
> @@ -382,7 +382,7 @@ obvalue_p (const_tree ref)
>   bool
>   xvalue_p (const_tree ref)
>   {
> -  return (lvalue_kind (ref) == clk_rvalueref);
> +  return (lvalue_kind (ref) & clk_rvalueref);
>   }
>   
>   /* True if REF is a bit-field.  */
> diff --git a/gcc/cp/typeck.cc b/gcc/cp/typeck.cc
> index b99947c10fd..fe88c1a7b84 100644
> --- a/gcc/cp/typeck.cc
> +++ b/gcc/cp/typeck.cc
> @@ -10876,8 +10893,9 @@ check_return_expr (tree retval, bool *no_warning)
>   	 the conditions for the named return value optimization.  */
>         bool converted = false;
>         tree moved;
> -      /* This is only interesting for class type.  */
> -      if (CLASS_TYPE_P (functype)
> +      /* Until C++23, this was only interesting for class type.  */
> +      if ((CLASS_TYPE_P (functype)
> +	   || (cxx_dialect >= cxx23 && TYPE_REF_P (functype)))
>   	  && (moved = treat_lvalue_as_rvalue_p (retval, /*return*/true)))

...and calling treat_lvalue_as_rvalue_p before auto deduction...

>   	{
>   	  if (cxx_dialect < cxx20)
> @@ -10679,10 +10679,27 @@ check_return_expr (tree retval, bool *no_warning)
>   	}
>         else
>   	{
> -	  if (!retval)
> -	    retval = void_node;
>   	  auto_node = type_uses_auto (pattern);
> -	  type = do_auto_deduction (pattern, retval, auto_node,
> +	  tree r;
> +	  if (!retval)
> +	    r = void_node;
> +	  /* In C++23, we must deduce the type to int&& for code like
> +	       decltype(auto) f(int&& x) { return (x); }
> +	     or
> +	       auto&& f(int x) { return x; }
> +	     so we use treat_lvalue_as_rvalue_p earlier.  But don't do it for
> +	       decltype(auto) f(int x) { return x; }
> +	     where we should deduce 'int' rather than 'int&&'; transmogrifying
> +	     RETVAL to an rvalue would break that.  */
> +	  else if (cxx_dialect >= cxx23
> +		   && (!AUTO_IS_DECLTYPE (auto_node)
> +		       || !unparenthesized_id_or_class_member_access_p (retval))
> +		   && (r = treat_lvalue_as_rvalue_p
> +		       (maybe_undo_parenthesized_ref (retval), /*return*/true)))
> +	    /* Use it.  */;

...so we don't need to do this?

> +	  else
> +	    r = retval;
> +	  type = do_auto_deduction (pattern, r, auto_node,
>   				    tf_warning_or_error, adc_return_type);
>   	}
>   
> diff --git a/gcc/testsuite/g++.dg/conversion/pr41426.C b/gcc/testsuite/g++.dg/conversion/pr41426.C
> index 5493a91ecfa..b4ecbca5f3a 100644
> --- a/gcc/testsuite/g++.dg/conversion/pr41426.C
> +++ b/gcc/testsuite/g++.dg/conversion/pr41426.C
> @@ -11,19 +11,20 @@ struct A
>   A<float> g1()
>   {
>      float f[] = {1.1f, 2.3f};
> -   return f;
> +   return f; // { dg-error "cannot bind non-const" "" { target c++23 } }
>   }
>   
>   const A<float> &g3()
>   {
>      float f[] = {1.1f, 2.3f};
> -   return f; // { dg-warning "returning reference to temporary" }
> +   return f; // { dg-warning "returning reference to temporary" "" { target c++20_down } }
> +// { dg-error "non-const lvalue|invalid user-defined conversion" "" { target c++23 } .-1 }
>   }
>   
>   A<float> &g4()
>   {
>      float f[] = {1.1f, 2.3f};
> -   return f; // { dg-error "cannot bind non-const lvalue ref" }
> +   return f; // { dg-error "cannot bind non-const lvalue ref|invalid user-defined conversion" }
>   }
>   
>   struct B
> @@ -35,6 +36,5 @@ struct B
>   B g2()
>   {
>      int c[10];
> -   return c;
> +   return c; // { dg-error "non-const lvalue" "" { target c++23 } }
>   }
> -
> diff --git a/gcc/testsuite/g++.dg/cpp0x/elision_weak.C b/gcc/testsuite/g++.dg/cpp0x/elision_weak.C
> index e8ba7551d84..ddd12743130 100644
> --- a/gcc/testsuite/g++.dg/cpp0x/elision_weak.C
> +++ b/gcc/testsuite/g++.dg/cpp0x/elision_weak.C
> @@ -9,11 +9,11 @@ struct S
>   S f()
>   {
>     S s;
> -  return s;
> +  return s; // { dg-error "cannot bind non-const lvalue reference" "" { target c++23 } }
>   }
>   
>   void g()
>   {
>     S s;
> -  throw s;
> +  throw s; // { dg-error "cannot bind non-const lvalue reference" "" { target c++23 } }
>   }
> diff --git a/gcc/testsuite/g++.dg/cpp0x/move-return3.C b/gcc/testsuite/g++.dg/cpp0x/move-return3.C
> index c79f0591936..30a936fb35a 100644
> --- a/gcc/testsuite/g++.dg/cpp0x/move-return3.C
> +++ b/gcc/testsuite/g++.dg/cpp0x/move-return3.C
> @@ -1,6 +1,7 @@
>   // PR c++/91212
>   // Test that C++11 implicit move semantics don't call the const copy.
> -// { dg-do link }
> +// In C++23, we call #2.
> +// { dg-do link { target c++20_down } }
>   
>   struct T { int i; };
>   
> diff --git a/gcc/testsuite/g++.dg/cpp1y/decltype-auto2.C b/gcc/testsuite/g++.dg/cpp1y/decltype-auto2.C
> index 56e011e36f4..24b32edfacf 100644
> --- a/gcc/testsuite/g++.dg/cpp1y/decltype-auto2.C
> +++ b/gcc/testsuite/g++.dg/cpp1y/decltype-auto2.C
> @@ -8,5 +8,5 @@ auto constexpr RtoL1(T&& r) -> decltype(auto) {
>   int main() {
>       int t;
>       int x{3};
> -    decltype (RtoL1(x+0)) y = t;
> +    decltype (RtoL1(x+0)) y = t; // { dg-error "cannot bind rvalue reference" "" { target c++23 } }
>   }
> diff --git a/gcc/testsuite/g++.dg/cpp1y/decltype-auto6.C b/gcc/testsuite/g++.dg/cpp1y/decltype-auto6.C
> new file mode 100644
> index 00000000000..da53278645c
> --- /dev/null
> +++ b/gcc/testsuite/g++.dg/cpp1y/decltype-auto6.C
> @@ -0,0 +1,19 @@
> +// PR c++/101165 - P2266R1 - Simpler implicit move
> +// { dg-do compile { target c++14 } }
> +// A variant of cxx23/elision1.C:eight, just with ().
> +
> +struct Widget {
> +  Widget(Widget&&);
> +};
> +
> +Widget val();
> +
> +decltype(auto)
> +foo ()
> +{
> +  decltype(auto) x = val();  // OK, x is Widget
> +  // We deduce the return type to int&&, therefore we're doing something
> +  // we ought not to be doing -- returning a reference to a local variable!
> +  // In C++20, we deduce to int&, but that has the same problem!
> +  return (x); // { dg-warning "reference to local variable" }
> +}
> diff --git a/gcc/testsuite/g++.dg/cpp1y/lambda-generic-89419.C b/gcc/testsuite/g++.dg/cpp1y/lambda-generic-89419.C
> index 46ce909f3b8..8e64d4e64ab 100644
> --- a/gcc/testsuite/g++.dg/cpp1y/lambda-generic-89419.C
> +++ b/gcc/testsuite/g++.dg/cpp1y/lambda-generic-89419.C
> @@ -2,7 +2,7 @@
>   // { dg-do compile { target c++14 } }
>   
>   struct A;
> -struct B {
> +struct B { // { dg-error "cannot bind" "" { target c++23 } }
>     struct C { C (); C (C &); } b;
>   };
>   struct D { A operator* (); };
> @@ -13,12 +13,12 @@ struct E {
>     auto bar () { return e; }
>     D e;
>   };
> -struct F { B f; int g; };
> +struct F { B f; int g; }; // { dg-error "use of deleted function" "" { target c++23 } }
>   
>   int
>   main ()
>   {
>     E e;
>     auto f = *e.bar ();
> -  auto i = [&] { F g; g.g = 1; auto h = [&](auto) { g.g = 0; }; f.foo (h); return g; };
> +  auto i = [&] { F g; g.g = 1; auto h = [&](auto) { g.g = 0; }; f.foo (h); return g; }; // { dg-error "use of deleted function" "" { target c++23 } }
>   }
> diff --git a/gcc/testsuite/g++.dg/cpp23/decltype1.C b/gcc/testsuite/g++.dg/cpp23/decltype1.C
> new file mode 100644
> index 00000000000..6f3cd0d45d5
> --- /dev/null
> +++ b/gcc/testsuite/g++.dg/cpp23/decltype1.C
> @@ -0,0 +1,113 @@
> +// PR c++/101165 - P2266R1 - Simpler implicit move
> +// { dg-do compile { target c++23 } }
> +// Tests from P2266R1, decltype-related changes in
> +// $ 3.2.1. Interaction with decltype and decltype(auto)
> +
> +template<typename T, typename U>
> +struct same_type { static const bool value = false; };
> +
> +template<typename T>
> +struct same_type<T, T> { static const bool value = true; };
> +
> +auto f1(int x) -> decltype(x) { return (x); }
> +static_assert(same_type<decltype(f1), int (int)>::value);
> +auto f2(int x) -> decltype((x)) { return (x); }   // { dg-error "cannot bind" }
> +static_assert(same_type<decltype(f2), int& (int)>::value);
> +auto f3(int x) -> decltype(auto) { return (x); }  // { dg-warning "reference to local variable" }
> +static_assert(same_type<decltype(f3), int&& (int)>::value);
> +auto g1(int x) -> decltype(x) { return x; }
> +static_assert(same_type<decltype(g1), int (int)>::value);
> +auto g2(int x) -> decltype((x)) { return x; }	  // { dg-error "cannot bind" }
> +static_assert(same_type<decltype(g2), int& (int)>::value);
> +auto g3(int x) -> decltype(auto) { return x; }
> +static_assert(same_type<decltype(g3), int (int)>::value);
> +
> +// Note that f2 and g2 are well-formed in C++20, but we propose to make
> +// f2 and g2 ill-formed, because they attempt to bind an lvalue reference
> +// to a move-eligible xvalue expression.
> +
> +struct X { };
> +
> +auto
> +f4 (X x)
> +{
> +  return x;
> +}
> +static_assert(same_type<decltype(f4), X(X)>::value);
> +
> +auto&
> +f5 (X x)
> +{
> +  return x; // { dg-error "cannot bind non-const lvalue reference" }
> +}
> +static_assert(same_type<decltype(f5), X&(X)>::value);
> +
> +auto&&
> +f6 (X x)
> +{
> +  return x; // { dg-warning "reference to local variable" }
> +}
> +static_assert(same_type<decltype(f6), X&&(X)>::value);
> +
> +auto
> +f7 (X x)
> +{
> +  return (x);
> +}
> +static_assert(same_type<decltype(f7), X(X)>::value);
> +
> +auto&
> +f8 (X x)
> +{
> +  return (x); // { dg-error "cannot bind non-const lvalue reference" }
> +}
> +static_assert(same_type<decltype(f8), X&(X)>::value);
> +
> +auto&&
> +f9 (X x)
> +{
> +  return (x); // { dg-warning "reference to local variable" }
> +}
> +static_assert(same_type<decltype(f9), X&&(X)>::value);
> +
> +decltype(auto)
> +f10 (X x)
> +{
> +  return x;
> +}
> +static_assert(same_type<decltype(f10), X(X)>::value);
> +
> +decltype(auto)
> +f11 (X x)
> +{
> +  return (x); // { dg-warning "reference to local variable" }
> +}
> +static_assert(same_type<decltype(f11), X&&(X)>::value);
> +
> +decltype(auto)
> +f12 (X& x)
> +{
> +  return x;
> +}
> +static_assert(same_type<decltype(f12), X&(X&)>::value);
> +
> +decltype(auto)
> +f13 (X& x)
> +{
> +  return (x);
> +}
> +static_assert(same_type<decltype(f13), X&(X&)>::value);
> +
> +decltype(auto)
> +f14 (X&& x)
> +{
> +  return x;
> +}
> +static_assert(same_type<decltype(f14), X&&(X&&)>::value);
> +
> +decltype(auto)
> +f15 (X&& x)
> +{
> +  return (x);
> +}
> +static_assert(same_type<decltype(f15), X&&(X&&)>::value);
> diff --git a/gcc/testsuite/g++.dg/cpp23/decltype2.C b/gcc/testsuite/g++.dg/cpp23/decltype2.C
> new file mode 100644
> index 00000000000..84679c48f82
> --- /dev/null
> +++ b/gcc/testsuite/g++.dg/cpp23/decltype2.C
> @@ -0,0 +1,49 @@
> +// PR c++/101165 - P2266R1 - Simpler implicit move
> +// { dg-do compile { target c++23 } }
> +// Test decltype(auto) more.
> +
> +template<typename T, typename U>
> +struct same_type { static const bool value = false; };
> +
> +template<typename T>
> +struct same_type<T, T> { static const bool value = true; };
> +
> +struct Widget {
> +  int x;
> +};
> +
> +Widget wg;
> +
> +decltype(auto) fn0(Widget&& x) {
> +    return (::wg);
> +}
> +static_assert(same_type<decltype(fn0), Widget& (Widget&&)>::value);
> +
> +decltype(auto) fn1(Widget&& x) {
> +    return ::wg;
> +}
> +static_assert(same_type<decltype(fn1), Widget (Widget&&)>::value);
> +
> +decltype(auto) fn2() {
> +    Widget w;
> +    return w;
> +}
> +static_assert(same_type<decltype(fn2), Widget ()>::value);
> +
> +decltype(auto) fn3() {
> +    Widget w;
> +    return (w); // { dg-warning "reference to local variable" }
> +}
> +static_assert(same_type<decltype(fn3), Widget&& ()>::value);
> +
> +decltype(auto) fn4() {
> +    Widget w;
> +    return w.x;
> +}
> +static_assert(same_type<decltype(fn4), int ()>::value);
> +
> +decltype(auto) fn5() {
> +    Widget w;
> +    return (w.x); // { dg-warning "reference to local variable" }
> +}
> +static_assert(same_type<decltype(fn5), int& ()>::value);
> diff --git a/gcc/testsuite/g++.dg/cpp23/elision1.C b/gcc/testsuite/g++.dg/cpp23/elision1.C
> new file mode 100644
> index 00000000000..8115739b7f9
> --- /dev/null
> +++ b/gcc/testsuite/g++.dg/cpp23/elision1.C
> @@ -0,0 +1,109 @@
> +// PR c++/101165 - P2266R1 - Simpler implicit move
> +// { dg-do compile { target c++23 } }
> +// Tests from P2266R1.
> +
> +namespace std {
> +  template<typename _Tp>
> +    struct remove_reference
> +    { typedef _Tp   type; };
> +
> +  template<typename _Tp>
> +    struct remove_reference<_Tp&>
> +    { typedef _Tp   type; };
> +
> +  template<typename _Tp>
> +    struct remove_reference<_Tp&&>
> +    { typedef _Tp   type; };
> +
> +  template<typename _Tp>
> +    constexpr typename std::remove_reference<_Tp>::type&&
> +    move(_Tp&& __t) noexcept
> +    { return static_cast<typename std::remove_reference<_Tp>::type&&>(__t); }
> +}
> +
> +template<typename T, typename U>
> +struct same_type { static const bool value = false; };
> +
> +template<typename T>
> +struct same_type<T, T> { static const bool value = true; };
> +
> +struct Widget {
> +    Widget(Widget&&);
> +};
> +
> +struct RRefTaker {
> +    RRefTaker(Widget&&);
> +};
> +
> +struct Mutt {
> +    operator int*() &&;
> +};
> +
> +struct Jeff {
> +    operator int&() &&;
> +};
> +
> +Widget one(Widget w) {
> +    return w;  // OK since C++11
> +}
> +
> +RRefTaker two(Widget w) {
> +    return w;  // OK since C++11 + CWG1579
> +}
> +
> +RRefTaker three(Widget&& w) {
> +    return w;  // OK since C++20 because P0527
> +}
> +
> +// Tests that implicit move applies even to functions that return references.
> +Widget&& four(Widget&& w) {
> +    return w;  // OK since C++23
> +}
> +
> +// FIXME This is supposed to work but we reject it with
> +// error: passing 'Mutt' as 'this' argument discards qualifiers
> +#if 0
> +int* five(Mutt x) {
> +    return x;  // OK since C++20 because P1155
> +}
> +#endif
> +
> +int& six(Jeff x) {
> +    return x;
> +}
> +
> +template<class T>
> +T&& seven(T&& x) { return x; }
> +
> +void test_seven(Widget w) {
> +    Widget& r = seven(w);
> +    Widget&& rr = seven(std::move(w));
> +}
> +
> +Widget val();
> +Widget& lref();
> +Widget&& rref();
> +
> +decltype(auto) eight() {
> +    decltype(auto) x = val();  // OK, x is Widget
> +    return x;  // OK, return type is Widget, we get copy elision
> +}
> +
> +decltype(auto) nine() {
> +    decltype(auto) x = lref();  // OK, x is Widget&
> +    return x;  // OK, return type is Widget&
> +}
> +
> +decltype(auto) ten() {
> +  decltype(auto) x = rref();  // OK, x is Widget&&
> +  // This was an error: return type is Widget&&, cannot bind to x.
> +  // But in C++23, x is treated as an rvalue.
> +  return x;
> +}
> +
> +// Now returns Widget&&, not Widget&.
> +// This is from $ 3.2.1. Interaction with decltype and decltype(auto).
> +decltype(auto) eleven(Widget&& x) {
> +    return (x);
> +}
> +static_assert(same_type<decltype(eleven), Widget&& (Widget&&)>::value);
> diff --git a/gcc/testsuite/g++.dg/cpp23/elision2.C b/gcc/testsuite/g++.dg/cpp23/elision2.C
> new file mode 100644
> index 00000000000..ce2c7aeef66
> --- /dev/null
> +++ b/gcc/testsuite/g++.dg/cpp23/elision2.C
> @@ -0,0 +1,46 @@
> +// PR c++/101165 - P2266R1 - Simpler implicit move
> +// { dg-do compile { target c++20 } }
> +// Test from P2266R1, $ 3.3. Two overload resolutions are overly confusing.
> +
> +struct Widget {
> +    Widget();
> +    Widget(Widget&&);
> +};
> +
> +struct Frodo {
> +    Frodo(Widget&);
> +    Frodo(Widget&&) = delete;
> +};
> +
> +struct Sam {
> +    Sam(Widget&) = delete; // #1
> +    Sam(const Widget&);  // #2
> +};
> +
> +Sam twelve() {
> +    Widget w;
> +    // This is supposed to calls #2 since C++20 because P1155.
> +    // But we actually choose #1 since r11-2411 (in C++20 only).
> +    return w; // { dg-error "deleted" "" { target c++20_only } }
> +}
> +
> +Frodo thirteen() {
> +    Widget w;
> +    // This is a correct error in both C++20 and C++23.
> +    return w;  // { dg-error "use of deleted function" }
> +}
> +
> +struct Merry {};
> +struct Pippin {};
> +struct Together : Merry, Pippin {};
> +struct Quest {
> +    Quest(Merry&&);
> +    Quest(Pippin&&);
> +    Quest(Together&);
> +};
> +
> +Quest fourteen() {
> +  Together t;
> +  // C++20: calls Quest(Together&).  Proposed: ill-formed.
> +  return t; // { dg-error "ambiguous" "" { target c++23 } }
> +}
> diff --git a/gcc/testsuite/g++.dg/cpp23/elision3.C b/gcc/testsuite/g++.dg/cpp23/elision3.C
> new file mode 100644
> index 00000000000..246342e64d3
> --- /dev/null
> +++ b/gcc/testsuite/g++.dg/cpp23/elision3.C
> @@ -0,0 +1,16 @@
> +// PR c++/101165 - P2266R1 - Simpler implicit move
> +// { dg-do compile { target c++23 } }
> +// Test from P2266R1, $ 3.4. A specific case involving reference_wrapper.
> +
> +#include <functional>
> +
> +struct Widget {
> +    Widget();
> +    Widget(Widget&&);
> +};
> +
> +std::reference_wrapper<Widget> fifteen() {
> +    Widget w;
> +    // OK until CWG1579; OK after LWG2993.  Proposed: ill-formed
> +    return w;  // { dg-error "could not convert" }
> +}
> diff --git a/gcc/testsuite/g++.dg/cpp23/elision4.C b/gcc/testsuite/g++.dg/cpp23/elision4.C
> new file mode 100644
> index 00000000000..c19b86b8b5f
> --- /dev/null
> +++ b/gcc/testsuite/g++.dg/cpp23/elision4.C
> @@ -0,0 +1,38 @@
> +// PR c++/101165 - P2266R1 - Simpler implicit move
> +// { dg-do compile { target c++23 } }
> +// Test from P2266R1, $ 5.2. LibreOffice OString constructor.
> +
> +struct X {
> +    X(auto&);
> +};
> +
> +// The following compiles in C++20 (deducing X(char (&)[10])) but not
> +// after P2266 (because the returned expression now has type char (&&)[10],
> +// which cannot bind to auto&).
> +X f() {
> +    char a[10];
> +    return a; // { dg-error "cannot bind non-const lvalue reference" }
> +}
> +
> +// The solution was to change it by making the return convert explicitly
> +// rather than implicitly:
> +X fixed() {
> +    char a[10];
> +    return X(a);
> +}
> +
> +// $ 5.3. LibreOffice o3tl::temporary
> +
> +template<class T>
> +T& temporary1(T&& x) { return x; } // { dg-error "cannot bind non-const lvalue reference" }
> +
> +// Fixed by:
> +template<class T>
> +T& temporary2(T&& x) { return static_cast<T&>(x); }
> +
> +void
> +test ()
> +{
> +  int& r1 = temporary1 (42);
> +  int& r2 = temporary2 (42);
> +}
> diff --git a/gcc/testsuite/g++.dg/cpp23/elision5.C b/gcc/testsuite/g++.dg/cpp23/elision5.C
> new file mode 100644
> index 00000000000..a7d3e7c27c4
> --- /dev/null
> +++ b/gcc/testsuite/g++.dg/cpp23/elision5.C
> @@ -0,0 +1,53 @@
> +// PR c++/101165 - P2266R1 - Simpler implicit move
> +// { dg-do compile { target c++23 } }
> +// Test from [class.copy.elision]/4.
> +
> +class Thing {
> +public:
> +  Thing();
> +  ~Thing();
> +  Thing(Thing&&);
> +private:
> +  Thing(const Thing&);
> +};
> +
> +Thing f(bool b) {
> +  Thing t;
> +  if (b)
> +    throw t;            // OK, Thing(Thing&&) used (or elided) to throw t
> +  return t;             // OK, Thing(Thing&&) used (or elided) to return t
> +}
> +
> +Thing t2 = f(false);    // OK, no extra copy/move performed, t2 constructed by call to f
> +
> +struct Weird {
> +  Weird();
> +  Weird(Weird&);
> +};
> +
> +Weird g(bool b) {
> +  static Weird w1;
> +  Weird w2;
> +  if (b) {
> +    return w1;  // OK: Weird(Weird&)
> +  } else {
> +    return w2;  // { dg-error "cannot bind non-const lvalue reference" }
> +  }
> +}
> +
> +int& h(bool b, int i) {
> +  static int s;
> +  if (b)
> +    return s;   // OK
> +  else
> +    return i;   // { dg-error "cannot bind non-const lvalue reference" }
> +}
> +
> +decltype(auto) h2(Thing t) {
> +  return t;     // OK, t is an xvalue and h2's return type is Thing
> +}
> +
> +decltype(auto) h3(Thing t) {
> +  // OK, (t) is an xvalue and h3's return type is Thing&&
> +  return (t); // { dg-warning "reference to local variable" }
> +}
> diff --git a/gcc/testsuite/g++.dg/cpp23/elision6.C b/gcc/testsuite/g++.dg/cpp23/elision6.C
> new file mode 100644
> index 00000000000..5d58da9e577
> --- /dev/null
> +++ b/gcc/testsuite/g++.dg/cpp23/elision6.C
> @@ -0,0 +1,20 @@
> +// PR c++/101165 - P2266R1 - Simpler implicit move
> +// { dg-do compile { target c++23 } }
> +// From [diff.cpp20.expr].
> +
> +template<typename T, typename U>
> +struct same_type { static const bool value = false; };
> +
> +template<typename T>
> +struct same_type<T, T> { static const bool value = true; };
> +
> +// In C++23, returns int&&; previously returned int&.
> +decltype(auto) f(int&& x) { return (x); }
> +static_assert(same_type<decltype(f), int&& (int&&)>::value);
> +
> +// This used to work in C++20.
> +int& g(int&& x) { return x; } // { dg-error "cannot bind non-const lvalue reference" }
> +
> +template<typename T>
> +decltype(auto) h(T&& x) { return (x); }
> +static_assert(same_type<decltype(h(42)), int&&>::value);
> diff --git a/gcc/testsuite/g++.dg/cpp23/elision7.C b/gcc/testsuite/g++.dg/cpp23/elision7.C
> new file mode 100644
> index 00000000000..19fa89ae133
> --- /dev/null
> +++ b/gcc/testsuite/g++.dg/cpp23/elision7.C
> @@ -0,0 +1,72 @@
> +// PR c++/101165 - P2266R1 - Simpler implicit move
> +// { dg-do compile { target c++23 } }
> +
> +struct X {
> +  X ();
> +  X(X&&);
> +};
> +
> +X&& rref ();
> +
> +X&&
> +f1 (X&& x)
> +{
> +  return x;
> +}
> +
> +template<typename T> T&&
> +f2 (T&& x)
> +{
> +  return x;
> +}
> +template X& f2<X&>(X&);
> +template X&& f2<X>(X&&);
> +
> +X&&
> +f3 ()
> +{
> +  X&& x = rref ();
> +  return x;
> +}
> +
> +void
> +f4 ()
> +try {
> +  X x;
> +  throw x;
> +} catch (...) { }
> +
> +void
> +f5 ()
> +{
> +  auto l1 = [](auto x) -> auto { return x; };
> +  auto &&x1 = l1(X{});
> +  auto l2 = [](auto x) -> auto& { return x; }; // { dg-error "cannot bind non-const lvalue reference" }
> +  auto &&x2 = l2(X{});
> +  auto l3 = [](auto x) -> auto&& { return x; }; // { dg-warning "reference to local" }
> +  auto &&x3 = l3(X{});
> +}
> +
> +constexpr int &
> +f6 (int &&n)
> +{
> +  return n; // { dg-error "cannot bind non-const lvalue reference" }
> +}
> +
> +void
> +do_f6 ()
> +{
> +  auto x = f6 (42);
> +}
> +
> +template<typename T> auto &
> +f7 (T &&t)
> +{
> +  return t; // { dg-error "cannot bind non-const lvalue reference" }
> +}
> +
> +void
> +do_f7 ()
> +{
> +  const int &x = f7 (0);
> +}
> diff --git a/gcc/testsuite/g++.dg/cpp23/feat-cxx2b.C b/gcc/testsuite/g++.dg/cpp23/feat-cxx2b.C
> index d3e40724085..20453bb7b14 100644
> --- a/gcc/testsuite/g++.dg/cpp23/feat-cxx2b.C
> +++ b/gcc/testsuite/g++.dg/cpp23/feat-cxx2b.C
> @@ -563,3 +563,9 @@
>   #elif __cpp_named_character_escapes != 202207
>   #  error "__cpp_named_character_escapes != 202207"
>   #endif
> +
> +#ifndef __cpp_implicit_move
> +#  error "__cpp_implicit_move"
> +#elif __cpp_implicit_move != 202207
> +#  error "__cpp_implicit_move != 202207"
> +#endif
> diff --git a/gcc/testsuite/g++.dg/gomp/pr56217.C b/gcc/testsuite/g++.dg/gomp/pr56217.C
> index 03dfc5f180b..731c0c08811 100644
> --- a/gcc/testsuite/g++.dg/gomp/pr56217.C
> +++ b/gcc/testsuite/g++.dg/gomp/pr56217.C
> @@ -1,5 +1,5 @@
>   // PR middle-end/56217
> -// { dg-do compile }
> +// { dg-do compile { target c++20_down } }
>   // { dg-options "-fopenmp" }
>   
>   struct S { int *p; S (); S (S &); };
> @@ -10,5 +10,7 @@ foo ()
>     S s;
>     #pragma omp task shared (s)
>       s.p = 0;
> +  // This fails in C++23, because "cannot bind non-const lvalue reference of
> +  // type 'S&' to an rvalue of type 'S'".
>     return s;
>   }
> diff --git a/gcc/testsuite/g++.dg/warn/Wno-return-local-addr.C b/gcc/testsuite/g++.dg/warn/Wno-return-local-addr.C
> index e15bfa24f54..cc9bb59770e 100644
> --- a/gcc/testsuite/g++.dg/warn/Wno-return-local-addr.C
> +++ b/gcc/testsuite/g++.dg/warn/Wno-return-local-addr.C
> @@ -4,7 +4,7 @@
>   int& bad1()
>   {
>     int x = 0;
> -  return x;
> +  return x; // { dg-error "cannot bind non-const lvalue reference" "" { target c++23 } }
>   }
>   
>   int* bad2()
> diff --git a/gcc/testsuite/g++.dg/warn/Wreturn-local-addr.C b/gcc/testsuite/g++.dg/warn/Wreturn-local-addr.C
> index 642a5767e84..4c18c2f06a0 100644
> --- a/gcc/testsuite/g++.dg/warn/Wreturn-local-addr.C
> +++ b/gcc/testsuite/g++.dg/warn/Wreturn-local-addr.C
> @@ -5,7 +5,7 @@
>   int& bad1()
>   {
>     int x = 0;
> -  return x;		// { dg-error "reference to local variable" }
> +  return x;		// { dg-error "reference to local variable|cannot bind non-const lvalue reference" }
>   }
>   
>   int* bad2()
> diff --git a/gcc/testsuite/g++.old-deja/g++.brendan/crash55.C b/gcc/testsuite/g++.old-deja/g++.brendan/crash55.C
> index fd4d4b65edb..b93e6e0c695 100644
> --- a/gcc/testsuite/g++.old-deja/g++.brendan/crash55.C
> +++ b/gcc/testsuite/g++.old-deja/g++.brendan/crash55.C
> @@ -8,5 +8,6 @@
>   
>             local = x+2;
>         
> -          return local; // { dg-warning "reference to local" }
> +          return local; // { dg-warning "reference to local" "" { target c++20_down } }
> +// { dg-error "non-const lvalue" "" { target c++23 } .-1 }
>         }
> diff --git a/gcc/testsuite/g++.old-deja/g++.jason/temporary2.C b/gcc/testsuite/g++.old-deja/g++.jason/temporary2.C
> index c855f8f4a07..2709b50e7f1 100644
> --- a/gcc/testsuite/g++.old-deja/g++.jason/temporary2.C
> +++ b/gcc/testsuite/g++.old-deja/g++.jason/temporary2.C
> @@ -8,7 +8,7 @@ public:
>     int i;
>   };
>   
> -X foo() { X x; return x; }
> +X foo() { X x; return x; } // { dg-error "cannot bind non-const lvalue reference" "" { target c++23 } }
>   
>   int main()
>   {
> diff --git a/gcc/testsuite/g++.old-deja/g++.mike/p2846b.C b/gcc/testsuite/g++.old-deja/g++.mike/p2846b.C
> index 57422fe64df..68826649cfc 100644
> --- a/gcc/testsuite/g++.old-deja/g++.mike/p2846b.C
> +++ b/gcc/testsuite/g++.old-deja/g++.mike/p2846b.C
> @@ -1,4 +1,4 @@
> -// { dg-do run  }
> +// { dg-do run { target c++20_down } }
>   // Shows that problem of initializing one object's secondary base from
>   // another object via a user defined copy constructor for that base,
>   // the pointer for the secondary vtable is not set after implicit
> @@ -11,6 +11,8 @@
>   
>   // prms-id: 2846
>   
> +// This test fails in C++23 due to P2266.
> +
>   extern "C" int printf(const char *, ...);
>   extern "C" void exit(int);
>   
> 
> base-commit: 08de065293f8b08158e1089fbacce9dbaba95077


^ permalink raw reply	[flat|nested] 10+ messages in thread

* Re: [PATCH] c++: Implement C++23 P2266R1, Simpler implicit move [PR101165]
  2022-09-07  2:38 ` Jason Merrill
@ 2022-09-08 22:54   ` Marek Polacek
  2022-09-12 20:27     ` Jason Merrill
  2022-09-20 18:19   ` [PATCH v2] " Marek Polacek
  1 sibling, 1 reply; 10+ messages in thread
From: Marek Polacek @ 2022-09-08 22:54 UTC (permalink / raw)
  To: Jason Merrill; +Cc: GCC Patches

On Tue, Sep 06, 2022 at 10:38:12PM -0400, Jason Merrill wrote:
> On 9/3/22 12:42, Marek Polacek wrote:
> > This patch implements https://wg21.link/p2266, which, once again,
> > changes the implicit move rules.  Here's a brief summary of various
> > changes in this area:
> > 
> > r125211: Introduced moving from certain lvalues when returning them
> > r171071: CWG 1148, enable move from value parameter on return
> > r212099: CWG 1579, it's OK to call a converting ctor taking an rvalue
> > r251035: CWG 1579, do maybe-rvalue overload resolution twice
> > r11-2411: Avoid calling const copy ctor on implicit move
> > r11-2412: C++20 implicit move changes, remove the fallback overload
> >            resolution, allow move on throw of parameters and implicit
> > 	  move of rvalue references
> > 
> > P2266 enables the implicit move for functions that return references.  This
> > was a one-line change: check TYPE_REF_P.  That is, we will now perform
> > a move in
> > 
> >    X&& foo (X&& x) {
> >      return x;
> >    }
> > 
> > P2266 also removes the fallback overload resolution, but this was
> > resolved by r11-2412: we only do convert_for_initialization with
> > LOOKUP_PREFER_RVALUE in C++17 and older.
> 
> I wonder if we want to extend the current C++20 handling to the older modes
> for GCC 13?  Not in this patch, but as a followup.
> 
> > P2266 also says that a returned move-eligible id-expression is always an
> > xvalue.  This required some further short, but nontrivial changes,
> > especially when it comes to deduction, because we have to pay attention
> > to whether we have auto, auto&& (which is like T&&), or decltype(auto)
> > with (un)parenthesized argument.  In C++23,
> > 
> >    decltype(auto) f(int&& x) { return (x); }
> >    auto&& f(int x) { return x; }
> > 
> > both should deduce to 'int&&' but
> > 
> >    decltype(auto) f(int x) { return x; }
> > 
> > should deduce to 'int'.  A cornucopia of tests attached.  I've also
> > verified that we behave like clang++.
> > 
> > xvalue_p seemed to be broken: since the introduction of clk_implicit_rval,
> > it cannot use '==' when checking for clk_rvalueref.
> > 
> > Since this change breaks code, it's only enabled in C++23.  In
> > particular, this code will not compile in C++23:
> > 
> >    int& g(int&& x) { return x; }
> 
> Nice that the C++20 compatibility is so simple!
> 
> > because x is now treated as an rvalue, and you can't bind a non-const lvalue
> > reference to an rvalue.
> > 
> > There's one FIXME in elision1.C:five, which we should compile but reject
> > with "passing 'Mutt' as 'this' argument discards qualifiers".  That
> > looks bogus to me, I think I'll open a PR for it.
> 
> Let's fix that now, I think.

Can of worms.   The test is

  struct Mutt {
      operator int*() &&;
  };

  int* five(Mutt x) {
      return x;  // OK since C++20 because P1155
  }

'x' should be treated as an rvalue, therefore the operator fn taking
an rvalue ref to Mutt should be used to convert 'x' to int*.  We fail
because we don't treat 'x' as an rvalue because the function doesn't
return a class.  So the patch should be just

--- a/gcc/cp/typeck.cc
+++ b/gcc/cp/typeck.cc
@@ -10875,10 +10875,7 @@ check_return_expr (tree retval, bool *no_warning)
          Note that these conditions are similar to, but not as strict as,
     the conditions for the named return value optimization.  */
       bool converted = false;
-      tree moved;
-      /* This is only interesting for class type.  */
-      if (CLASS_TYPE_P (functype)
-     && (moved = treat_lvalue_as_rvalue_p (retval, /*return*/true)))
+      if (tree moved = treat_lvalue_as_rvalue_p (retval, /*return*/true))
    {
      if (cxx_dialect < cxx20)
        {

which fixes the test, but breaks a lot of middle-end warnings.  For instance
g++.dg/warn/nonnull3.C, where the patch above changes .gimple:

 bool A::foo<B> (struct A * const this, <<< Unknown tree: offset_type >>> p)
 {
-  bool D.2146;
+  bool D.2150;
 
-  D.2146 = p != -1;
-  return D.2146;
+  p.0_1 = p;
+  D.2150 = p.0_1 != -1;
+  return D.2150;
 }

and we no longer get the warning.  I thought maybe I could undo the implicit
rvalue conversion in cp_fold, when it sees implicit_rvalue_p, but that didn't
work.  So currently I'm stuck.  Should we try to figure this out or push aside?

Marek


^ permalink raw reply	[flat|nested] 10+ messages in thread

* Re: [PATCH] c++: Implement C++23 P2266R1, Simpler implicit move [PR101165]
  2022-09-08 22:54   ` Marek Polacek
@ 2022-09-12 20:27     ` Jason Merrill
  2022-09-20 18:21       ` Marek Polacek
  0 siblings, 1 reply; 10+ messages in thread
From: Jason Merrill @ 2022-09-12 20:27 UTC (permalink / raw)
  To: Marek Polacek; +Cc: GCC Patches

On 9/8/22 18:54, Marek Polacek wrote:
> On Tue, Sep 06, 2022 at 10:38:12PM -0400, Jason Merrill wrote:
>> On 9/3/22 12:42, Marek Polacek wrote:
>>> This patch implements https://wg21.link/p2266, which, once again,
>>> changes the implicit move rules.  Here's a brief summary of various
>>> changes in this area:
>>>
>>> r125211: Introduced moving from certain lvalues when returning them
>>> r171071: CWG 1148, enable move from value parameter on return
>>> r212099: CWG 1579, it's OK to call a converting ctor taking an rvalue
>>> r251035: CWG 1579, do maybe-rvalue overload resolution twice
>>> r11-2411: Avoid calling const copy ctor on implicit move
>>> r11-2412: C++20 implicit move changes, remove the fallback overload
>>>             resolution, allow move on throw of parameters and implicit
>>> 	  move of rvalue references
>>>
>>> P2266 enables the implicit move for functions that return references.  This
>>> was a one-line change: check TYPE_REF_P.  That is, we will now perform
>>> a move in
>>>
>>>     X&& foo (X&& x) {
>>>       return x;
>>>     }
>>>
>>> P2266 also removes the fallback overload resolution, but this was
>>> resolved by r11-2412: we only do convert_for_initialization with
>>> LOOKUP_PREFER_RVALUE in C++17 and older.
>>
>> I wonder if we want to extend the current C++20 handling to the older modes
>> for GCC 13?  Not in this patch, but as a followup.
>>
>>> P2266 also says that a returned move-eligible id-expression is always an
>>> xvalue.  This required some further short, but nontrivial changes,
>>> especially when it comes to deduction, because we have to pay attention
>>> to whether we have auto, auto&& (which is like T&&), or decltype(auto)
>>> with (un)parenthesized argument.  In C++23,
>>>
>>>     decltype(auto) f(int&& x) { return (x); }
>>>     auto&& f(int x) { return x; }
>>>
>>> both should deduce to 'int&&' but
>>>
>>>     decltype(auto) f(int x) { return x; }
>>>
>>> should deduce to 'int'.  A cornucopia of tests attached.  I've also
>>> verified that we behave like clang++.
>>>
>>> xvalue_p seemed to be broken: since the introduction of clk_implicit_rval,
>>> it cannot use '==' when checking for clk_rvalueref.
>>>
>>> Since this change breaks code, it's only enabled in C++23.  In
>>> particular, this code will not compile in C++23:
>>>
>>>     int& g(int&& x) { return x; }
>>
>> Nice that the C++20 compatibility is so simple!
>>
>>> because x is now treated as an rvalue, and you can't bind a non-const lvalue
>>> reference to an rvalue.
>>>
>>> There's one FIXME in elision1.C:five, which we should compile but reject
>>> with "passing 'Mutt' as 'this' argument discards qualifiers".  That
>>> looks bogus to me, I think I'll open a PR for it.
>>
>> Let's fix that now, I think.
> 
> Can of worms.   The test is
> 
>    struct Mutt {
>        operator int*() &&;
>    };
> 
>    int* five(Mutt x) {
>        return x;  // OK since C++20 because P1155
>    }
> 
> 'x' should be treated as an rvalue, therefore the operator fn taking
> an rvalue ref to Mutt should be used to convert 'x' to int*.  We fail
> because we don't treat 'x' as an rvalue because the function doesn't
> return a class.  So the patch should be just
> 
> --- a/gcc/cp/typeck.cc
> +++ b/gcc/cp/typeck.cc
> @@ -10875,10 +10875,7 @@ check_return_expr (tree retval, bool *no_warning)
>            Note that these conditions are similar to, but not as strict as,
>       the conditions for the named return value optimization.  */
>         bool converted = false;
> -      tree moved;
> -      /* This is only interesting for class type.  */
> -      if (CLASS_TYPE_P (functype)
> -     && (moved = treat_lvalue_as_rvalue_p (retval, /*return*/true)))
> +      if (tree moved = treat_lvalue_as_rvalue_p (retval, /*return*/true))
>      {
>        if (cxx_dialect < cxx20)
>          {
> 
> which fixes the test, but breaks a lot of middle-end warnings.  For instance
> g++.dg/warn/nonnull3.C, where the patch above changes .gimple:
> 
>   bool A::foo<B> (struct A * const this, <<< Unknown tree: offset_type >>> p)
>   {
> -  bool D.2146;
> +  bool D.2150;
>   
> -  D.2146 = p != -1;
> -  return D.2146;
> +  p.0_1 = p;
> +  D.2150 = p.0_1 != -1;
> +  return D.2150;
>   }
> 
> and we no longer get the warning.  I thought maybe I could undo the implicit
> rvalue conversion in cp_fold, when it sees implicit_rvalue_p, but that didn't
> work.  So currently I'm stuck.  Should we try to figure this out or push aside?

Can you undo the implicit rvalue conversion within check_return_expr, 
where we can still refer back to the original expression?

Or avoid the rvalue conversion if the return type is scalar?

Did you see my comments in the body of the patch?

Jason


^ permalink raw reply	[flat|nested] 10+ messages in thread

* [PATCH v2] c++: Implement C++23 P2266R1, Simpler implicit move [PR101165]
  2022-09-07  2:38 ` Jason Merrill
  2022-09-08 22:54   ` Marek Polacek
@ 2022-09-20 18:19   ` Marek Polacek
  2022-09-26 17:29     ` Jason Merrill
  1 sibling, 1 reply; 10+ messages in thread
From: Marek Polacek @ 2022-09-20 18:19 UTC (permalink / raw)
  To: Jason Merrill; +Cc: GCC Patches

On Tue, Sep 06, 2022 at 10:38:12PM -0400, Jason Merrill wrote:
> On 9/3/22 12:42, Marek Polacek wrote:
> > This patch implements https://wg21.link/p2266, which, once again,
> > changes the implicit move rules.  Here's a brief summary of various
> > changes in this area:
> > 
> > r125211: Introduced moving from certain lvalues when returning them
> > r171071: CWG 1148, enable move from value parameter on return
> > r212099: CWG 1579, it's OK to call a converting ctor taking an rvalue
> > r251035: CWG 1579, do maybe-rvalue overload resolution twice
> > r11-2411: Avoid calling const copy ctor on implicit move
> > r11-2412: C++20 implicit move changes, remove the fallback overload
> >            resolution, allow move on throw of parameters and implicit
> > 	  move of rvalue references
> > 
> > P2266 enables the implicit move for functions that return references.  This
> > was a one-line change: check TYPE_REF_P.  That is, we will now perform
> > a move in
> > 
> >    X&& foo (X&& x) {
> >      return x;
> >    }
> > 
> > P2266 also removes the fallback overload resolution, but this was
> > resolved by r11-2412: we only do convert_for_initialization with
> > LOOKUP_PREFER_RVALUE in C++17 and older.
> 
> I wonder if we want to extend the current C++20 handling to the older modes
> for GCC 13?  Not in this patch, but as a followup.

Yes, I think that would be very nice if we removed that code.
 
> > P2266 also says that a returned move-eligible id-expression is always an
> > xvalue.  This required some further short, but nontrivial changes,
> > especially when it comes to deduction, because we have to pay attention
> > to whether we have auto, auto&& (which is like T&&), or decltype(auto)
> > with (un)parenthesized argument.  In C++23,
> > 
> >    decltype(auto) f(int&& x) { return (x); }
> >    auto&& f(int x) { return x; }
> > 
> > both should deduce to 'int&&' but
> > 
> >    decltype(auto) f(int x) { return x; }
> > 
> > should deduce to 'int'.  A cornucopia of tests attached.  I've also
> > verified that we behave like clang++.
> > 
> > xvalue_p seemed to be broken: since the introduction of clk_implicit_rval,
> > it cannot use '==' when checking for clk_rvalueref.
> > 
> > Since this change breaks code, it's only enabled in C++23.  In
> > particular, this code will not compile in C++23:
> > 
> >    int& g(int&& x) { return x; }
> 
> Nice that the C++20 compatibility is so simple!
> 
> > because x is now treated as an rvalue, and you can't bind a non-const lvalue
> > reference to an rvalue.
> > 
> > There's one FIXME in elision1.C:five, which we should compile but reject
> > with "passing 'Mutt' as 'this' argument discards qualifiers".  That
> > looks bogus to me, I think I'll open a PR for it.
> 
> Let's fix that now, I think.

OK, copypasting this bit from the other email so that we can have one
thread:

> Can of worms.   The test is
> 
>    struct Mutt {
>        operator int*() &&;
>    };
> 
>    int* five(Mutt x) {
>        return x;  // OK since C++20 because P1155
>    }
> 
> 'x' should be treated as an rvalue, therefore the operator fn taking
> an rvalue ref to Mutt should be used to convert 'x' to int*.  We fail
> because we don't treat 'x' as an rvalue because the function doesn't
> return a class.  So the patch should be just
> 
> --- a/gcc/cp/typeck.cc
> +++ b/gcc/cp/typeck.cc
> @@ -10875,10 +10875,7 @@ check_return_expr (tree retval, bool *no_warning)
>            Note that these conditions are similar to, but not as strict as,
>       the conditions for the named return value optimization.  */
>         bool converted = false;
> -      tree moved;
> -      /* This is only interesting for class type.  */
> -      if (CLASS_TYPE_P (functype)
> -     && (moved = treat_lvalue_as_rvalue_p (retval, /*return*/true)))
> +      if (tree moved = treat_lvalue_as_rvalue_p (retval, /*return*/true))
>      {
>        if (cxx_dialect < cxx20)
>          {
> 
> which fixes the test, but breaks a lot of middle-end warnings.  For instance
> g++.dg/warn/nonnull3.C, where the patch above changes .gimple:
> 
>   bool A::foo<B> (struct A * const this, <<< Unknown tree: offset_type >>> p)
>   {
> -  bool D.2146;
> +  bool D.2150;
>   
> -  D.2146 = p != -1;
> -  return D.2146;
> +  p.0_1 = p;
> +  D.2150 = p.0_1 != -1;
> +  return D.2150;
>   }
> 
> and we no longer get the warning.  I thought maybe I could undo the implicit
> rvalue conversion in cp_fold, when it sees implicit_rvalue_p, but that didn't
> work.  So currently I'm stuck.  Should we try to figure this out or push aside?

> Can you undo the implicit rvalue conversion within check_return_expr, 
> where we can still refer back to the original expression?

Unfortunately no, one problem is that treat_lvalue_as_rvalue_p modifies
the underlying decl by setting TREE_ADDRESSABLE, which then presumably
breaks warnings.  That is, treat_ can get 'VCE<X>(x)' and produce
'*NLE<(X&) &x>' where 'x' flags have been modified, since we're taking
x's address.

> Or avoid the rvalue conversion if the return type is scalar?

I wish :(.  In the 'five' example above, the return type is a pointer,
a scalar, but we have to convert to rvalue.

It's sort of sad that this corner case causes so much trouble: I think
we have to do the conversion only because of ref-qualifiers, so that
the correct operator function is chosen.

A way out may be setting a flag on the V_C_E that indicates it is an
rvalue, rather than performing the conversion above.  This was your
idea so I don't want to take credit for it.  Should I go ahead and
try it?
 
> > Bootstrapped/regtested on x86_64-pc-linux-gnu, ok for trunk?
> > 
> > 	PR c++/101165
> > 
> > gcc/c-family/ChangeLog:
> > 
> > 	* c-cppbuiltin.cc (c_cpp_builtins): Define __cpp_implicit_move.
> > 
> > gcc/cp/ChangeLog:
> > 
> > 	* call.cc (reference_binding): Check clk_implicit_rval in C++20 only.
> > 	* cp-tree.h (unparenthesized_id_or_class_member_access_p): Declare.
> > 	* pt.cc (unparenthesized_id_or_class_member_access_p): New function,
> > 	broken out of...
> > 	(do_auto_deduction): ...here.  Use it.
> > 	* tree.cc (xvalue_p): Check & clk_rvalueref, not == clk_rvalueref.
> > 	* typeck.cc (check_return_expr): In C++23, maybe call
> > 	treat_lvalue_as_rvalue_p before do_auto_deduction.  Allow implicit
> > 	move for functions returning a reference as well.
> > 
> > gcc/testsuite/ChangeLog:
> > 
> > 	* g++.dg/conversion/pr41426.C: Add dg-error for C++23.
> > 	* g++.dg/cpp0x/elision_weak.C: Likewise.
> > 	* g++.dg/cpp0x/move-return3.C: Only link in c++20_down.
> > 	* g++.dg/cpp1y/decltype-auto2.C: Add dg-error for C++23.
> > 	* g++.dg/cpp1y/lambda-generic-89419.C: Likewise.
> > 	* g++.dg/cpp23/feat-cxx2b.C: Test __cpp_implicit_move.
> > 	* g++.dg/gomp/pr56217.C: Only compile in c++20_down.
> > 	* g++.dg/warn/Wno-return-local-addr.C: Add dg-error for C++23.
> > 	* g++.dg/warn/Wreturn-local-addr.C: Adjust dg-error.
> > 	* g++.old-deja/g++.brendan/crash55.C: Add dg-error for C++23.
> > 	* g++.old-deja/g++.jason/temporary2.C: Likewise.
> > 	* g++.old-deja/g++.mike/p2846b.C: Only run in c++20_down.
> > 	* g++.dg/cpp1y/decltype-auto6.C: New test.
> > 	* g++.dg/cpp23/decltype1.C: New test.
> > 	* g++.dg/cpp23/decltype2.C: New test.
> > 	* g++.dg/cpp23/elision1.C: New test.
> > 	* g++.dg/cpp23/elision2.C: New test.
> > 	* g++.dg/cpp23/elision3.C: New test.
> > 	* g++.dg/cpp23/elision4.C: New test.
> > 	* g++.dg/cpp23/elision5.C: New test.
> > 	* g++.dg/cpp23/elision6.C: New test.
> > 	* g++.dg/cpp23/elision7.C: New test.
> > ---
> >   gcc/c-family/c-cppbuiltin.cc                  |   1 +
> >   gcc/cp/call.cc                                |   2 +-
> >   gcc/cp/cp-tree.h                              |   1 +
> >   gcc/cp/pt.cc                                  |  33 +++--
> >   gcc/cp/tree.cc                                |   2 +-
> >   gcc/cp/typeck.cc                              |  28 ++++-
> >   gcc/testsuite/g++.dg/conversion/pr41426.C     |  10 +-
> >   gcc/testsuite/g++.dg/cpp0x/elision_weak.C     |   4 +-
> >   gcc/testsuite/g++.dg/cpp0x/move-return3.C     |   3 +-
> >   gcc/testsuite/g++.dg/cpp1y/decltype-auto2.C   |   2 +-
> >   gcc/testsuite/g++.dg/cpp1y/decltype-auto6.C   |  19 +++
> >   .../g++.dg/cpp1y/lambda-generic-89419.C       |   6 +-
> >   gcc/testsuite/g++.dg/cpp23/decltype1.C        | 113 ++++++++++++++++++
> >   gcc/testsuite/g++.dg/cpp23/decltype2.C        |  49 ++++++++
> >   gcc/testsuite/g++.dg/cpp23/elision1.C         | 109 +++++++++++++++++
> >   gcc/testsuite/g++.dg/cpp23/elision2.C         |  46 +++++++
> >   gcc/testsuite/g++.dg/cpp23/elision3.C         |  16 +++
> >   gcc/testsuite/g++.dg/cpp23/elision4.C         |  38 ++++++
> >   gcc/testsuite/g++.dg/cpp23/elision5.C         |  53 ++++++++
> >   gcc/testsuite/g++.dg/cpp23/elision6.C         |  20 ++++
> >   gcc/testsuite/g++.dg/cpp23/elision7.C         |  72 +++++++++++
> >   gcc/testsuite/g++.dg/cpp23/feat-cxx2b.C       |   6 +
> >   gcc/testsuite/g++.dg/gomp/pr56217.C           |   4 +-
> >   .../g++.dg/warn/Wno-return-local-addr.C       |   2 +-
> >   .../g++.dg/warn/Wreturn-local-addr.C          |   2 +-
> >   .../g++.old-deja/g++.brendan/crash55.C        |   3 +-
> >   .../g++.old-deja/g++.jason/temporary2.C       |   2 +-
> >   gcc/testsuite/g++.old-deja/g++.mike/p2846b.C  |   4 +-
> >   28 files changed, 613 insertions(+), 37 deletions(-)
> >   create mode 100644 gcc/testsuite/g++.dg/cpp1y/decltype-auto6.C
> >   create mode 100644 gcc/testsuite/g++.dg/cpp23/decltype1.C
> >   create mode 100644 gcc/testsuite/g++.dg/cpp23/decltype2.C
> >   create mode 100644 gcc/testsuite/g++.dg/cpp23/elision1.C
> >   create mode 100644 gcc/testsuite/g++.dg/cpp23/elision2.C
> >   create mode 100644 gcc/testsuite/g++.dg/cpp23/elision3.C
> >   create mode 100644 gcc/testsuite/g++.dg/cpp23/elision4.C
> >   create mode 100644 gcc/testsuite/g++.dg/cpp23/elision5.C
> >   create mode 100644 gcc/testsuite/g++.dg/cpp23/elision6.C
> >   create mode 100644 gcc/testsuite/g++.dg/cpp23/elision7.C
> > 
> > diff --git a/gcc/c-family/c-cppbuiltin.cc b/gcc/c-family/c-cppbuiltin.cc
> > index a1557eb23d5..dd8b20d0c34 100644
> > --- a/gcc/c-family/c-cppbuiltin.cc
> > +++ b/gcc/c-family/c-cppbuiltin.cc
> > @@ -1081,6 +1081,7 @@ c_cpp_builtins (cpp_reader *pfile)
> >   	  cpp_define (pfile, "__cpp_constexpr=202110L");
> >   	  cpp_define (pfile, "__cpp_multidimensional_subscript=202110L");
> >   	  cpp_define (pfile, "__cpp_named_character_escapes=202207L");
> > +	  cpp_define (pfile, "__cpp_implicit_move=202207L");
> >   	}
> >         if (flag_concepts)
> >           {
> > diff --git a/gcc/cp/call.cc b/gcc/cp/call.cc
> > index d107a2814dc..cb4ae9ecf9d 100644
> > --- a/gcc/cp/call.cc
> > +++ b/gcc/cp/call.cc
> > @@ -1865,7 +1865,7 @@ reference_binding (tree rto, tree rfrom, tree expr, bool c_cast_p, int flags,
> >         /* Nor the reverse.  */
> >         if (!is_lvalue && !TYPE_REF_IS_RVALUE (rto)
> >   	  /* Unless it's really an lvalue.  */
> 
> Please add a bit about C++20 vs C++23 in this comment.

Done.
 
> > -	  && !(cxx_dialect >= cxx20
> > +	  && !(cxx_dialect == cxx20
> >   	       && (gl_kind & clk_implicit_rval))
> >   	  && (!CP_TYPE_CONST_NON_VOLATILE_P (to)
> >   	      || (flags & LOOKUP_NO_RVAL_BIND))
> > diff --git a/gcc/cp/cp-tree.h b/gcc/cp/cp-tree.h
> > index 7b28405c3ac..4cdbbc34cb3 100644
> > --- a/gcc/cp/cp-tree.h
> > +++ b/gcc/cp/cp-tree.h
> > @@ -7278,6 +7278,7 @@ extern tree make_constrained_decltype_auto	(tree, tree);
> >   extern tree make_template_placeholder		(tree);
> >   extern bool template_placeholder_p		(tree);
> >   extern bool ctad_template_p			(tree);
> > +extern bool unparenthesized_id_or_class_member_access_p (tree);
> >   extern tree do_auto_deduction                   (tree, tree, tree,
> >                                                    tsubst_flags_t
> >   						 = tf_warning_or_error,
> > diff --git a/gcc/cp/pt.cc b/gcc/cp/pt.cc
> > index cd0d8920ed0..b347a9a78fc 100644
> > --- a/gcc/cp/pt.cc
> > +++ b/gcc/cp/pt.cc
> > @@ -30405,6 +30405,26 @@ do_class_deduction (tree ptype, tree tmpl, tree init,
> >   				  cp_type_quals (ptype));
> >   }
> > +/* Return true if INIT is an unparenthesized id-expression or an
> > +   unparenthesized class member access.  Used for the argument of
> > +   decltype(auto).  */
> > +
> > +bool
> > +unparenthesized_id_or_class_member_access_p (tree init)
> > +{
> > +  STRIP_ANY_LOCATION_WRAPPER (init);
> > +
> 
> I wonder about looking through IMPLICIT_RVALUE_P here...

Unwrapping an IMPLICIT_RVALUE_P is really ugly, and teaching
unparenthesized_ about IMPLICIT_RVALUE_P wouldn't be enough,
since we'd have to unwrap INIT as well so that finish_decltype_type
gets the decl, not '*x'.  However, I have...
 
> > +  /* We need to be able to tell '(r)' and 'r' apart (when it's of
> > +     reference type).  Only the latter is an id-expression.  */
> > +  if (REFERENCE_REF_P (init)
> > +      && !REF_PARENTHESIZED_P (init))
> > +    init = TREE_OPERAND (init, 0);
> > +  return (DECL_P (init)
> > +	  || ((TREE_CODE (init) == COMPONENT_REF
> > +	       || TREE_CODE (init) == SCOPE_REF)
> > +	      && !REF_PARENTHESIZED_P (init)));
> > +}
> > +
> >   /* Replace occurrences of 'auto' in TYPE with the appropriate type deduced
> >      from INIT.  AUTO_NODE is the TEMPLATE_TYPE_PARM used for 'auto' in TYPE.
> >      The CONTEXT determines the context in which auto deduction is performed
> > @@ -30501,18 +30521,7 @@ do_auto_deduction (tree type, tree init, tree auto_node,
> >       }
> >     else if (AUTO_IS_DECLTYPE (auto_node))
> >       {
> > -      /* Figure out if INIT is an unparenthesized id-expression or an
> > -	 unparenthesized class member access.  */
> > -      tree stripped_init = tree_strip_any_location_wrapper (init);
> > -      /* We need to be able to tell '(r)' and 'r' apart (when it's of
> > -	 reference type).  Only the latter is an id-expression.  */
> > -      if (REFERENCE_REF_P (stripped_init)
> > -	  && !REF_PARENTHESIZED_P (stripped_init))
> > -	stripped_init = TREE_OPERAND (stripped_init, 0);
> > -      const bool id = (DECL_P (stripped_init)
> > -		       || ((TREE_CODE (stripped_init) == COMPONENT_REF
> > -			    || TREE_CODE (stripped_init) == SCOPE_REF)
> > -			   && !REF_PARENTHESIZED_P (stripped_init)));
> > +      const bool id = unparenthesized_id_or_class_member_access_p (init);
> >         tree deduced = finish_decltype_type (init, id, complain);
> >         deduced = canonicalize_type_argument (deduced, complain);
> >         if (deduced == error_mark_node)
> > diff --git a/gcc/cp/tree.cc b/gcc/cp/tree.cc
> > index c678e3b9c4c..226ffd48519 100644
> > --- a/gcc/cp/tree.cc
> > +++ b/gcc/cp/tree.cc
> > @@ -382,7 +382,7 @@ obvalue_p (const_tree ref)
> >   bool
> >   xvalue_p (const_tree ref)
> >   {
> > -  return (lvalue_kind (ref) == clk_rvalueref);
> > +  return (lvalue_kind (ref) & clk_rvalueref);
> >   }
> >   /* True if REF is a bit-field.  */
> > diff --git a/gcc/cp/typeck.cc b/gcc/cp/typeck.cc
> > index b99947c10fd..fe88c1a7b84 100644
> > --- a/gcc/cp/typeck.cc
> > +++ b/gcc/cp/typeck.cc
> > @@ -10876,8 +10893,9 @@ check_return_expr (tree retval, bool *no_warning)
> >   	 the conditions for the named return value optimization.  */
> >         bool converted = false;
> >         tree moved;
> > -      /* This is only interesting for class type.  */
> > -      if (CLASS_TYPE_P (functype)
> > +      /* Until C++23, this was only interesting for class type.  */
> > +      if ((CLASS_TYPE_P (functype)
> > +	   || (cxx_dialect >= cxx23 && TYPE_REF_P (functype)))
> >   	  && (moved = treat_lvalue_as_rvalue_p (retval, /*return*/true)))
> 
> ...and calling treat_lvalue_as_rvalue_p before auto deduction...
> 
> >   	{
> >   	  if (cxx_dialect < cxx20)
> > @@ -10679,10 +10679,27 @@ check_return_expr (tree retval, bool *no_warning)
> >   	}
> >         else
> >   	{
> > -	  if (!retval)
> > -	    retval = void_node;
> >   	  auto_node = type_uses_auto (pattern);
> > -	  type = do_auto_deduction (pattern, retval, auto_node,
> > +	  tree r;
> > +	  if (!retval)
> > +	    r = void_node;
> > +	  /* In C++23, we must deduce the type to int&& for code like
> > +	       decltype(auto) f(int&& x) { return (x); }
> > +	     or
> > +	       auto&& f(int x) { return x; }
> > +	     so we use treat_lvalue_as_rvalue_p earlier.  But don't do it for
> > +	       decltype(auto) f(int x) { return x; }
> > +	     where we should deduce 'int' rather than 'int&&'; transmogrifying
> > +	     RETVAL to an rvalue would break that.  */
> > +	  else if (cxx_dialect >= cxx23
> > +		   && (!AUTO_IS_DECLTYPE (auto_node)
> > +		       || !unparenthesized_id_or_class_member_access_p (retval))
> > +		   && (r = treat_lvalue_as_rvalue_p
> > +		       (maybe_undo_parenthesized_ref (retval), /*return*/true)))
> > +	    /* Use it.  */;
> 
> ...so we don't need to do this?

...moved this into do_auto_deduction, where I check context == adc_return_type.

FWIW this is what I have at this moment.
 
-- >8 --
This patch implements https://wg21.link/p2266, which, once again,
changes the implicit move rules.  Here's a brief summary of various
changes in this area:

r125211: Introduced moving from certain lvalues when returning them
r171071: CWG 1148, enable move from value parameter on return
r212099: CWG 1579, it's OK to call a converting ctor taking an rvalue
r251035: CWG 1579, do maybe-rvalue overload resolution twice
r11-2411: Avoid calling const copy ctor on implicit move
r11-2412: C++20 implicit move changes, remove the fallback overload
          resolution, allow move on throw of parameters and implicit
	  move of rvalue references

P2266 enables the implicit move for functions that return references.  This
was a one-line change: check TYPE_REF_P.  That is, we will now perform
a move in

  X&& foo (X&& x) {
    return x;
  }

P2266 also removes the fallback overload resolution, but this was
resolved by r11-2412: we only do convert_for_initialization with
LOOKUP_PREFER_RVALUE in C++17 and older.
P2266 also says that a returned move-eligible id-expression is always an
xvalue.  This required some further short, but nontrivial changes,
especially when it comes to deduction, because we have to pay attention
to whether we have auto, auto&& (which is like T&&), or decltype(auto)
with (un)parenthesized argument.  In C++23,

  decltype(auto) f(int&& x) { return (x); }
  auto&& f(int x) { return x; }

both should deduce to 'int&&' but

  decltype(auto) f(int x) { return x; }

should deduce to 'int'.  A cornucopia of tests attached.  I've also
verified that we behave like clang++.

xvalue_p seemed to be broken: since the introduction of clk_implicit_rval,
it cannot use '==' when checking for clk_rvalueref.

Since this change breaks code, it's only enabled in C++23.  In
particular, this code will not compile in C++23:

  int& g(int&& x) { return x; }

because x is now treated as an rvalue, and you can't bind a non-const lvalue
reference to an rvalue.

There's one FIXME in elision1.C:five, which we should compile but reject
with "passing 'Mutt' as 'this' argument discards qualifiers".  This is
PR106882.

	PR c++/101165

gcc/c-family/ChangeLog:

	* c-cppbuiltin.cc (c_cpp_builtins): Define __cpp_implicit_move.

gcc/cp/ChangeLog:

	* call.cc (reference_binding): Check clk_implicit_rval in C++20 only.
	* cp-tree.h (unparenthesized_id_or_class_member_access_p): Declare.
	* pt.cc (unparenthesized_id_or_class_member_access_p): New function,
	broken out of...
	(do_auto_deduction): ...here.  Use it.  In C++23, maybe call
	treat_lvalue_as_rvalue_p.
	* tree.cc (xvalue_p): Check & clk_rvalueref, not == clk_rvalueref.
	* typeck.cc (check_return_expr): Allow implicit move for functions
	returning a reference as well.

gcc/testsuite/ChangeLog:

	* g++.dg/conversion/pr41426.C: Add dg-error for C++23.
	* g++.dg/cpp0x/elision_weak.C: Likewise.
	* g++.dg/cpp0x/move-return3.C: Only link in c++20_down.
	* g++.dg/cpp1y/decltype-auto2.C: Add dg-error for C++23.
	* g++.dg/cpp1y/lambda-generic-89419.C: Likewise.
	* g++.dg/cpp23/feat-cxx2b.C: Test __cpp_implicit_move.
	* g++.dg/gomp/pr56217.C: Only compile in c++20_down.
	* g++.dg/warn/Wno-return-local-addr.C: Add dg-error for C++23.
	* g++.dg/warn/Wreturn-local-addr.C: Adjust dg-error.
	* g++.old-deja/g++.brendan/crash55.C: Add dg-error for C++23.
	* g++.old-deja/g++.jason/temporary2.C: Likewise.
	* g++.old-deja/g++.mike/p2846b.C: Only run in c++20_down.
	* g++.dg/cpp1y/decltype-auto6.C: New test.
	* g++.dg/cpp23/decltype1.C: New test.
	* g++.dg/cpp23/decltype2.C: New test.
	* g++.dg/cpp23/elision1.C: New test.
	* g++.dg/cpp23/elision2.C: New test.
	* g++.dg/cpp23/elision3.C: New test.
	* g++.dg/cpp23/elision4.C: New test.
	* g++.dg/cpp23/elision5.C: New test.
	* g++.dg/cpp23/elision6.C: New test.
	* g++.dg/cpp23/elision7.C: New test.
---
 gcc/c-family/c-cppbuiltin.cc                  |   1 +
 gcc/cp/call.cc                                |   6 +-
 gcc/cp/cp-tree.h                              |   1 +
 gcc/cp/pt.cc                                  |  50 ++++++--
 gcc/cp/tree.cc                                |   2 +-
 gcc/cp/typeck.cc                              |   6 +-
 gcc/testsuite/g++.dg/conversion/pr41426.C     |  10 +-
 gcc/testsuite/g++.dg/cpp0x/elision_weak.C     |   4 +-
 gcc/testsuite/g++.dg/cpp0x/move-return3.C     |   3 +-
 gcc/testsuite/g++.dg/cpp1y/decltype-auto2.C   |   2 +-
 gcc/testsuite/g++.dg/cpp1y/decltype-auto6.C   |  19 +++
 .../g++.dg/cpp1y/lambda-generic-89419.C       |   6 +-
 gcc/testsuite/g++.dg/cpp23/decltype1.C        | 113 ++++++++++++++++++
 gcc/testsuite/g++.dg/cpp23/decltype2.C        |  49 ++++++++
 gcc/testsuite/g++.dg/cpp23/elision1.C         | 109 +++++++++++++++++
 gcc/testsuite/g++.dg/cpp23/elision2.C         |  46 +++++++
 gcc/testsuite/g++.dg/cpp23/elision3.C         |  16 +++
 gcc/testsuite/g++.dg/cpp23/elision4.C         |  38 ++++++
 gcc/testsuite/g++.dg/cpp23/elision5.C         |  53 ++++++++
 gcc/testsuite/g++.dg/cpp23/elision6.C         |  20 ++++
 gcc/testsuite/g++.dg/cpp23/elision7.C         |  72 +++++++++++
 gcc/testsuite/g++.dg/cpp23/feat-cxx2b.C       |   6 +
 gcc/testsuite/g++.dg/gomp/pr56217.C           |   4 +-
 .../g++.dg/warn/Wno-return-local-addr.C       |   2 +-
 .../g++.dg/warn/Wreturn-local-addr.C          |   2 +-
 .../g++.old-deja/g++.brendan/crash55.C        |   3 +-
 .../g++.old-deja/g++.jason/temporary2.C       |   2 +-
 gcc/testsuite/g++.old-deja/g++.mike/p2846b.C  |   4 +-
 28 files changed, 614 insertions(+), 35 deletions(-)
 create mode 100644 gcc/testsuite/g++.dg/cpp1y/decltype-auto6.C
 create mode 100644 gcc/testsuite/g++.dg/cpp23/decltype1.C
 create mode 100644 gcc/testsuite/g++.dg/cpp23/decltype2.C
 create mode 100644 gcc/testsuite/g++.dg/cpp23/elision1.C
 create mode 100644 gcc/testsuite/g++.dg/cpp23/elision2.C
 create mode 100644 gcc/testsuite/g++.dg/cpp23/elision3.C
 create mode 100644 gcc/testsuite/g++.dg/cpp23/elision4.C
 create mode 100644 gcc/testsuite/g++.dg/cpp23/elision5.C
 create mode 100644 gcc/testsuite/g++.dg/cpp23/elision6.C
 create mode 100644 gcc/testsuite/g++.dg/cpp23/elision7.C

diff --git a/gcc/c-family/c-cppbuiltin.cc b/gcc/c-family/c-cppbuiltin.cc
index a1557eb23d5..dd8b20d0c34 100644
--- a/gcc/c-family/c-cppbuiltin.cc
+++ b/gcc/c-family/c-cppbuiltin.cc
@@ -1081,6 +1081,7 @@ c_cpp_builtins (cpp_reader *pfile)
 	  cpp_define (pfile, "__cpp_constexpr=202110L");
 	  cpp_define (pfile, "__cpp_multidimensional_subscript=202110L");
 	  cpp_define (pfile, "__cpp_named_character_escapes=202207L");
+	  cpp_define (pfile, "__cpp_implicit_move=202207L");
 	}
       if (flag_concepts)
         {
diff --git a/gcc/cp/call.cc b/gcc/cp/call.cc
index 7e9289fc2d0..1a786fc96df 100644
--- a/gcc/cp/call.cc
+++ b/gcc/cp/call.cc
@@ -1864,8 +1864,10 @@ reference_binding (tree rto, tree rfrom, tree expr, bool c_cast_p, int flags,
 
       /* Nor the reverse.  */
       if (!is_lvalue && !TYPE_REF_IS_RVALUE (rto)
-	  /* Unless it's really an lvalue.  */
-	  && !(cxx_dialect >= cxx20
+	  /* Unless it's really a C++20 lvalue being treated as an xvalue.
+	     But in C++23, such an expression is just an xvalue, not a special
+	     lvalue, so the binding is once again ill-formed.  */
+	  && !(cxx_dialect == cxx20
 	       && (gl_kind & clk_implicit_rval))
 	  && (!CP_TYPE_CONST_NON_VOLATILE_P (to)
 	      || (flags & LOOKUP_NO_RVAL_BIND))
diff --git a/gcc/cp/cp-tree.h b/gcc/cp/cp-tree.h
index f19ecafc266..35fea6ac3cf 100644
--- a/gcc/cp/cp-tree.h
+++ b/gcc/cp/cp-tree.h
@@ -7278,6 +7278,7 @@ extern tree make_constrained_decltype_auto	(tree, tree);
 extern tree make_template_placeholder		(tree);
 extern bool template_placeholder_p		(tree);
 extern bool ctad_template_p			(tree);
+extern bool unparenthesized_id_or_class_member_access_p (tree);
 extern tree do_auto_deduction                   (tree, tree, tree,
                                                  tsubst_flags_t
 						 = tf_warning_or_error,
diff --git a/gcc/cp/pt.cc b/gcc/cp/pt.cc
index db4e808adec..5833fd54959 100644
--- a/gcc/cp/pt.cc
+++ b/gcc/cp/pt.cc
@@ -30407,6 +30407,26 @@ do_class_deduction (tree ptype, tree tmpl, tree init,
 				  cp_type_quals (ptype));
 }
 
+/* Return true if INIT is an unparenthesized id-expression or an
+   unparenthesized class member access.  Used for the argument of
+   decltype(auto).  */
+
+bool
+unparenthesized_id_or_class_member_access_p (tree init)
+{
+  STRIP_ANY_LOCATION_WRAPPER (init);
+
+  /* We need to be able to tell '(r)' and 'r' apart (when it's of
+     reference type).  Only the latter is an id-expression.  */
+  if (REFERENCE_REF_P (init)
+      && !REF_PARENTHESIZED_P (init))
+    init = TREE_OPERAND (init, 0);
+  return (DECL_P (init)
+	  || ((TREE_CODE (init) == COMPONENT_REF
+	       || TREE_CODE (init) == SCOPE_REF)
+	      && !REF_PARENTHESIZED_P (init)));
+}
+
 /* Replace occurrences of 'auto' in TYPE with the appropriate type deduced
    from INIT.  AUTO_NODE is the TEMPLATE_TYPE_PARM used for 'auto' in TYPE.
    The CONTEXT determines the context in which auto deduction is performed
@@ -30442,6 +30462,23 @@ do_auto_deduction (tree type, tree init, tree auto_node,
      auto_node.  */
   complain &= ~tf_partial;
 
+  /* In C++23, we must deduce the type to int&& for code like
+       decltype(auto) f(int&& x) { return (x); }
+     or
+       auto&& f(int x) { return x; }
+     so we use treat_lvalue_as_rvalue_p.  But don't do it for
+       decltype(auto) f(int x) { return x; }
+     where we should deduce 'int' rather than 'int&&'; transmogrifying
+     INIT to an rvalue would break that.  */
+  tree r;
+  if (cxx_dialect >= cxx23
+      && context == adc_return_type
+      && (!AUTO_IS_DECLTYPE (auto_node)
+	  || !unparenthesized_id_or_class_member_access_p (init))
+      && (r = treat_lvalue_as_rvalue_p (maybe_undo_parenthesized_ref (init),
+					/*return*/true)))
+    init = r;
+
   if (tree tmpl = CLASS_PLACEHOLDER_TEMPLATE (auto_node))
     /* C++17 class template argument deduction.  */
     return do_class_deduction (type, tmpl, init, flags, complain);
@@ -30503,18 +30540,7 @@ do_auto_deduction (tree type, tree init, tree auto_node,
     }
   else if (AUTO_IS_DECLTYPE (auto_node))
     {
-      /* Figure out if INIT is an unparenthesized id-expression or an
-	 unparenthesized class member access.  */
-      tree stripped_init = tree_strip_any_location_wrapper (init);
-      /* We need to be able to tell '(r)' and 'r' apart (when it's of
-	 reference type).  Only the latter is an id-expression.  */
-      if (REFERENCE_REF_P (stripped_init)
-	  && !REF_PARENTHESIZED_P (stripped_init))
-	stripped_init = TREE_OPERAND (stripped_init, 0);
-      const bool id = (DECL_P (stripped_init)
-		       || ((TREE_CODE (stripped_init) == COMPONENT_REF
-			    || TREE_CODE (stripped_init) == SCOPE_REF)
-			   && !REF_PARENTHESIZED_P (stripped_init)));
+      const bool id = unparenthesized_id_or_class_member_access_p (init);
       tree deduced = finish_decltype_type (init, id, complain);
       deduced = canonicalize_type_argument (deduced, complain);
       if (deduced == error_mark_node)
diff --git a/gcc/cp/tree.cc b/gcc/cp/tree.cc
index d0bd41ae5a0..ea4dfc651bb 100644
--- a/gcc/cp/tree.cc
+++ b/gcc/cp/tree.cc
@@ -382,7 +382,7 @@ obvalue_p (const_tree ref)
 bool
 xvalue_p (const_tree ref)
 {
-  return (lvalue_kind (ref) == clk_rvalueref);
+  return (lvalue_kind (ref) & clk_rvalueref);
 }
 
 /* True if REF is a bit-field.  */
diff --git a/gcc/cp/typeck.cc b/gcc/cp/typeck.cc
index 22d834d3a58..83545ab66ef 100644
--- a/gcc/cp/typeck.cc
+++ b/gcc/cp/typeck.cc
@@ -10872,8 +10872,10 @@ check_return_expr (tree retval, bool *no_warning)
 	 the conditions for the named return value optimization.  */
       bool converted = false;
       tree moved;
-      /* This is only interesting for class type.  */
-      if (CLASS_TYPE_P (functype)
+      /* Until C++23, this was only interesting for class type, but
+	 (FIXME) in C++23, we should do the below for any type.  */
+      if ((CLASS_TYPE_P (functype)
+	   || (cxx_dialect >= cxx23 && TYPE_REF_P (functype)))
 	  && (moved = treat_lvalue_as_rvalue_p (retval, /*return*/true)))
 	{
 	  if (cxx_dialect < cxx20)
diff --git a/gcc/testsuite/g++.dg/conversion/pr41426.C b/gcc/testsuite/g++.dg/conversion/pr41426.C
index 5493a91ecfa..b4ecbca5f3a 100644
--- a/gcc/testsuite/g++.dg/conversion/pr41426.C
+++ b/gcc/testsuite/g++.dg/conversion/pr41426.C
@@ -11,19 +11,20 @@ struct A
 A<float> g1()
 {
    float f[] = {1.1f, 2.3f};
-   return f;
+   return f; // { dg-error "cannot bind non-const" "" { target c++23 } }
 }
 
 const A<float> &g3()
 {
    float f[] = {1.1f, 2.3f};
-   return f; // { dg-warning "returning reference to temporary" }
+   return f; // { dg-warning "returning reference to temporary" "" { target c++20_down } }
+// { dg-error "non-const lvalue|invalid user-defined conversion" "" { target c++23 } .-1 }
 }
 
 A<float> &g4()
 {
    float f[] = {1.1f, 2.3f};
-   return f; // { dg-error "cannot bind non-const lvalue ref" }
+   return f; // { dg-error "cannot bind non-const lvalue ref|invalid user-defined conversion" }
 }
 
 struct B
@@ -35,6 +36,5 @@ struct B
 B g2()
 {
    int c[10];
-   return c;
+   return c; // { dg-error "non-const lvalue" "" { target c++23 } }
 }
-
diff --git a/gcc/testsuite/g++.dg/cpp0x/elision_weak.C b/gcc/testsuite/g++.dg/cpp0x/elision_weak.C
index e8ba7551d84..ddd12743130 100644
--- a/gcc/testsuite/g++.dg/cpp0x/elision_weak.C
+++ b/gcc/testsuite/g++.dg/cpp0x/elision_weak.C
@@ -9,11 +9,11 @@ struct S
 S f()
 {
   S s;
-  return s;
+  return s; // { dg-error "cannot bind non-const lvalue reference" "" { target c++23 } }
 }
 
 void g()
 {
   S s;
-  throw s;
+  throw s; // { dg-error "cannot bind non-const lvalue reference" "" { target c++23 } }
 }
diff --git a/gcc/testsuite/g++.dg/cpp0x/move-return3.C b/gcc/testsuite/g++.dg/cpp0x/move-return3.C
index c79f0591936..30a936fb35a 100644
--- a/gcc/testsuite/g++.dg/cpp0x/move-return3.C
+++ b/gcc/testsuite/g++.dg/cpp0x/move-return3.C
@@ -1,6 +1,7 @@
 // PR c++/91212
 // Test that C++11 implicit move semantics don't call the const copy.
-// { dg-do link }
+// In C++23, we call #2.
+// { dg-do link { target c++20_down } }
 
 struct T { int i; };
 
diff --git a/gcc/testsuite/g++.dg/cpp1y/decltype-auto2.C b/gcc/testsuite/g++.dg/cpp1y/decltype-auto2.C
index 56e011e36f4..24b32edfacf 100644
--- a/gcc/testsuite/g++.dg/cpp1y/decltype-auto2.C
+++ b/gcc/testsuite/g++.dg/cpp1y/decltype-auto2.C
@@ -8,5 +8,5 @@ auto constexpr RtoL1(T&& r) -> decltype(auto) {
 int main() {
     int t;
     int x{3};
-    decltype (RtoL1(x+0)) y = t;
+    decltype (RtoL1(x+0)) y = t; // { dg-error "cannot bind rvalue reference" "" { target c++23 } }
 }
diff --git a/gcc/testsuite/g++.dg/cpp1y/decltype-auto6.C b/gcc/testsuite/g++.dg/cpp1y/decltype-auto6.C
new file mode 100644
index 00000000000..da53278645c
--- /dev/null
+++ b/gcc/testsuite/g++.dg/cpp1y/decltype-auto6.C
@@ -0,0 +1,19 @@
+// PR c++/101165 - P2266R1 - Simpler implicit move
+// { dg-do compile { target c++14 } }
+// A variant of cxx23/elision1.C:eight, just with ().
+
+struct Widget {
+  Widget(Widget&&);
+};
+
+Widget val();
+
+decltype(auto)
+foo ()
+{
+  decltype(auto) x = val();  // OK, x is Widget
+  // We deduce the return type to int&&, therefore we're doing something
+  // we ought not to be doing -- returning a reference to a local variable!
+  // In C++20, we deduce to int&, but that has the same problem!
+  return (x); // { dg-warning "reference to local variable" }
+}
diff --git a/gcc/testsuite/g++.dg/cpp1y/lambda-generic-89419.C b/gcc/testsuite/g++.dg/cpp1y/lambda-generic-89419.C
index 46ce909f3b8..8e64d4e64ab 100644
--- a/gcc/testsuite/g++.dg/cpp1y/lambda-generic-89419.C
+++ b/gcc/testsuite/g++.dg/cpp1y/lambda-generic-89419.C
@@ -2,7 +2,7 @@
 // { dg-do compile { target c++14 } }
 
 struct A;
-struct B {
+struct B { // { dg-error "cannot bind" "" { target c++23 } }
   struct C { C (); C (C &); } b;
 };
 struct D { A operator* (); };
@@ -13,12 +13,12 @@ struct E {
   auto bar () { return e; }
   D e;
 };
-struct F { B f; int g; };
+struct F { B f; int g; }; // { dg-error "use of deleted function" "" { target c++23 } }
 
 int
 main ()
 {
   E e;
   auto f = *e.bar ();
-  auto i = [&] { F g; g.g = 1; auto h = [&](auto) { g.g = 0; }; f.foo (h); return g; };
+  auto i = [&] { F g; g.g = 1; auto h = [&](auto) { g.g = 0; }; f.foo (h); return g; }; // { dg-error "use of deleted function" "" { target c++23 } }
 }
diff --git a/gcc/testsuite/g++.dg/cpp23/decltype1.C b/gcc/testsuite/g++.dg/cpp23/decltype1.C
new file mode 100644
index 00000000000..6f3cd0d45d5
--- /dev/null
+++ b/gcc/testsuite/g++.dg/cpp23/decltype1.C
@@ -0,0 +1,113 @@
+// PR c++/101165 - P2266R1 - Simpler implicit move
+// { dg-do compile { target c++23 } }
+// Tests from P2266R1, decltype-related changes in
+// $ 3.2.1. Interaction with decltype and decltype(auto)
+
+template<typename T, typename U>
+struct same_type { static const bool value = false; };
+
+template<typename T>
+struct same_type<T, T> { static const bool value = true; };
+
+auto f1(int x) -> decltype(x) { return (x); }
+static_assert(same_type<decltype(f1), int (int)>::value);
+auto f2(int x) -> decltype((x)) { return (x); }   // { dg-error "cannot bind" }
+static_assert(same_type<decltype(f2), int& (int)>::value);
+auto f3(int x) -> decltype(auto) { return (x); }  // { dg-warning "reference to local variable" }
+static_assert(same_type<decltype(f3), int&& (int)>::value);
+auto g1(int x) -> decltype(x) { return x; }
+static_assert(same_type<decltype(g1), int (int)>::value);
+auto g2(int x) -> decltype((x)) { return x; }	  // { dg-error "cannot bind" }
+static_assert(same_type<decltype(g2), int& (int)>::value);
+auto g3(int x) -> decltype(auto) { return x; }
+static_assert(same_type<decltype(g3), int (int)>::value);
+
+// Note that f2 and g2 are well-formed in C++20, but we propose to make
+// f2 and g2 ill-formed, because they attempt to bind an lvalue reference
+// to a move-eligible xvalue expression.
+
+struct X { };
+
+auto
+f4 (X x)
+{
+  return x;
+}
+static_assert(same_type<decltype(f4), X(X)>::value);
+
+auto&
+f5 (X x)
+{
+  return x; // { dg-error "cannot bind non-const lvalue reference" }
+}
+static_assert(same_type<decltype(f5), X&(X)>::value);
+
+auto&&
+f6 (X x)
+{
+  return x; // { dg-warning "reference to local variable" }
+}
+static_assert(same_type<decltype(f6), X&&(X)>::value);
+
+auto
+f7 (X x)
+{
+  return (x);
+}
+static_assert(same_type<decltype(f7), X(X)>::value);
+
+auto&
+f8 (X x)
+{
+  return (x); // { dg-error "cannot bind non-const lvalue reference" }
+}
+static_assert(same_type<decltype(f8), X&(X)>::value);
+
+auto&&
+f9 (X x)
+{
+  return (x); // { dg-warning "reference to local variable" }
+}
+static_assert(same_type<decltype(f9), X&&(X)>::value);
+
+decltype(auto)
+f10 (X x)
+{
+  return x;
+}
+static_assert(same_type<decltype(f10), X(X)>::value);
+
+decltype(auto)
+f11 (X x)
+{
+  return (x); // { dg-warning "reference to local variable" }
+}
+static_assert(same_type<decltype(f11), X&&(X)>::value);
+
+decltype(auto)
+f12 (X& x)
+{
+  return x;
+}
+static_assert(same_type<decltype(f12), X&(X&)>::value);
+
+decltype(auto)
+f13 (X& x)
+{
+  return (x);
+}
+static_assert(same_type<decltype(f13), X&(X&)>::value);
+
+decltype(auto)
+f14 (X&& x)
+{
+  return x;
+}
+static_assert(same_type<decltype(f14), X&&(X&&)>::value);
+
+decltype(auto)
+f15 (X&& x)
+{
+  return (x);
+}
+static_assert(same_type<decltype(f15), X&&(X&&)>::value);
diff --git a/gcc/testsuite/g++.dg/cpp23/decltype2.C b/gcc/testsuite/g++.dg/cpp23/decltype2.C
new file mode 100644
index 00000000000..84679c48f82
--- /dev/null
+++ b/gcc/testsuite/g++.dg/cpp23/decltype2.C
@@ -0,0 +1,49 @@
+// PR c++/101165 - P2266R1 - Simpler implicit move
+// { dg-do compile { target c++23 } }
+// Test decltype(auto) more.
+
+template<typename T, typename U>
+struct same_type { static const bool value = false; };
+
+template<typename T>
+struct same_type<T, T> { static const bool value = true; };
+
+struct Widget {
+  int x;
+};
+
+Widget wg;
+
+decltype(auto) fn0(Widget&& x) {
+    return (::wg);
+}
+static_assert(same_type<decltype(fn0), Widget& (Widget&&)>::value);
+
+decltype(auto) fn1(Widget&& x) {
+    return ::wg;
+}
+static_assert(same_type<decltype(fn1), Widget (Widget&&)>::value);
+
+decltype(auto) fn2() {
+    Widget w;
+    return w;
+}
+static_assert(same_type<decltype(fn2), Widget ()>::value);
+
+decltype(auto) fn3() {
+    Widget w;
+    return (w); // { dg-warning "reference to local variable" }
+}
+static_assert(same_type<decltype(fn3), Widget&& ()>::value);
+
+decltype(auto) fn4() {
+    Widget w;
+    return w.x;
+}
+static_assert(same_type<decltype(fn4), int ()>::value);
+
+decltype(auto) fn5() {
+    Widget w;
+    return (w.x); // { dg-warning "reference to local variable" }
+}
+static_assert(same_type<decltype(fn5), int& ()>::value);
diff --git a/gcc/testsuite/g++.dg/cpp23/elision1.C b/gcc/testsuite/g++.dg/cpp23/elision1.C
new file mode 100644
index 00000000000..8115739b7f9
--- /dev/null
+++ b/gcc/testsuite/g++.dg/cpp23/elision1.C
@@ -0,0 +1,109 @@
+// PR c++/101165 - P2266R1 - Simpler implicit move
+// { dg-do compile { target c++23 } }
+// Tests from P2266R1.
+
+namespace std {
+  template<typename _Tp>
+    struct remove_reference
+    { typedef _Tp   type; };
+
+  template<typename _Tp>
+    struct remove_reference<_Tp&>
+    { typedef _Tp   type; };
+
+  template<typename _Tp>
+    struct remove_reference<_Tp&&>
+    { typedef _Tp   type; };
+
+  template<typename _Tp>
+    constexpr typename std::remove_reference<_Tp>::type&&
+    move(_Tp&& __t) noexcept
+    { return static_cast<typename std::remove_reference<_Tp>::type&&>(__t); }
+}
+
+template<typename T, typename U>
+struct same_type { static const bool value = false; };
+
+template<typename T>
+struct same_type<T, T> { static const bool value = true; };
+
+struct Widget {
+    Widget(Widget&&);
+};
+
+struct RRefTaker {
+    RRefTaker(Widget&&);
+};
+
+struct Mutt {
+    operator int*() &&;
+};
+
+struct Jeff {
+    operator int&() &&;
+};
+
+Widget one(Widget w) {
+    return w;  // OK since C++11
+}
+
+RRefTaker two(Widget w) {
+    return w;  // OK since C++11 + CWG1579
+}
+
+RRefTaker three(Widget&& w) {
+    return w;  // OK since C++20 because P0527
+}
+
+// Tests that implicit move applies even to functions that return references.
+Widget&& four(Widget&& w) {
+    return w;  // OK since C++23
+}
+
+// FIXME This is supposed to work but we reject it with
+// error: passing 'Mutt' as 'this' argument discards qualifiers
+#if 0
+int* five(Mutt x) {
+    return x;  // OK since C++20 because P1155
+}
+#endif
+
+int& six(Jeff x) {
+    return x;
+}
+
+template<class T>
+T&& seven(T&& x) { return x; }
+
+void test_seven(Widget w) {
+    Widget& r = seven(w);
+    Widget&& rr = seven(std::move(w));
+}
+
+Widget val();
+Widget& lref();
+Widget&& rref();
+
+decltype(auto) eight() {
+    decltype(auto) x = val();  // OK, x is Widget
+    return x;  // OK, return type is Widget, we get copy elision
+}
+
+decltype(auto) nine() {
+    decltype(auto) x = lref();  // OK, x is Widget&
+    return x;  // OK, return type is Widget&
+}
+
+decltype(auto) ten() {
+  decltype(auto) x = rref();  // OK, x is Widget&&
+  // This was an error: return type is Widget&&, cannot bind to x.
+  // But in C++23, x is treated as an rvalue.
+  return x;
+}
+
+// Now returns Widget&&, not Widget&.
+// This is from $ 3.2.1. Interaction with decltype and decltype(auto).
+decltype(auto) eleven(Widget&& x) {
+    return (x);
+}
+static_assert(same_type<decltype(eleven), Widget&& (Widget&&)>::value);
diff --git a/gcc/testsuite/g++.dg/cpp23/elision2.C b/gcc/testsuite/g++.dg/cpp23/elision2.C
new file mode 100644
index 00000000000..ce2c7aeef66
--- /dev/null
+++ b/gcc/testsuite/g++.dg/cpp23/elision2.C
@@ -0,0 +1,46 @@
+// PR c++/101165 - P2266R1 - Simpler implicit move
+// { dg-do compile { target c++20 } }
+// Test from P2266R1, $ 3.3. Two overload resolutions are overly confusing.
+
+struct Widget {
+    Widget();
+    Widget(Widget&&);
+};
+
+struct Frodo {
+    Frodo(Widget&);
+    Frodo(Widget&&) = delete;
+};
+
+struct Sam {
+    Sam(Widget&) = delete; // #1
+    Sam(const Widget&);  // #2
+};
+
+Sam twelve() {
+    Widget w;
+    // This is supposed to calls #2 since C++20 because P1155.
+    // But we actually choose #1 since r11-2411 (in C++20 only).
+    return w; // { dg-error "deleted" "" { target c++20_only } }
+}
+
+Frodo thirteen() {
+    Widget w;
+    // This is a correct error in both C++20 and C++23.
+    return w;  // { dg-error "use of deleted function" }
+}
+
+struct Merry {};
+struct Pippin {};
+struct Together : Merry, Pippin {};
+struct Quest {
+    Quest(Merry&&);
+    Quest(Pippin&&);
+    Quest(Together&);
+};
+
+Quest fourteen() {
+  Together t;
+  // C++20: calls Quest(Together&).  Proposed: ill-formed.
+  return t; // { dg-error "ambiguous" "" { target c++23 } }
+}
diff --git a/gcc/testsuite/g++.dg/cpp23/elision3.C b/gcc/testsuite/g++.dg/cpp23/elision3.C
new file mode 100644
index 00000000000..246342e64d3
--- /dev/null
+++ b/gcc/testsuite/g++.dg/cpp23/elision3.C
@@ -0,0 +1,16 @@
+// PR c++/101165 - P2266R1 - Simpler implicit move
+// { dg-do compile { target c++23 } }
+// Test from P2266R1, $ 3.4. A specific case involving reference_wrapper.
+
+#include <functional>
+
+struct Widget {
+    Widget();
+    Widget(Widget&&);
+};
+
+std::reference_wrapper<Widget> fifteen() {
+    Widget w;
+    // OK until CWG1579; OK after LWG2993.  Proposed: ill-formed
+    return w;  // { dg-error "could not convert" }
+}
diff --git a/gcc/testsuite/g++.dg/cpp23/elision4.C b/gcc/testsuite/g++.dg/cpp23/elision4.C
new file mode 100644
index 00000000000..c19b86b8b5f
--- /dev/null
+++ b/gcc/testsuite/g++.dg/cpp23/elision4.C
@@ -0,0 +1,38 @@
+// PR c++/101165 - P2266R1 - Simpler implicit move
+// { dg-do compile { target c++23 } }
+// Test from P2266R1, $ 5.2. LibreOffice OString constructor.
+
+struct X {
+    X(auto&);
+};
+
+// The following compiles in C++20 (deducing X(char (&)[10])) but not
+// after P2266 (because the returned expression now has type char (&&)[10],
+// which cannot bind to auto&).
+X f() {
+    char a[10];
+    return a; // { dg-error "cannot bind non-const lvalue reference" }
+}
+
+// The solution was to change it by making the return convert explicitly
+// rather than implicitly:
+X fixed() {
+    char a[10];
+    return X(a);
+}
+
+// $ 5.3. LibreOffice o3tl::temporary
+
+template<class T>
+T& temporary1(T&& x) { return x; } // { dg-error "cannot bind non-const lvalue reference" }
+
+// Fixed by:
+template<class T>
+T& temporary2(T&& x) { return static_cast<T&>(x); }
+
+void
+test ()
+{
+  int& r1 = temporary1 (42);
+  int& r2 = temporary2 (42);
+}
diff --git a/gcc/testsuite/g++.dg/cpp23/elision5.C b/gcc/testsuite/g++.dg/cpp23/elision5.C
new file mode 100644
index 00000000000..a7d3e7c27c4
--- /dev/null
+++ b/gcc/testsuite/g++.dg/cpp23/elision5.C
@@ -0,0 +1,53 @@
+// PR c++/101165 - P2266R1 - Simpler implicit move
+// { dg-do compile { target c++23 } }
+// Test from [class.copy.elision]/4.
+
+class Thing {
+public:
+  Thing();
+  ~Thing();
+  Thing(Thing&&);
+private:
+  Thing(const Thing&);
+};
+
+Thing f(bool b) {
+  Thing t;
+  if (b)
+    throw t;            // OK, Thing(Thing&&) used (or elided) to throw t
+  return t;             // OK, Thing(Thing&&) used (or elided) to return t
+}
+
+Thing t2 = f(false);    // OK, no extra copy/move performed, t2 constructed by call to f
+
+struct Weird {
+  Weird();
+  Weird(Weird&);
+};
+
+Weird g(bool b) {
+  static Weird w1;
+  Weird w2;
+  if (b) {
+    return w1;  // OK: Weird(Weird&)
+  } else {
+    return w2;  // { dg-error "cannot bind non-const lvalue reference" }
+  }
+}
+
+int& h(bool b, int i) {
+  static int s;
+  if (b)
+    return s;   // OK
+  else
+    return i;   // { dg-error "cannot bind non-const lvalue reference" }
+}
+
+decltype(auto) h2(Thing t) {
+  return t;     // OK, t is an xvalue and h2's return type is Thing
+}
+
+decltype(auto) h3(Thing t) {
+  // OK, (t) is an xvalue and h3's return type is Thing&&
+  return (t); // { dg-warning "reference to local variable" }
+}
diff --git a/gcc/testsuite/g++.dg/cpp23/elision6.C b/gcc/testsuite/g++.dg/cpp23/elision6.C
new file mode 100644
index 00000000000..5d58da9e577
--- /dev/null
+++ b/gcc/testsuite/g++.dg/cpp23/elision6.C
@@ -0,0 +1,20 @@
+// PR c++/101165 - P2266R1 - Simpler implicit move
+// { dg-do compile { target c++23 } }
+// From [diff.cpp20.expr].
+
+template<typename T, typename U>
+struct same_type { static const bool value = false; };
+
+template<typename T>
+struct same_type<T, T> { static const bool value = true; };
+
+// In C++23, returns int&&; previously returned int&.
+decltype(auto) f(int&& x) { return (x); }
+static_assert(same_type<decltype(f), int&& (int&&)>::value);
+
+// This used to work in C++20.
+int& g(int&& x) { return x; } // { dg-error "cannot bind non-const lvalue reference" }
+
+template<typename T>
+decltype(auto) h(T&& x) { return (x); }
+static_assert(same_type<decltype(h(42)), int&&>::value);
diff --git a/gcc/testsuite/g++.dg/cpp23/elision7.C b/gcc/testsuite/g++.dg/cpp23/elision7.C
new file mode 100644
index 00000000000..19fa89ae133
--- /dev/null
+++ b/gcc/testsuite/g++.dg/cpp23/elision7.C
@@ -0,0 +1,72 @@
+// PR c++/101165 - P2266R1 - Simpler implicit move
+// { dg-do compile { target c++23 } }
+
+struct X {
+  X ();
+  X(X&&);
+};
+
+X&& rref ();
+
+X&&
+f1 (X&& x)
+{
+  return x;
+}
+
+template<typename T> T&&
+f2 (T&& x)
+{
+  return x;
+}
+template X& f2<X&>(X&);
+template X&& f2<X>(X&&);
+
+X&&
+f3 ()
+{
+  X&& x = rref ();
+  return x;
+}
+
+void
+f4 ()
+try {
+  X x;
+  throw x;
+} catch (...) { }
+
+void
+f5 ()
+{
+  auto l1 = [](auto x) -> auto { return x; };
+  auto &&x1 = l1(X{});
+  auto l2 = [](auto x) -> auto& { return x; }; // { dg-error "cannot bind non-const lvalue reference" }
+  auto &&x2 = l2(X{});
+  auto l3 = [](auto x) -> auto&& { return x; }; // { dg-warning "reference to local" }
+  auto &&x3 = l3(X{});
+}
+
+constexpr int &
+f6 (int &&n)
+{
+  return n; // { dg-error "cannot bind non-const lvalue reference" }
+}
+
+void
+do_f6 ()
+{
+  auto x = f6 (42);
+}
+
+template<typename T> auto &
+f7 (T &&t)
+{
+  return t; // { dg-error "cannot bind non-const lvalue reference" }
+}
+
+void
+do_f7 ()
+{
+  const int &x = f7 (0);
+}
diff --git a/gcc/testsuite/g++.dg/cpp23/feat-cxx2b.C b/gcc/testsuite/g++.dg/cpp23/feat-cxx2b.C
index d3e40724085..20453bb7b14 100644
--- a/gcc/testsuite/g++.dg/cpp23/feat-cxx2b.C
+++ b/gcc/testsuite/g++.dg/cpp23/feat-cxx2b.C
@@ -563,3 +563,9 @@
 #elif __cpp_named_character_escapes != 202207
 #  error "__cpp_named_character_escapes != 202207"
 #endif
+
+#ifndef __cpp_implicit_move
+#  error "__cpp_implicit_move"
+#elif __cpp_implicit_move != 202207
+#  error "__cpp_implicit_move != 202207"
+#endif
diff --git a/gcc/testsuite/g++.dg/gomp/pr56217.C b/gcc/testsuite/g++.dg/gomp/pr56217.C
index 03dfc5f180b..731c0c08811 100644
--- a/gcc/testsuite/g++.dg/gomp/pr56217.C
+++ b/gcc/testsuite/g++.dg/gomp/pr56217.C
@@ -1,5 +1,5 @@
 // PR middle-end/56217
-// { dg-do compile }
+// { dg-do compile { target c++20_down } }
 // { dg-options "-fopenmp" }
 
 struct S { int *p; S (); S (S &); };
@@ -10,5 +10,7 @@ foo ()
   S s;
   #pragma omp task shared (s)
     s.p = 0;
+  // This fails in C++23, because "cannot bind non-const lvalue reference of
+  // type 'S&' to an rvalue of type 'S'".
   return s;
 }
diff --git a/gcc/testsuite/g++.dg/warn/Wno-return-local-addr.C b/gcc/testsuite/g++.dg/warn/Wno-return-local-addr.C
index e15bfa24f54..cc9bb59770e 100644
--- a/gcc/testsuite/g++.dg/warn/Wno-return-local-addr.C
+++ b/gcc/testsuite/g++.dg/warn/Wno-return-local-addr.C
@@ -4,7 +4,7 @@
 int& bad1()
 {
   int x = 0;
-  return x;
+  return x; // { dg-error "cannot bind non-const lvalue reference" "" { target c++23 } }
 }
 
 int* bad2()
diff --git a/gcc/testsuite/g++.dg/warn/Wreturn-local-addr.C b/gcc/testsuite/g++.dg/warn/Wreturn-local-addr.C
index 642a5767e84..4c18c2f06a0 100644
--- a/gcc/testsuite/g++.dg/warn/Wreturn-local-addr.C
+++ b/gcc/testsuite/g++.dg/warn/Wreturn-local-addr.C
@@ -5,7 +5,7 @@
 int& bad1()
 {
   int x = 0;
-  return x;		// { dg-error "reference to local variable" }
+  return x;		// { dg-error "reference to local variable|cannot bind non-const lvalue reference" }
 }
 
 int* bad2()
diff --git a/gcc/testsuite/g++.old-deja/g++.brendan/crash55.C b/gcc/testsuite/g++.old-deja/g++.brendan/crash55.C
index fd4d4b65edb..b93e6e0c695 100644
--- a/gcc/testsuite/g++.old-deja/g++.brendan/crash55.C
+++ b/gcc/testsuite/g++.old-deja/g++.brendan/crash55.C
@@ -8,5 +8,6 @@
 
           local = x+2;
       
-          return local; // { dg-warning "reference to local" }
+          return local; // { dg-warning "reference to local" "" { target c++20_down } }
+// { dg-error "non-const lvalue" "" { target c++23 } .-1 }
       }
diff --git a/gcc/testsuite/g++.old-deja/g++.jason/temporary2.C b/gcc/testsuite/g++.old-deja/g++.jason/temporary2.C
index c855f8f4a07..2709b50e7f1 100644
--- a/gcc/testsuite/g++.old-deja/g++.jason/temporary2.C
+++ b/gcc/testsuite/g++.old-deja/g++.jason/temporary2.C
@@ -8,7 +8,7 @@ public:
   int i;
 };
 
-X foo() { X x; return x; }
+X foo() { X x; return x; } // { dg-error "cannot bind non-const lvalue reference" "" { target c++23 } }
 
 int main() 
 {
diff --git a/gcc/testsuite/g++.old-deja/g++.mike/p2846b.C b/gcc/testsuite/g++.old-deja/g++.mike/p2846b.C
index 57422fe64df..68826649cfc 100644
--- a/gcc/testsuite/g++.old-deja/g++.mike/p2846b.C
+++ b/gcc/testsuite/g++.old-deja/g++.mike/p2846b.C
@@ -1,4 +1,4 @@
-// { dg-do run  }
+// { dg-do run { target c++20_down } }
 // Shows that problem of initializing one object's secondary base from
 // another object via a user defined copy constructor for that base,
 // the pointer for the secondary vtable is not set after implicit
@@ -11,6 +11,8 @@
 
 // prms-id: 2846
 
+// This test fails in C++23 due to P2266.
+
 extern "C" int printf(const char *, ...);
 extern "C" void exit(int);
 

base-commit: 10d6109fe183d984a0377a7afe2854a0d794ebeb
-- 
2.37.3


^ permalink raw reply	[flat|nested] 10+ messages in thread

* Re: [PATCH] c++: Implement C++23 P2266R1, Simpler implicit move [PR101165]
  2022-09-12 20:27     ` Jason Merrill
@ 2022-09-20 18:21       ` Marek Polacek
  0 siblings, 0 replies; 10+ messages in thread
From: Marek Polacek @ 2022-09-20 18:21 UTC (permalink / raw)
  To: Jason Merrill; +Cc: GCC Patches

On Mon, Sep 12, 2022 at 04:27:27PM -0400, Jason Merrill wrote:
> On 9/8/22 18:54, Marek Polacek wrote:
> > On Tue, Sep 06, 2022 at 10:38:12PM -0400, Jason Merrill wrote:
> > > On 9/3/22 12:42, Marek Polacek wrote:
> > > > This patch implements https://wg21.link/p2266, which, once again,
> > > > changes the implicit move rules.  Here's a brief summary of various
> > > > changes in this area:
> > > > 
> > > > r125211: Introduced moving from certain lvalues when returning them
> > > > r171071: CWG 1148, enable move from value parameter on return
> > > > r212099: CWG 1579, it's OK to call a converting ctor taking an rvalue
> > > > r251035: CWG 1579, do maybe-rvalue overload resolution twice
> > > > r11-2411: Avoid calling const copy ctor on implicit move
> > > > r11-2412: C++20 implicit move changes, remove the fallback overload
> > > >             resolution, allow move on throw of parameters and implicit
> > > > 	  move of rvalue references
> > > > 
> > > > P2266 enables the implicit move for functions that return references.  This
> > > > was a one-line change: check TYPE_REF_P.  That is, we will now perform
> > > > a move in
> > > > 
> > > >     X&& foo (X&& x) {
> > > >       return x;
> > > >     }
> > > > 
> > > > P2266 also removes the fallback overload resolution, but this was
> > > > resolved by r11-2412: we only do convert_for_initialization with
> > > > LOOKUP_PREFER_RVALUE in C++17 and older.
> > > 
> > > I wonder if we want to extend the current C++20 handling to the older modes
> > > for GCC 13?  Not in this patch, but as a followup.
> > > 
> > > > P2266 also says that a returned move-eligible id-expression is always an
> > > > xvalue.  This required some further short, but nontrivial changes,
> > > > especially when it comes to deduction, because we have to pay attention
> > > > to whether we have auto, auto&& (which is like T&&), or decltype(auto)
> > > > with (un)parenthesized argument.  In C++23,
> > > > 
> > > >     decltype(auto) f(int&& x) { return (x); }
> > > >     auto&& f(int x) { return x; }
> > > > 
> > > > both should deduce to 'int&&' but
> > > > 
> > > >     decltype(auto) f(int x) { return x; }
> > > > 
> > > > should deduce to 'int'.  A cornucopia of tests attached.  I've also
> > > > verified that we behave like clang++.
> > > > 
> > > > xvalue_p seemed to be broken: since the introduction of clk_implicit_rval,
> > > > it cannot use '==' when checking for clk_rvalueref.
> > > > 
> > > > Since this change breaks code, it's only enabled in C++23.  In
> > > > particular, this code will not compile in C++23:
> > > > 
> > > >     int& g(int&& x) { return x; }
> > > 
> > > Nice that the C++20 compatibility is so simple!
> > > 
> > > > because x is now treated as an rvalue, and you can't bind a non-const lvalue
> > > > reference to an rvalue.
> > > > 
> > > > There's one FIXME in elision1.C:five, which we should compile but reject
> > > > with "passing 'Mutt' as 'this' argument discards qualifiers".  That
> > > > looks bogus to me, I think I'll open a PR for it.
> > > 
> > > Let's fix that now, I think.
> > 
> > Can of worms.   The test is
> > 
> >    struct Mutt {
> >        operator int*() &&;
> >    };
> > 
> >    int* five(Mutt x) {
> >        return x;  // OK since C++20 because P1155
> >    }
> > 
> > 'x' should be treated as an rvalue, therefore the operator fn taking
> > an rvalue ref to Mutt should be used to convert 'x' to int*.  We fail
> > because we don't treat 'x' as an rvalue because the function doesn't
> > return a class.  So the patch should be just
> > 
> > --- a/gcc/cp/typeck.cc
> > +++ b/gcc/cp/typeck.cc
> > @@ -10875,10 +10875,7 @@ check_return_expr (tree retval, bool *no_warning)
> >            Note that these conditions are similar to, but not as strict as,
> >       the conditions for the named return value optimization.  */
> >         bool converted = false;
> > -      tree moved;
> > -      /* This is only interesting for class type.  */
> > -      if (CLASS_TYPE_P (functype)
> > -     && (moved = treat_lvalue_as_rvalue_p (retval, /*return*/true)))
> > +      if (tree moved = treat_lvalue_as_rvalue_p (retval, /*return*/true))
> >      {
> >        if (cxx_dialect < cxx20)
> >          {
> > 
> > which fixes the test, but breaks a lot of middle-end warnings.  For instance
> > g++.dg/warn/nonnull3.C, where the patch above changes .gimple:
> > 
> >   bool A::foo<B> (struct A * const this, <<< Unknown tree: offset_type >>> p)
> >   {
> > -  bool D.2146;
> > +  bool D.2150;
> > -  D.2146 = p != -1;
> > -  return D.2146;
> > +  p.0_1 = p;
> > +  D.2150 = p.0_1 != -1;
> > +  return D.2150;
> >   }
> > 
> > and we no longer get the warning.  I thought maybe I could undo the implicit
> > rvalue conversion in cp_fold, when it sees implicit_rvalue_p, but that didn't
> > work.  So currently I'm stuck.  Should we try to figure this out or push aside?
> 
> Can you undo the implicit rvalue conversion within check_return_expr, where
> we can still refer back to the original expression?
> 
> Or avoid the rvalue conversion if the return type is scalar?
> 
> Did you see my comments in the body of the patch?

I responded in the email I just sent so that we can have a single thread.

Marek


^ permalink raw reply	[flat|nested] 10+ messages in thread

* Re: [PATCH v2] c++: Implement C++23 P2266R1, Simpler implicit move [PR101165]
  2022-09-20 18:19   ` [PATCH v2] " Marek Polacek
@ 2022-09-26 17:29     ` Jason Merrill
  2022-09-27 20:26       ` [PATCH v3] " Marek Polacek
  0 siblings, 1 reply; 10+ messages in thread
From: Jason Merrill @ 2022-09-26 17:29 UTC (permalink / raw)
  To: Marek Polacek; +Cc: GCC Patches

On 9/20/22 14:19, Marek Polacek wrote:
> On Tue, Sep 06, 2022 at 10:38:12PM -0400, Jason Merrill wrote:
>> On 9/3/22 12:42, Marek Polacek wrote:
>>> This patch implements https://wg21.link/p2266, which, once again,
>>> changes the implicit move rules.  Here's a brief summary of various
>>> changes in this area:
>>>
>>> r125211: Introduced moving from certain lvalues when returning them
>>> r171071: CWG 1148, enable move from value parameter on return
>>> r212099: CWG 1579, it's OK to call a converting ctor taking an rvalue
>>> r251035: CWG 1579, do maybe-rvalue overload resolution twice
>>> r11-2411: Avoid calling const copy ctor on implicit move
>>> r11-2412: C++20 implicit move changes, remove the fallback overload
>>>             resolution, allow move on throw of parameters and implicit
>>> 	  move of rvalue references
>>>
>>> P2266 enables the implicit move for functions that return references.  This
>>> was a one-line change: check TYPE_REF_P.  That is, we will now perform
>>> a move in
>>>
>>>     X&& foo (X&& x) {
>>>       return x;
>>>     }
>>>
>>> P2266 also removes the fallback overload resolution, but this was
>>> resolved by r11-2412: we only do convert_for_initialization with
>>> LOOKUP_PREFER_RVALUE in C++17 and older.
>>
>> I wonder if we want to extend the current C++20 handling to the older modes
>> for GCC 13?  Not in this patch, but as a followup.
> 
> Yes, I think that would be very nice if we removed that code.
>   
>>> P2266 also says that a returned move-eligible id-expression is always an
>>> xvalue.  This required some further short, but nontrivial changes,
>>> especially when it comes to deduction, because we have to pay attention
>>> to whether we have auto, auto&& (which is like T&&), or decltype(auto)
>>> with (un)parenthesized argument.  In C++23,
>>>
>>>     decltype(auto) f(int&& x) { return (x); }
>>>     auto&& f(int x) { return x; }
>>>
>>> both should deduce to 'int&&' but
>>>
>>>     decltype(auto) f(int x) { return x; }
>>>
>>> should deduce to 'int'.  A cornucopia of tests attached.  I've also
>>> verified that we behave like clang++.
>>>
>>> xvalue_p seemed to be broken: since the introduction of clk_implicit_rval,
>>> it cannot use '==' when checking for clk_rvalueref.
>>>
>>> Since this change breaks code, it's only enabled in C++23.  In
>>> particular, this code will not compile in C++23:
>>>
>>>     int& g(int&& x) { return x; }
>>
>> Nice that the C++20 compatibility is so simple!
>>
>>> because x is now treated as an rvalue, and you can't bind a non-const lvalue
>>> reference to an rvalue.
>>>
>>> There's one FIXME in elision1.C:five, which we should compile but reject
>>> with "passing 'Mutt' as 'this' argument discards qualifiers".  That
>>> looks bogus to me, I think I'll open a PR for it.
>>
>> Let's fix that now, I think.
> 
> OK, copypasting this bit from the other email so that we can have one
> thread:
> 
>> Can of worms.   The test is
>>
>>     struct Mutt {
>>         operator int*() &&;
>>     };
>>
>>     int* five(Mutt x) {
>>         return x;  // OK since C++20 because P1155
>>     }
>>
>> 'x' should be treated as an rvalue, therefore the operator fn taking
>> an rvalue ref to Mutt should be used to convert 'x' to int*.  We fail
>> because we don't treat 'x' as an rvalue because the function doesn't
>> return a class.  So the patch should be just
>>
>> --- a/gcc/cp/typeck.cc
>> +++ b/gcc/cp/typeck.cc
>> @@ -10875,10 +10875,7 @@ check_return_expr (tree retval, bool *no_warning)
>>             Note that these conditions are similar to, but not as strict as,
>>        the conditions for the named return value optimization.  */
>>          bool converted = false;
>> -      tree moved;
>> -      /* This is only interesting for class type.  */
>> -      if (CLASS_TYPE_P (functype)
>> -     && (moved = treat_lvalue_as_rvalue_p (retval, /*return*/true)))
>> +      if (tree moved = treat_lvalue_as_rvalue_p (retval, /*return*/true))
>>       {
>>         if (cxx_dialect < cxx20)
>>           {
>>
>> which fixes the test, but breaks a lot of middle-end warnings.  For instance
>> g++.dg/warn/nonnull3.C, where the patch above changes .gimple:
>>
>>    bool A::foo<B> (struct A * const this, <<< Unknown tree: offset_type >>> p)
>>    {
>> -  bool D.2146;
>> +  bool D.2150;
>>    
>> -  D.2146 = p != -1;
>> -  return D.2146;
>> +  p.0_1 = p;
>> +  D.2150 = p.0_1 != -1;
>> +  return D.2150;
>>    }
>>
>> and we no longer get the warning.  I thought maybe I could undo the implicit
>> rvalue conversion in cp_fold, when it sees implicit_rvalue_p, but that didn't
>> work.  So currently I'm stuck.  Should we try to figure this out or push aside?
> 
>> Can you undo the implicit rvalue conversion within check_return_expr,
>> where we can still refer back to the original expression?
> 
> Unfortunately no, one problem is that treat_lvalue_as_rvalue_p modifies
> the underlying decl by setting TREE_ADDRESSABLE, which then presumably
> breaks warnings.  That is, treat_ can get 'VCE<X>(x)' and produce
> '*NLE<(X&) &x>' where 'x' flags have been modified, since we're taking
> x's address.
> 
>> Or avoid the rvalue conversion if the return type is scalar?
> 
> I wish :(.  In the 'five' example above, the return type is a pointer,
> a scalar, but we have to convert to rvalue.

OK, then when both the return type and the type of the return value are 
scalar?

> It's sort of sad that this corner case causes so much trouble: I think
> we have to do the conversion only because of ref-qualifiers, so that
> the correct operator function is chosen.
> 
> A way out may be setting a flag on the V_C_E that indicates it is an
> rvalue, rather than performing the conversion above.  This was your
> idea so I don't want to take credit for it.  Should I go ahead and
> try it?

Sure, probably in build_static_cast_1.

>>> Bootstrapped/regtested on x86_64-pc-linux-gnu, ok for trunk?
>>>
>>> 	PR c++/101165
>>>
>>> gcc/c-family/ChangeLog:
>>>
>>> 	* c-cppbuiltin.cc (c_cpp_builtins): Define __cpp_implicit_move.
>>>
>>> gcc/cp/ChangeLog:
>>>
>>> 	* call.cc (reference_binding): Check clk_implicit_rval in C++20 only.
>>> 	* cp-tree.h (unparenthesized_id_or_class_member_access_p): Declare.
>>> 	* pt.cc (unparenthesized_id_or_class_member_access_p): New function,
>>> 	broken out of...
>>> 	(do_auto_deduction): ...here.  Use it.
>>> 	* tree.cc (xvalue_p): Check & clk_rvalueref, not == clk_rvalueref.
>>> 	* typeck.cc (check_return_expr): In C++23, maybe call
>>> 	treat_lvalue_as_rvalue_p before do_auto_deduction.  Allow implicit
>>> 	move for functions returning a reference as well.
>>>
>>> gcc/testsuite/ChangeLog:
>>>
>>> 	* g++.dg/conversion/pr41426.C: Add dg-error for C++23.
>>> 	* g++.dg/cpp0x/elision_weak.C: Likewise.
>>> 	* g++.dg/cpp0x/move-return3.C: Only link in c++20_down.
>>> 	* g++.dg/cpp1y/decltype-auto2.C: Add dg-error for C++23.
>>> 	* g++.dg/cpp1y/lambda-generic-89419.C: Likewise.
>>> 	* g++.dg/cpp23/feat-cxx2b.C: Test __cpp_implicit_move.
>>> 	* g++.dg/gomp/pr56217.C: Only compile in c++20_down.
>>> 	* g++.dg/warn/Wno-return-local-addr.C: Add dg-error for C++23.
>>> 	* g++.dg/warn/Wreturn-local-addr.C: Adjust dg-error.
>>> 	* g++.old-deja/g++.brendan/crash55.C: Add dg-error for C++23.
>>> 	* g++.old-deja/g++.jason/temporary2.C: Likewise.
>>> 	* g++.old-deja/g++.mike/p2846b.C: Only run in c++20_down.
>>> 	* g++.dg/cpp1y/decltype-auto6.C: New test.
>>> 	* g++.dg/cpp23/decltype1.C: New test.
>>> 	* g++.dg/cpp23/decltype2.C: New test.
>>> 	* g++.dg/cpp23/elision1.C: New test.
>>> 	* g++.dg/cpp23/elision2.C: New test.
>>> 	* g++.dg/cpp23/elision3.C: New test.
>>> 	* g++.dg/cpp23/elision4.C: New test.
>>> 	* g++.dg/cpp23/elision5.C: New test.
>>> 	* g++.dg/cpp23/elision6.C: New test.
>>> 	* g++.dg/cpp23/elision7.C: New test.
>>> ---
>>>    gcc/c-family/c-cppbuiltin.cc                  |   1 +
>>>    gcc/cp/call.cc                                |   2 +-
>>>    gcc/cp/cp-tree.h                              |   1 +
>>>    gcc/cp/pt.cc                                  |  33 +++--
>>>    gcc/cp/tree.cc                                |   2 +-
>>>    gcc/cp/typeck.cc                              |  28 ++++-
>>>    gcc/testsuite/g++.dg/conversion/pr41426.C     |  10 +-
>>>    gcc/testsuite/g++.dg/cpp0x/elision_weak.C     |   4 +-
>>>    gcc/testsuite/g++.dg/cpp0x/move-return3.C     |   3 +-
>>>    gcc/testsuite/g++.dg/cpp1y/decltype-auto2.C   |   2 +-
>>>    gcc/testsuite/g++.dg/cpp1y/decltype-auto6.C   |  19 +++
>>>    .../g++.dg/cpp1y/lambda-generic-89419.C       |   6 +-
>>>    gcc/testsuite/g++.dg/cpp23/decltype1.C        | 113 ++++++++++++++++++
>>>    gcc/testsuite/g++.dg/cpp23/decltype2.C        |  49 ++++++++
>>>    gcc/testsuite/g++.dg/cpp23/elision1.C         | 109 +++++++++++++++++
>>>    gcc/testsuite/g++.dg/cpp23/elision2.C         |  46 +++++++
>>>    gcc/testsuite/g++.dg/cpp23/elision3.C         |  16 +++
>>>    gcc/testsuite/g++.dg/cpp23/elision4.C         |  38 ++++++
>>>    gcc/testsuite/g++.dg/cpp23/elision5.C         |  53 ++++++++
>>>    gcc/testsuite/g++.dg/cpp23/elision6.C         |  20 ++++
>>>    gcc/testsuite/g++.dg/cpp23/elision7.C         |  72 +++++++++++
>>>    gcc/testsuite/g++.dg/cpp23/feat-cxx2b.C       |   6 +
>>>    gcc/testsuite/g++.dg/gomp/pr56217.C           |   4 +-
>>>    .../g++.dg/warn/Wno-return-local-addr.C       |   2 +-
>>>    .../g++.dg/warn/Wreturn-local-addr.C          |   2 +-
>>>    .../g++.old-deja/g++.brendan/crash55.C        |   3 +-
>>>    .../g++.old-deja/g++.jason/temporary2.C       |   2 +-
>>>    gcc/testsuite/g++.old-deja/g++.mike/p2846b.C  |   4 +-
>>>    28 files changed, 613 insertions(+), 37 deletions(-)
>>>    create mode 100644 gcc/testsuite/g++.dg/cpp1y/decltype-auto6.C
>>>    create mode 100644 gcc/testsuite/g++.dg/cpp23/decltype1.C
>>>    create mode 100644 gcc/testsuite/g++.dg/cpp23/decltype2.C
>>>    create mode 100644 gcc/testsuite/g++.dg/cpp23/elision1.C
>>>    create mode 100644 gcc/testsuite/g++.dg/cpp23/elision2.C
>>>    create mode 100644 gcc/testsuite/g++.dg/cpp23/elision3.C
>>>    create mode 100644 gcc/testsuite/g++.dg/cpp23/elision4.C
>>>    create mode 100644 gcc/testsuite/g++.dg/cpp23/elision5.C
>>>    create mode 100644 gcc/testsuite/g++.dg/cpp23/elision6.C
>>>    create mode 100644 gcc/testsuite/g++.dg/cpp23/elision7.C
>>>
>>> diff --git a/gcc/c-family/c-cppbuiltin.cc b/gcc/c-family/c-cppbuiltin.cc
>>> index a1557eb23d5..dd8b20d0c34 100644
>>> --- a/gcc/c-family/c-cppbuiltin.cc
>>> +++ b/gcc/c-family/c-cppbuiltin.cc
>>> @@ -1081,6 +1081,7 @@ c_cpp_builtins (cpp_reader *pfile)
>>>    	  cpp_define (pfile, "__cpp_constexpr=202110L");
>>>    	  cpp_define (pfile, "__cpp_multidimensional_subscript=202110L");
>>>    	  cpp_define (pfile, "__cpp_named_character_escapes=202207L");
>>> +	  cpp_define (pfile, "__cpp_implicit_move=202207L");
>>>    	}
>>>          if (flag_concepts)
>>>            {
>>> diff --git a/gcc/cp/call.cc b/gcc/cp/call.cc
>>> index d107a2814dc..cb4ae9ecf9d 100644
>>> --- a/gcc/cp/call.cc
>>> +++ b/gcc/cp/call.cc
>>> @@ -1865,7 +1865,7 @@ reference_binding (tree rto, tree rfrom, tree expr, bool c_cast_p, int flags,
>>>          /* Nor the reverse.  */
>>>          if (!is_lvalue && !TYPE_REF_IS_RVALUE (rto)
>>>    	  /* Unless it's really an lvalue.  */
>>
>> Please add a bit about C++20 vs C++23 in this comment.
> 
> Done.
>   
>>> -	  && !(cxx_dialect >= cxx20
>>> +	  && !(cxx_dialect == cxx20
>>>    	       && (gl_kind & clk_implicit_rval))
>>>    	  && (!CP_TYPE_CONST_NON_VOLATILE_P (to)
>>>    	      || (flags & LOOKUP_NO_RVAL_BIND))
>>> diff --git a/gcc/cp/cp-tree.h b/gcc/cp/cp-tree.h
>>> index 7b28405c3ac..4cdbbc34cb3 100644
>>> --- a/gcc/cp/cp-tree.h
>>> +++ b/gcc/cp/cp-tree.h
>>> @@ -7278,6 +7278,7 @@ extern tree make_constrained_decltype_auto	(tree, tree);
>>>    extern tree make_template_placeholder		(tree);
>>>    extern bool template_placeholder_p		(tree);
>>>    extern bool ctad_template_p			(tree);
>>> +extern bool unparenthesized_id_or_class_member_access_p (tree);
>>>    extern tree do_auto_deduction                   (tree, tree, tree,
>>>                                                     tsubst_flags_t
>>>    						 = tf_warning_or_error,
>>> diff --git a/gcc/cp/pt.cc b/gcc/cp/pt.cc
>>> index cd0d8920ed0..b347a9a78fc 100644
>>> --- a/gcc/cp/pt.cc
>>> +++ b/gcc/cp/pt.cc
>>> @@ -30405,6 +30405,26 @@ do_class_deduction (tree ptype, tree tmpl, tree init,
>>>    				  cp_type_quals (ptype));
>>>    }
>>> +/* Return true if INIT is an unparenthesized id-expression or an
>>> +   unparenthesized class member access.  Used for the argument of
>>> +   decltype(auto).  */
>>> +
>>> +bool
>>> +unparenthesized_id_or_class_member_access_p (tree init)
>>> +{
>>> +  STRIP_ANY_LOCATION_WRAPPER (init);
>>> +
>>
>> I wonder about looking through IMPLICIT_RVALUE_P here...
> 
> Unwrapping an IMPLICIT_RVALUE_P is really ugly, and teaching
> unparenthesized_ about IMPLICIT_RVALUE_P wouldn't be enough,
> since we'd have to unwrap INIT as well so that finish_decltype_type
> gets the decl, not '*x'.  However, I have...
>   
>>> +  /* We need to be able to tell '(r)' and 'r' apart (when it's of
>>> +     reference type).  Only the latter is an id-expression.  */
>>> +  if (REFERENCE_REF_P (init)
>>> +      && !REF_PARENTHESIZED_P (init))
>>> +    init = TREE_OPERAND (init, 0);
>>> +  return (DECL_P (init)
>>> +	  || ((TREE_CODE (init) == COMPONENT_REF
>>> +	       || TREE_CODE (init) == SCOPE_REF)
>>> +	      && !REF_PARENTHESIZED_P (init)));
>>> +}
>>> +
>>>    /* Replace occurrences of 'auto' in TYPE with the appropriate type deduced
>>>       from INIT.  AUTO_NODE is the TEMPLATE_TYPE_PARM used for 'auto' in TYPE.
>>>       The CONTEXT determines the context in which auto deduction is performed
>>> @@ -30501,18 +30521,7 @@ do_auto_deduction (tree type, tree init, tree auto_node,
>>>        }
>>>      else if (AUTO_IS_DECLTYPE (auto_node))
>>>        {
>>> -      /* Figure out if INIT is an unparenthesized id-expression or an
>>> -	 unparenthesized class member access.  */
>>> -      tree stripped_init = tree_strip_any_location_wrapper (init);
>>> -      /* We need to be able to tell '(r)' and 'r' apart (when it's of
>>> -	 reference type).  Only the latter is an id-expression.  */
>>> -      if (REFERENCE_REF_P (stripped_init)
>>> -	  && !REF_PARENTHESIZED_P (stripped_init))
>>> -	stripped_init = TREE_OPERAND (stripped_init, 0);
>>> -      const bool id = (DECL_P (stripped_init)
>>> -		       || ((TREE_CODE (stripped_init) == COMPONENT_REF
>>> -			    || TREE_CODE (stripped_init) == SCOPE_REF)
>>> -			   && !REF_PARENTHESIZED_P (stripped_init)));
>>> +      const bool id = unparenthesized_id_or_class_member_access_p (init);
>>>          tree deduced = finish_decltype_type (init, id, complain);
>>>          deduced = canonicalize_type_argument (deduced, complain);
>>>          if (deduced == error_mark_node)
>>> diff --git a/gcc/cp/tree.cc b/gcc/cp/tree.cc
>>> index c678e3b9c4c..226ffd48519 100644
>>> --- a/gcc/cp/tree.cc
>>> +++ b/gcc/cp/tree.cc
>>> @@ -382,7 +382,7 @@ obvalue_p (const_tree ref)
>>>    bool
>>>    xvalue_p (const_tree ref)
>>>    {
>>> -  return (lvalue_kind (ref) == clk_rvalueref);
>>> +  return (lvalue_kind (ref) & clk_rvalueref);
>>>    }
>>>    /* True if REF is a bit-field.  */
>>> diff --git a/gcc/cp/typeck.cc b/gcc/cp/typeck.cc
>>> index b99947c10fd..fe88c1a7b84 100644
>>> --- a/gcc/cp/typeck.cc
>>> +++ b/gcc/cp/typeck.cc
>>> @@ -10876,8 +10893,9 @@ check_return_expr (tree retval, bool *no_warning)
>>>    	 the conditions for the named return value optimization.  */
>>>          bool converted = false;
>>>          tree moved;
>>> -      /* This is only interesting for class type.  */
>>> -      if (CLASS_TYPE_P (functype)
>>> +      /* Until C++23, this was only interesting for class type.  */
>>> +      if ((CLASS_TYPE_P (functype)
>>> +	   || (cxx_dialect >= cxx23 && TYPE_REF_P (functype)))
>>>    	  && (moved = treat_lvalue_as_rvalue_p (retval, /*return*/true)))
>>
>> ...and calling treat_lvalue_as_rvalue_p before auto deduction...
>>
>>>    	{
>>>    	  if (cxx_dialect < cxx20)
>>> @@ -10679,10 +10679,27 @@ check_return_expr (tree retval, bool *no_warning)
>>>    	}
>>>          else
>>>    	{
>>> -	  if (!retval)
>>> -	    retval = void_node;
>>>    	  auto_node = type_uses_auto (pattern);
>>> -	  type = do_auto_deduction (pattern, retval, auto_node,
>>> +	  tree r;
>>> +	  if (!retval)
>>> +	    r = void_node;
>>> +	  /* In C++23, we must deduce the type to int&& for code like
>>> +	       decltype(auto) f(int&& x) { return (x); }
>>> +	     or
>>> +	       auto&& f(int x) { return x; }
>>> +	     so we use treat_lvalue_as_rvalue_p earlier.  But don't do it for
>>> +	       decltype(auto) f(int x) { return x; }
>>> +	     where we should deduce 'int' rather than 'int&&'; transmogrifying
>>> +	     RETVAL to an rvalue would break that.  */
>>> +	  else if (cxx_dialect >= cxx23
>>> +		   && (!AUTO_IS_DECLTYPE (auto_node)
>>> +		       || !unparenthesized_id_or_class_member_access_p (retval))
>>> +		   && (r = treat_lvalue_as_rvalue_p
>>> +		       (maybe_undo_parenthesized_ref (retval), /*return*/true)))
>>> +	    /* Use it.  */;
>>
>> ...so we don't need to do this?
> 
> ...moved this into do_auto_deduction, where I check context == adc_return_type.
> 
> FWIW this is what I have at this moment.
>   
> -- >8 --
> This patch implements https://wg21.link/p2266, which, once again,
> changes the implicit move rules.  Here's a brief summary of various
> changes in this area:
> 
> r125211: Introduced moving from certain lvalues when returning them
> r171071: CWG 1148, enable move from value parameter on return
> r212099: CWG 1579, it's OK to call a converting ctor taking an rvalue
> r251035: CWG 1579, do maybe-rvalue overload resolution twice
> r11-2411: Avoid calling const copy ctor on implicit move
> r11-2412: C++20 implicit move changes, remove the fallback overload
>            resolution, allow move on throw of parameters and implicit
> 	  move of rvalue references
> 
> P2266 enables the implicit move for functions that return references.  This
> was a one-line change: check TYPE_REF_P.  That is, we will now perform
> a move in
> 
>    X&& foo (X&& x) {
>      return x;
>    }
> 
> P2266 also removes the fallback overload resolution, but this was
> resolved by r11-2412: we only do convert_for_initialization with
> LOOKUP_PREFER_RVALUE in C++17 and older.
> P2266 also says that a returned move-eligible id-expression is always an
> xvalue.  This required some further short, but nontrivial changes,
> especially when it comes to deduction, because we have to pay attention
> to whether we have auto, auto&& (which is like T&&), or decltype(auto)
> with (un)parenthesized argument.  In C++23,
> 
>    decltype(auto) f(int&& x) { return (x); }
>    auto&& f(int x) { return x; }
> 
> both should deduce to 'int&&' but
> 
>    decltype(auto) f(int x) { return x; }
> 
> should deduce to 'int'.  A cornucopia of tests attached.  I've also
> verified that we behave like clang++.
> 
> xvalue_p seemed to be broken: since the introduction of clk_implicit_rval,
> it cannot use '==' when checking for clk_rvalueref.
> 
> Since this change breaks code, it's only enabled in C++23.  In
> particular, this code will not compile in C++23:
> 
>    int& g(int&& x) { return x; }
> 
> because x is now treated as an rvalue, and you can't bind a non-const lvalue
> reference to an rvalue.
> 
> There's one FIXME in elision1.C:five, which we should compile but reject
> with "passing 'Mutt' as 'this' argument discards qualifiers".  This is
> PR106882.
> 
> 	PR c++/101165
> 
> gcc/c-family/ChangeLog:
> 
> 	* c-cppbuiltin.cc (c_cpp_builtins): Define __cpp_implicit_move.
> 
> gcc/cp/ChangeLog:
> 
> 	* call.cc (reference_binding): Check clk_implicit_rval in C++20 only.
> 	* cp-tree.h (unparenthesized_id_or_class_member_access_p): Declare.
> 	* pt.cc (unparenthesized_id_or_class_member_access_p): New function,
> 	broken out of...
> 	(do_auto_deduction): ...here.  Use it.  In C++23, maybe call
> 	treat_lvalue_as_rvalue_p.
> 	* tree.cc (xvalue_p): Check & clk_rvalueref, not == clk_rvalueref.
> 	* typeck.cc (check_return_expr): Allow implicit move for functions
> 	returning a reference as well.
> 
> gcc/testsuite/ChangeLog:
> 
> 	* g++.dg/conversion/pr41426.C: Add dg-error for C++23.
> 	* g++.dg/cpp0x/elision_weak.C: Likewise.
> 	* g++.dg/cpp0x/move-return3.C: Only link in c++20_down.
> 	* g++.dg/cpp1y/decltype-auto2.C: Add dg-error for C++23.
> 	* g++.dg/cpp1y/lambda-generic-89419.C: Likewise.
> 	* g++.dg/cpp23/feat-cxx2b.C: Test __cpp_implicit_move.
> 	* g++.dg/gomp/pr56217.C: Only compile in c++20_down.
> 	* g++.dg/warn/Wno-return-local-addr.C: Add dg-error for C++23.
> 	* g++.dg/warn/Wreturn-local-addr.C: Adjust dg-error.
> 	* g++.old-deja/g++.brendan/crash55.C: Add dg-error for C++23.
> 	* g++.old-deja/g++.jason/temporary2.C: Likewise.
> 	* g++.old-deja/g++.mike/p2846b.C: Only run in c++20_down.
> 	* g++.dg/cpp1y/decltype-auto6.C: New test.
> 	* g++.dg/cpp23/decltype1.C: New test.
> 	* g++.dg/cpp23/decltype2.C: New test.
> 	* g++.dg/cpp23/elision1.C: New test.
> 	* g++.dg/cpp23/elision2.C: New test.
> 	* g++.dg/cpp23/elision3.C: New test.
> 	* g++.dg/cpp23/elision4.C: New test.
> 	* g++.dg/cpp23/elision5.C: New test.
> 	* g++.dg/cpp23/elision6.C: New test.
> 	* g++.dg/cpp23/elision7.C: New test.
> ---
>   gcc/c-family/c-cppbuiltin.cc                  |   1 +
>   gcc/cp/call.cc                                |   6 +-
>   gcc/cp/cp-tree.h                              |   1 +
>   gcc/cp/pt.cc                                  |  50 ++++++--
>   gcc/cp/tree.cc                                |   2 +-
>   gcc/cp/typeck.cc                              |   6 +-
>   gcc/testsuite/g++.dg/conversion/pr41426.C     |  10 +-
>   gcc/testsuite/g++.dg/cpp0x/elision_weak.C     |   4 +-
>   gcc/testsuite/g++.dg/cpp0x/move-return3.C     |   3 +-
>   gcc/testsuite/g++.dg/cpp1y/decltype-auto2.C   |   2 +-
>   gcc/testsuite/g++.dg/cpp1y/decltype-auto6.C   |  19 +++
>   .../g++.dg/cpp1y/lambda-generic-89419.C       |   6 +-
>   gcc/testsuite/g++.dg/cpp23/decltype1.C        | 113 ++++++++++++++++++
>   gcc/testsuite/g++.dg/cpp23/decltype2.C        |  49 ++++++++
>   gcc/testsuite/g++.dg/cpp23/elision1.C         | 109 +++++++++++++++++
>   gcc/testsuite/g++.dg/cpp23/elision2.C         |  46 +++++++
>   gcc/testsuite/g++.dg/cpp23/elision3.C         |  16 +++
>   gcc/testsuite/g++.dg/cpp23/elision4.C         |  38 ++++++
>   gcc/testsuite/g++.dg/cpp23/elision5.C         |  53 ++++++++
>   gcc/testsuite/g++.dg/cpp23/elision6.C         |  20 ++++
>   gcc/testsuite/g++.dg/cpp23/elision7.C         |  72 +++++++++++
>   gcc/testsuite/g++.dg/cpp23/feat-cxx2b.C       |   6 +
>   gcc/testsuite/g++.dg/gomp/pr56217.C           |   4 +-
>   .../g++.dg/warn/Wno-return-local-addr.C       |   2 +-
>   .../g++.dg/warn/Wreturn-local-addr.C          |   2 +-
>   .../g++.old-deja/g++.brendan/crash55.C        |   3 +-
>   .../g++.old-deja/g++.jason/temporary2.C       |   2 +-
>   gcc/testsuite/g++.old-deja/g++.mike/p2846b.C  |   4 +-
>   28 files changed, 614 insertions(+), 35 deletions(-)
>   create mode 100644 gcc/testsuite/g++.dg/cpp1y/decltype-auto6.C
>   create mode 100644 gcc/testsuite/g++.dg/cpp23/decltype1.C
>   create mode 100644 gcc/testsuite/g++.dg/cpp23/decltype2.C
>   create mode 100644 gcc/testsuite/g++.dg/cpp23/elision1.C
>   create mode 100644 gcc/testsuite/g++.dg/cpp23/elision2.C
>   create mode 100644 gcc/testsuite/g++.dg/cpp23/elision3.C
>   create mode 100644 gcc/testsuite/g++.dg/cpp23/elision4.C
>   create mode 100644 gcc/testsuite/g++.dg/cpp23/elision5.C
>   create mode 100644 gcc/testsuite/g++.dg/cpp23/elision6.C
>   create mode 100644 gcc/testsuite/g++.dg/cpp23/elision7.C
> 
> diff --git a/gcc/c-family/c-cppbuiltin.cc b/gcc/c-family/c-cppbuiltin.cc
> index a1557eb23d5..dd8b20d0c34 100644
> --- a/gcc/c-family/c-cppbuiltin.cc
> +++ b/gcc/c-family/c-cppbuiltin.cc
> @@ -1081,6 +1081,7 @@ c_cpp_builtins (cpp_reader *pfile)
>   	  cpp_define (pfile, "__cpp_constexpr=202110L");
>   	  cpp_define (pfile, "__cpp_multidimensional_subscript=202110L");
>   	  cpp_define (pfile, "__cpp_named_character_escapes=202207L");
> +	  cpp_define (pfile, "__cpp_implicit_move=202207L");
>   	}
>         if (flag_concepts)
>           {
> diff --git a/gcc/cp/call.cc b/gcc/cp/call.cc
> index 7e9289fc2d0..1a786fc96df 100644
> --- a/gcc/cp/call.cc
> +++ b/gcc/cp/call.cc
> @@ -1864,8 +1864,10 @@ reference_binding (tree rto, tree rfrom, tree expr, bool c_cast_p, int flags,
>   
>         /* Nor the reverse.  */
>         if (!is_lvalue && !TYPE_REF_IS_RVALUE (rto)
> -	  /* Unless it's really an lvalue.  */
> -	  && !(cxx_dialect >= cxx20
> +	  /* Unless it's really a C++20 lvalue being treated as an xvalue.
> +	     But in C++23, such an expression is just an xvalue, not a special
> +	     lvalue, so the binding is once again ill-formed.  */
> +	  && !(cxx_dialect == cxx20
>   	       && (gl_kind & clk_implicit_rval))
>   	  && (!CP_TYPE_CONST_NON_VOLATILE_P (to)
>   	      || (flags & LOOKUP_NO_RVAL_BIND))
> diff --git a/gcc/cp/cp-tree.h b/gcc/cp/cp-tree.h
> index f19ecafc266..35fea6ac3cf 100644
> --- a/gcc/cp/cp-tree.h
> +++ b/gcc/cp/cp-tree.h
> @@ -7278,6 +7278,7 @@ extern tree make_constrained_decltype_auto	(tree, tree);
>   extern tree make_template_placeholder		(tree);
>   extern bool template_placeholder_p		(tree);
>   extern bool ctad_template_p			(tree);
> +extern bool unparenthesized_id_or_class_member_access_p (tree);
>   extern tree do_auto_deduction                   (tree, tree, tree,
>                                                    tsubst_flags_t
>   						 = tf_warning_or_error,
> diff --git a/gcc/cp/pt.cc b/gcc/cp/pt.cc
> index db4e808adec..5833fd54959 100644
> --- a/gcc/cp/pt.cc
> +++ b/gcc/cp/pt.cc
> @@ -30407,6 +30407,26 @@ do_class_deduction (tree ptype, tree tmpl, tree init,
>   				  cp_type_quals (ptype));
>   }
>   
> +/* Return true if INIT is an unparenthesized id-expression or an
> +   unparenthesized class member access.  Used for the argument of
> +   decltype(auto).  */
> +
> +bool
> +unparenthesized_id_or_class_member_access_p (tree init)
> +{
> +  STRIP_ANY_LOCATION_WRAPPER (init);
> +
> +  /* We need to be able to tell '(r)' and 'r' apart (when it's of
> +     reference type).  Only the latter is an id-expression.  */
> +  if (REFERENCE_REF_P (init)
> +      && !REF_PARENTHESIZED_P (init))
> +    init = TREE_OPERAND (init, 0);
> +  return (DECL_P (init)
> +	  || ((TREE_CODE (init) == COMPONENT_REF
> +	       || TREE_CODE (init) == SCOPE_REF)
> +	      && !REF_PARENTHESIZED_P (init)));
> +}
> +
>   /* Replace occurrences of 'auto' in TYPE with the appropriate type deduced
>      from INIT.  AUTO_NODE is the TEMPLATE_TYPE_PARM used for 'auto' in TYPE.
>      The CONTEXT determines the context in which auto deduction is performed
> @@ -30442,6 +30462,23 @@ do_auto_deduction (tree type, tree init, tree auto_node,
>        auto_node.  */
>     complain &= ~tf_partial;
>   
> +  /* In C++23, we must deduce the type to int&& for code like
> +       decltype(auto) f(int&& x) { return (x); }
> +     or
> +       auto&& f(int x) { return x; }
> +     so we use treat_lvalue_as_rvalue_p.  But don't do it for
> +       decltype(auto) f(int x) { return x; }
> +     where we should deduce 'int' rather than 'int&&'; transmogrifying
> +     INIT to an rvalue would break that.  */
> +  tree r;
> +  if (cxx_dialect >= cxx23
> +      && context == adc_return_type
> +      && (!AUTO_IS_DECLTYPE (auto_node)
> +	  || !unparenthesized_id_or_class_member_access_p (init))
> +      && (r = treat_lvalue_as_rvalue_p (maybe_undo_parenthesized_ref (init),
> +					/*return*/true)))
> +    init = r;
> +
>     if (tree tmpl = CLASS_PLACEHOLDER_TEMPLATE (auto_node))
>       /* C++17 class template argument deduction.  */
>       return do_class_deduction (type, tmpl, init, flags, complain);
> @@ -30503,18 +30540,7 @@ do_auto_deduction (tree type, tree init, tree auto_node,
>       }
>     else if (AUTO_IS_DECLTYPE (auto_node))
>       {
> -      /* Figure out if INIT is an unparenthesized id-expression or an
> -	 unparenthesized class member access.  */
> -      tree stripped_init = tree_strip_any_location_wrapper (init);
> -      /* We need to be able to tell '(r)' and 'r' apart (when it's of
> -	 reference type).  Only the latter is an id-expression.  */
> -      if (REFERENCE_REF_P (stripped_init)
> -	  && !REF_PARENTHESIZED_P (stripped_init))
> -	stripped_init = TREE_OPERAND (stripped_init, 0);
> -      const bool id = (DECL_P (stripped_init)
> -		       || ((TREE_CODE (stripped_init) == COMPONENT_REF
> -			    || TREE_CODE (stripped_init) == SCOPE_REF)
> -			   && !REF_PARENTHESIZED_P (stripped_init)));
> +      const bool id = unparenthesized_id_or_class_member_access_p (init);
>         tree deduced = finish_decltype_type (init, id, complain);
>         deduced = canonicalize_type_argument (deduced, complain);
>         if (deduced == error_mark_node)
> diff --git a/gcc/cp/tree.cc b/gcc/cp/tree.cc
> index d0bd41ae5a0..ea4dfc651bb 100644
> --- a/gcc/cp/tree.cc
> +++ b/gcc/cp/tree.cc
> @@ -382,7 +382,7 @@ obvalue_p (const_tree ref)
>   bool
>   xvalue_p (const_tree ref)
>   {
> -  return (lvalue_kind (ref) == clk_rvalueref);
> +  return (lvalue_kind (ref) & clk_rvalueref);
>   }
>   
>   /* True if REF is a bit-field.  */
> diff --git a/gcc/cp/typeck.cc b/gcc/cp/typeck.cc
> index 22d834d3a58..83545ab66ef 100644
> --- a/gcc/cp/typeck.cc
> +++ b/gcc/cp/typeck.cc
> @@ -10872,8 +10872,10 @@ check_return_expr (tree retval, bool *no_warning)
>   	 the conditions for the named return value optimization.  */
>         bool converted = false;
>         tree moved;
> -      /* This is only interesting for class type.  */
> -      if (CLASS_TYPE_P (functype)
> +      /* Until C++23, this was only interesting for class type, but
> +	 (FIXME) in C++23, we should do the below for any type.  */
> +      if ((CLASS_TYPE_P (functype)
> +	   || (cxx_dialect >= cxx23 && TYPE_REF_P (functype)))
>   	  && (moved = treat_lvalue_as_rvalue_p (retval, /*return*/true)))
>   	{
>   	  if (cxx_dialect < cxx20)
> diff --git a/gcc/testsuite/g++.dg/conversion/pr41426.C b/gcc/testsuite/g++.dg/conversion/pr41426.C
> index 5493a91ecfa..b4ecbca5f3a 100644
> --- a/gcc/testsuite/g++.dg/conversion/pr41426.C
> +++ b/gcc/testsuite/g++.dg/conversion/pr41426.C
> @@ -11,19 +11,20 @@ struct A
>   A<float> g1()
>   {
>      float f[] = {1.1f, 2.3f};
> -   return f;
> +   return f; // { dg-error "cannot bind non-const" "" { target c++23 } }
>   }
>   
>   const A<float> &g3()
>   {
>      float f[] = {1.1f, 2.3f};
> -   return f; // { dg-warning "returning reference to temporary" }
> +   return f; // { dg-warning "returning reference to temporary" "" { target c++20_down } }
> +// { dg-error "non-const lvalue|invalid user-defined conversion" "" { target c++23 } .-1 }
>   }
>   
>   A<float> &g4()
>   {
>      float f[] = {1.1f, 2.3f};
> -   return f; // { dg-error "cannot bind non-const lvalue ref" }
> +   return f; // { dg-error "cannot bind non-const lvalue ref|invalid user-defined conversion" }
>   }
>   
>   struct B
> @@ -35,6 +36,5 @@ struct B
>   B g2()
>   {
>      int c[10];
> -   return c;
> +   return c; // { dg-error "non-const lvalue" "" { target c++23 } }
>   }
> -
> diff --git a/gcc/testsuite/g++.dg/cpp0x/elision_weak.C b/gcc/testsuite/g++.dg/cpp0x/elision_weak.C
> index e8ba7551d84..ddd12743130 100644
> --- a/gcc/testsuite/g++.dg/cpp0x/elision_weak.C
> +++ b/gcc/testsuite/g++.dg/cpp0x/elision_weak.C
> @@ -9,11 +9,11 @@ struct S
>   S f()
>   {
>     S s;
> -  return s;
> +  return s; // { dg-error "cannot bind non-const lvalue reference" "" { target c++23 } }
>   }
>   
>   void g()
>   {
>     S s;
> -  throw s;
> +  throw s; // { dg-error "cannot bind non-const lvalue reference" "" { target c++23 } }
>   }
> diff --git a/gcc/testsuite/g++.dg/cpp0x/move-return3.C b/gcc/testsuite/g++.dg/cpp0x/move-return3.C
> index c79f0591936..30a936fb35a 100644
> --- a/gcc/testsuite/g++.dg/cpp0x/move-return3.C
> +++ b/gcc/testsuite/g++.dg/cpp0x/move-return3.C
> @@ -1,6 +1,7 @@
>   // PR c++/91212
>   // Test that C++11 implicit move semantics don't call the const copy.
> -// { dg-do link }
> +// In C++23, we call #2.
> +// { dg-do link { target c++20_down } }
>   
>   struct T { int i; };
>   
> diff --git a/gcc/testsuite/g++.dg/cpp1y/decltype-auto2.C b/gcc/testsuite/g++.dg/cpp1y/decltype-auto2.C
> index 56e011e36f4..24b32edfacf 100644
> --- a/gcc/testsuite/g++.dg/cpp1y/decltype-auto2.C
> +++ b/gcc/testsuite/g++.dg/cpp1y/decltype-auto2.C
> @@ -8,5 +8,5 @@ auto constexpr RtoL1(T&& r) -> decltype(auto) {
>   int main() {
>       int t;
>       int x{3};
> -    decltype (RtoL1(x+0)) y = t;
> +    decltype (RtoL1(x+0)) y = t; // { dg-error "cannot bind rvalue reference" "" { target c++23 } }
>   }
> diff --git a/gcc/testsuite/g++.dg/cpp1y/decltype-auto6.C b/gcc/testsuite/g++.dg/cpp1y/decltype-auto6.C
> new file mode 100644
> index 00000000000..da53278645c
> --- /dev/null
> +++ b/gcc/testsuite/g++.dg/cpp1y/decltype-auto6.C
> @@ -0,0 +1,19 @@
> +// PR c++/101165 - P2266R1 - Simpler implicit move
> +// { dg-do compile { target c++14 } }
> +// A variant of cxx23/elision1.C:eight, just with ().
> +
> +struct Widget {
> +  Widget(Widget&&);
> +};
> +
> +Widget val();
> +
> +decltype(auto)
> +foo ()
> +{
> +  decltype(auto) x = val();  // OK, x is Widget
> +  // We deduce the return type to int&&, therefore we're doing something
> +  // we ought not to be doing -- returning a reference to a local variable!
> +  // In C++20, we deduce to int&, but that has the same problem!
> +  return (x); // { dg-warning "reference to local variable" }
> +}
> diff --git a/gcc/testsuite/g++.dg/cpp1y/lambda-generic-89419.C b/gcc/testsuite/g++.dg/cpp1y/lambda-generic-89419.C
> index 46ce909f3b8..8e64d4e64ab 100644
> --- a/gcc/testsuite/g++.dg/cpp1y/lambda-generic-89419.C
> +++ b/gcc/testsuite/g++.dg/cpp1y/lambda-generic-89419.C
> @@ -2,7 +2,7 @@
>   // { dg-do compile { target c++14 } }
>   
>   struct A;
> -struct B {
> +struct B { // { dg-error "cannot bind" "" { target c++23 } }
>     struct C { C (); C (C &); } b;
>   };
>   struct D { A operator* (); };
> @@ -13,12 +13,12 @@ struct E {
>     auto bar () { return e; }
>     D e;
>   };
> -struct F { B f; int g; };
> +struct F { B f; int g; }; // { dg-error "use of deleted function" "" { target c++23 } }
>   
>   int
>   main ()
>   {
>     E e;
>     auto f = *e.bar ();
> -  auto i = [&] { F g; g.g = 1; auto h = [&](auto) { g.g = 0; }; f.foo (h); return g; };
> +  auto i = [&] { F g; g.g = 1; auto h = [&](auto) { g.g = 0; }; f.foo (h); return g; }; // { dg-error "use of deleted function" "" { target c++23 } }
>   }
> diff --git a/gcc/testsuite/g++.dg/cpp23/decltype1.C b/gcc/testsuite/g++.dg/cpp23/decltype1.C
> new file mode 100644
> index 00000000000..6f3cd0d45d5
> --- /dev/null
> +++ b/gcc/testsuite/g++.dg/cpp23/decltype1.C
> @@ -0,0 +1,113 @@
> +// PR c++/101165 - P2266R1 - Simpler implicit move
> +// { dg-do compile { target c++23 } }
> +// Tests from P2266R1, decltype-related changes in
> +// $ 3.2.1. Interaction with decltype and decltype(auto)
> +
> +template<typename T, typename U>
> +struct same_type { static const bool value = false; };
> +
> +template<typename T>
> +struct same_type<T, T> { static const bool value = true; };
> +
> +auto f1(int x) -> decltype(x) { return (x); }
> +static_assert(same_type<decltype(f1), int (int)>::value);
> +auto f2(int x) -> decltype((x)) { return (x); }   // { dg-error "cannot bind" }
> +static_assert(same_type<decltype(f2), int& (int)>::value);
> +auto f3(int x) -> decltype(auto) { return (x); }  // { dg-warning "reference to local variable" }
> +static_assert(same_type<decltype(f3), int&& (int)>::value);
> +auto g1(int x) -> decltype(x) { return x; }
> +static_assert(same_type<decltype(g1), int (int)>::value);
> +auto g2(int x) -> decltype((x)) { return x; }	  // { dg-error "cannot bind" }
> +static_assert(same_type<decltype(g2), int& (int)>::value);
> +auto g3(int x) -> decltype(auto) { return x; }
> +static_assert(same_type<decltype(g3), int (int)>::value);
> +
> +// Note that f2 and g2 are well-formed in C++20, but we propose to make
> +// f2 and g2 ill-formed, because they attempt to bind an lvalue reference
> +// to a move-eligible xvalue expression.
> +
> +struct X { };
> +
> +auto
> +f4 (X x)
> +{
> +  return x;
> +}
> +static_assert(same_type<decltype(f4), X(X)>::value);
> +
> +auto&
> +f5 (X x)
> +{
> +  return x; // { dg-error "cannot bind non-const lvalue reference" }
> +}
> +static_assert(same_type<decltype(f5), X&(X)>::value);
> +
> +auto&&
> +f6 (X x)
> +{
> +  return x; // { dg-warning "reference to local variable" }
> +}
> +static_assert(same_type<decltype(f6), X&&(X)>::value);
> +
> +auto
> +f7 (X x)
> +{
> +  return (x);
> +}
> +static_assert(same_type<decltype(f7), X(X)>::value);
> +
> +auto&
> +f8 (X x)
> +{
> +  return (x); // { dg-error "cannot bind non-const lvalue reference" }
> +}
> +static_assert(same_type<decltype(f8), X&(X)>::value);
> +
> +auto&&
> +f9 (X x)
> +{
> +  return (x); // { dg-warning "reference to local variable" }
> +}
> +static_assert(same_type<decltype(f9), X&&(X)>::value);
> +
> +decltype(auto)
> +f10 (X x)
> +{
> +  return x;
> +}
> +static_assert(same_type<decltype(f10), X(X)>::value);
> +
> +decltype(auto)
> +f11 (X x)
> +{
> +  return (x); // { dg-warning "reference to local variable" }
> +}
> +static_assert(same_type<decltype(f11), X&&(X)>::value);
> +
> +decltype(auto)
> +f12 (X& x)
> +{
> +  return x;
> +}
> +static_assert(same_type<decltype(f12), X&(X&)>::value);
> +
> +decltype(auto)
> +f13 (X& x)
> +{
> +  return (x);
> +}
> +static_assert(same_type<decltype(f13), X&(X&)>::value);
> +
> +decltype(auto)
> +f14 (X&& x)
> +{
> +  return x;
> +}
> +static_assert(same_type<decltype(f14), X&&(X&&)>::value);
> +
> +decltype(auto)
> +f15 (X&& x)
> +{
> +  return (x);
> +}
> +static_assert(same_type<decltype(f15), X&&(X&&)>::value);
> diff --git a/gcc/testsuite/g++.dg/cpp23/decltype2.C b/gcc/testsuite/g++.dg/cpp23/decltype2.C
> new file mode 100644
> index 00000000000..84679c48f82
> --- /dev/null
> +++ b/gcc/testsuite/g++.dg/cpp23/decltype2.C
> @@ -0,0 +1,49 @@
> +// PR c++/101165 - P2266R1 - Simpler implicit move
> +// { dg-do compile { target c++23 } }
> +// Test decltype(auto) more.
> +
> +template<typename T, typename U>
> +struct same_type { static const bool value = false; };
> +
> +template<typename T>
> +struct same_type<T, T> { static const bool value = true; };
> +
> +struct Widget {
> +  int x;
> +};
> +
> +Widget wg;
> +
> +decltype(auto) fn0(Widget&& x) {
> +    return (::wg);
> +}
> +static_assert(same_type<decltype(fn0), Widget& (Widget&&)>::value);
> +
> +decltype(auto) fn1(Widget&& x) {
> +    return ::wg;
> +}
> +static_assert(same_type<decltype(fn1), Widget (Widget&&)>::value);
> +
> +decltype(auto) fn2() {
> +    Widget w;
> +    return w;
> +}
> +static_assert(same_type<decltype(fn2), Widget ()>::value);
> +
> +decltype(auto) fn3() {
> +    Widget w;
> +    return (w); // { dg-warning "reference to local variable" }
> +}
> +static_assert(same_type<decltype(fn3), Widget&& ()>::value);
> +
> +decltype(auto) fn4() {
> +    Widget w;
> +    return w.x;
> +}
> +static_assert(same_type<decltype(fn4), int ()>::value);
> +
> +decltype(auto) fn5() {
> +    Widget w;
> +    return (w.x); // { dg-warning "reference to local variable" }
> +}
> +static_assert(same_type<decltype(fn5), int& ()>::value);
> diff --git a/gcc/testsuite/g++.dg/cpp23/elision1.C b/gcc/testsuite/g++.dg/cpp23/elision1.C
> new file mode 100644
> index 00000000000..8115739b7f9
> --- /dev/null
> +++ b/gcc/testsuite/g++.dg/cpp23/elision1.C
> @@ -0,0 +1,109 @@
> +// PR c++/101165 - P2266R1 - Simpler implicit move
> +// { dg-do compile { target c++23 } }
> +// Tests from P2266R1.
> +
> +namespace std {
> +  template<typename _Tp>
> +    struct remove_reference
> +    { typedef _Tp   type; };
> +
> +  template<typename _Tp>
> +    struct remove_reference<_Tp&>
> +    { typedef _Tp   type; };
> +
> +  template<typename _Tp>
> +    struct remove_reference<_Tp&&>
> +    { typedef _Tp   type; };
> +
> +  template<typename _Tp>
> +    constexpr typename std::remove_reference<_Tp>::type&&
> +    move(_Tp&& __t) noexcept
> +    { return static_cast<typename std::remove_reference<_Tp>::type&&>(__t); }
> +}
> +
> +template<typename T, typename U>
> +struct same_type { static const bool value = false; };
> +
> +template<typename T>
> +struct same_type<T, T> { static const bool value = true; };
> +
> +struct Widget {
> +    Widget(Widget&&);
> +};
> +
> +struct RRefTaker {
> +    RRefTaker(Widget&&);
> +};
> +
> +struct Mutt {
> +    operator int*() &&;
> +};
> +
> +struct Jeff {
> +    operator int&() &&;
> +};
> +
> +Widget one(Widget w) {
> +    return w;  // OK since C++11
> +}
> +
> +RRefTaker two(Widget w) {
> +    return w;  // OK since C++11 + CWG1579
> +}
> +
> +RRefTaker three(Widget&& w) {
> +    return w;  // OK since C++20 because P0527
> +}
> +
> +// Tests that implicit move applies even to functions that return references.
> +Widget&& four(Widget&& w) {
> +    return w;  // OK since C++23
> +}
> +
> +// FIXME This is supposed to work but we reject it with
> +// error: passing 'Mutt' as 'this' argument discards qualifiers
> +#if 0
> +int* five(Mutt x) {
> +    return x;  // OK since C++20 because P1155
> +}
> +#endif
> +
> +int& six(Jeff x) {
> +    return x;
> +}
> +
> +template<class T>
> +T&& seven(T&& x) { return x; }
> +
> +void test_seven(Widget w) {
> +    Widget& r = seven(w);
> +    Widget&& rr = seven(std::move(w));
> +}
> +
> +Widget val();
> +Widget& lref();
> +Widget&& rref();
> +
> +decltype(auto) eight() {
> +    decltype(auto) x = val();  // OK, x is Widget
> +    return x;  // OK, return type is Widget, we get copy elision
> +}
> +
> +decltype(auto) nine() {
> +    decltype(auto) x = lref();  // OK, x is Widget&
> +    return x;  // OK, return type is Widget&
> +}
> +
> +decltype(auto) ten() {
> +  decltype(auto) x = rref();  // OK, x is Widget&&
> +  // This was an error: return type is Widget&&, cannot bind to x.
> +  // But in C++23, x is treated as an rvalue.
> +  return x;
> +}
> +
> +// Now returns Widget&&, not Widget&.
> +// This is from $ 3.2.1. Interaction with decltype and decltype(auto).
> +decltype(auto) eleven(Widget&& x) {
> +    return (x);
> +}
> +static_assert(same_type<decltype(eleven), Widget&& (Widget&&)>::value);
> diff --git a/gcc/testsuite/g++.dg/cpp23/elision2.C b/gcc/testsuite/g++.dg/cpp23/elision2.C
> new file mode 100644
> index 00000000000..ce2c7aeef66
> --- /dev/null
> +++ b/gcc/testsuite/g++.dg/cpp23/elision2.C
> @@ -0,0 +1,46 @@
> +// PR c++/101165 - P2266R1 - Simpler implicit move
> +// { dg-do compile { target c++20 } }
> +// Test from P2266R1, $ 3.3. Two overload resolutions are overly confusing.
> +
> +struct Widget {
> +    Widget();
> +    Widget(Widget&&);
> +};
> +
> +struct Frodo {
> +    Frodo(Widget&);
> +    Frodo(Widget&&) = delete;
> +};
> +
> +struct Sam {
> +    Sam(Widget&) = delete; // #1
> +    Sam(const Widget&);  // #2
> +};
> +
> +Sam twelve() {
> +    Widget w;
> +    // This is supposed to calls #2 since C++20 because P1155.
> +    // But we actually choose #1 since r11-2411 (in C++20 only).
> +    return w; // { dg-error "deleted" "" { target c++20_only } }
> +}
> +
> +Frodo thirteen() {
> +    Widget w;
> +    // This is a correct error in both C++20 and C++23.
> +    return w;  // { dg-error "use of deleted function" }
> +}
> +
> +struct Merry {};
> +struct Pippin {};
> +struct Together : Merry, Pippin {};
> +struct Quest {
> +    Quest(Merry&&);
> +    Quest(Pippin&&);
> +    Quest(Together&);
> +};
> +
> +Quest fourteen() {
> +  Together t;
> +  // C++20: calls Quest(Together&).  Proposed: ill-formed.
> +  return t; // { dg-error "ambiguous" "" { target c++23 } }
> +}
> diff --git a/gcc/testsuite/g++.dg/cpp23/elision3.C b/gcc/testsuite/g++.dg/cpp23/elision3.C
> new file mode 100644
> index 00000000000..246342e64d3
> --- /dev/null
> +++ b/gcc/testsuite/g++.dg/cpp23/elision3.C
> @@ -0,0 +1,16 @@
> +// PR c++/101165 - P2266R1 - Simpler implicit move
> +// { dg-do compile { target c++23 } }
> +// Test from P2266R1, $ 3.4. A specific case involving reference_wrapper.
> +
> +#include <functional>
> +
> +struct Widget {
> +    Widget();
> +    Widget(Widget&&);
> +};
> +
> +std::reference_wrapper<Widget> fifteen() {
> +    Widget w;
> +    // OK until CWG1579; OK after LWG2993.  Proposed: ill-formed
> +    return w;  // { dg-error "could not convert" }
> +}
> diff --git a/gcc/testsuite/g++.dg/cpp23/elision4.C b/gcc/testsuite/g++.dg/cpp23/elision4.C
> new file mode 100644
> index 00000000000..c19b86b8b5f
> --- /dev/null
> +++ b/gcc/testsuite/g++.dg/cpp23/elision4.C
> @@ -0,0 +1,38 @@
> +// PR c++/101165 - P2266R1 - Simpler implicit move
> +// { dg-do compile { target c++23 } }
> +// Test from P2266R1, $ 5.2. LibreOffice OString constructor.
> +
> +struct X {
> +    X(auto&);
> +};
> +
> +// The following compiles in C++20 (deducing X(char (&)[10])) but not
> +// after P2266 (because the returned expression now has type char (&&)[10],
> +// which cannot bind to auto&).
> +X f() {
> +    char a[10];
> +    return a; // { dg-error "cannot bind non-const lvalue reference" }
> +}
> +
> +// The solution was to change it by making the return convert explicitly
> +// rather than implicitly:
> +X fixed() {
> +    char a[10];
> +    return X(a);
> +}
> +
> +// $ 5.3. LibreOffice o3tl::temporary
> +
> +template<class T>
> +T& temporary1(T&& x) { return x; } // { dg-error "cannot bind non-const lvalue reference" }
> +
> +// Fixed by:
> +template<class T>
> +T& temporary2(T&& x) { return static_cast<T&>(x); }
> +
> +void
> +test ()
> +{
> +  int& r1 = temporary1 (42);
> +  int& r2 = temporary2 (42);
> +}
> diff --git a/gcc/testsuite/g++.dg/cpp23/elision5.C b/gcc/testsuite/g++.dg/cpp23/elision5.C
> new file mode 100644
> index 00000000000..a7d3e7c27c4
> --- /dev/null
> +++ b/gcc/testsuite/g++.dg/cpp23/elision5.C
> @@ -0,0 +1,53 @@
> +// PR c++/101165 - P2266R1 - Simpler implicit move
> +// { dg-do compile { target c++23 } }
> +// Test from [class.copy.elision]/4.
> +
> +class Thing {
> +public:
> +  Thing();
> +  ~Thing();
> +  Thing(Thing&&);
> +private:
> +  Thing(const Thing&);
> +};
> +
> +Thing f(bool b) {
> +  Thing t;
> +  if (b)
> +    throw t;            // OK, Thing(Thing&&) used (or elided) to throw t
> +  return t;             // OK, Thing(Thing&&) used (or elided) to return t
> +}
> +
> +Thing t2 = f(false);    // OK, no extra copy/move performed, t2 constructed by call to f
> +
> +struct Weird {
> +  Weird();
> +  Weird(Weird&);
> +};
> +
> +Weird g(bool b) {
> +  static Weird w1;
> +  Weird w2;
> +  if (b) {
> +    return w1;  // OK: Weird(Weird&)
> +  } else {
> +    return w2;  // { dg-error "cannot bind non-const lvalue reference" }
> +  }
> +}
> +
> +int& h(bool b, int i) {
> +  static int s;
> +  if (b)
> +    return s;   // OK
> +  else
> +    return i;   // { dg-error "cannot bind non-const lvalue reference" }
> +}
> +
> +decltype(auto) h2(Thing t) {
> +  return t;     // OK, t is an xvalue and h2's return type is Thing
> +}
> +
> +decltype(auto) h3(Thing t) {
> +  // OK, (t) is an xvalue and h3's return type is Thing&&
> +  return (t); // { dg-warning "reference to local variable" }
> +}
> diff --git a/gcc/testsuite/g++.dg/cpp23/elision6.C b/gcc/testsuite/g++.dg/cpp23/elision6.C
> new file mode 100644
> index 00000000000..5d58da9e577
> --- /dev/null
> +++ b/gcc/testsuite/g++.dg/cpp23/elision6.C
> @@ -0,0 +1,20 @@
> +// PR c++/101165 - P2266R1 - Simpler implicit move
> +// { dg-do compile { target c++23 } }
> +// From [diff.cpp20.expr].
> +
> +template<typename T, typename U>
> +struct same_type { static const bool value = false; };
> +
> +template<typename T>
> +struct same_type<T, T> { static const bool value = true; };
> +
> +// In C++23, returns int&&; previously returned int&.
> +decltype(auto) f(int&& x) { return (x); }
> +static_assert(same_type<decltype(f), int&& (int&&)>::value);
> +
> +// This used to work in C++20.
> +int& g(int&& x) { return x; } // { dg-error "cannot bind non-const lvalue reference" }
> +
> +template<typename T>
> +decltype(auto) h(T&& x) { return (x); }
> +static_assert(same_type<decltype(h(42)), int&&>::value);
> diff --git a/gcc/testsuite/g++.dg/cpp23/elision7.C b/gcc/testsuite/g++.dg/cpp23/elision7.C
> new file mode 100644
> index 00000000000..19fa89ae133
> --- /dev/null
> +++ b/gcc/testsuite/g++.dg/cpp23/elision7.C
> @@ -0,0 +1,72 @@
> +// PR c++/101165 - P2266R1 - Simpler implicit move
> +// { dg-do compile { target c++23 } }
> +
> +struct X {
> +  X ();
> +  X(X&&);
> +};
> +
> +X&& rref ();
> +
> +X&&
> +f1 (X&& x)
> +{
> +  return x;
> +}
> +
> +template<typename T> T&&
> +f2 (T&& x)
> +{
> +  return x;
> +}
> +template X& f2<X&>(X&);
> +template X&& f2<X>(X&&);
> +
> +X&&
> +f3 ()
> +{
> +  X&& x = rref ();
> +  return x;
> +}
> +
> +void
> +f4 ()
> +try {
> +  X x;
> +  throw x;
> +} catch (...) { }
> +
> +void
> +f5 ()
> +{
> +  auto l1 = [](auto x) -> auto { return x; };
> +  auto &&x1 = l1(X{});
> +  auto l2 = [](auto x) -> auto& { return x; }; // { dg-error "cannot bind non-const lvalue reference" }
> +  auto &&x2 = l2(X{});
> +  auto l3 = [](auto x) -> auto&& { return x; }; // { dg-warning "reference to local" }
> +  auto &&x3 = l3(X{});
> +}
> +
> +constexpr int &
> +f6 (int &&n)
> +{
> +  return n; // { dg-error "cannot bind non-const lvalue reference" }
> +}
> +
> +void
> +do_f6 ()
> +{
> +  auto x = f6 (42);
> +}
> +
> +template<typename T> auto &
> +f7 (T &&t)
> +{
> +  return t; // { dg-error "cannot bind non-const lvalue reference" }
> +}
> +
> +void
> +do_f7 ()
> +{
> +  const int &x = f7 (0);
> +}
> diff --git a/gcc/testsuite/g++.dg/cpp23/feat-cxx2b.C b/gcc/testsuite/g++.dg/cpp23/feat-cxx2b.C
> index d3e40724085..20453bb7b14 100644
> --- a/gcc/testsuite/g++.dg/cpp23/feat-cxx2b.C
> +++ b/gcc/testsuite/g++.dg/cpp23/feat-cxx2b.C
> @@ -563,3 +563,9 @@
>   #elif __cpp_named_character_escapes != 202207
>   #  error "__cpp_named_character_escapes != 202207"
>   #endif
> +
> +#ifndef __cpp_implicit_move
> +#  error "__cpp_implicit_move"
> +#elif __cpp_implicit_move != 202207
> +#  error "__cpp_implicit_move != 202207"
> +#endif
> diff --git a/gcc/testsuite/g++.dg/gomp/pr56217.C b/gcc/testsuite/g++.dg/gomp/pr56217.C
> index 03dfc5f180b..731c0c08811 100644
> --- a/gcc/testsuite/g++.dg/gomp/pr56217.C
> +++ b/gcc/testsuite/g++.dg/gomp/pr56217.C
> @@ -1,5 +1,5 @@
>   // PR middle-end/56217
> -// { dg-do compile }
> +// { dg-do compile { target c++20_down } }
>   // { dg-options "-fopenmp" }
>   
>   struct S { int *p; S (); S (S &); };
> @@ -10,5 +10,7 @@ foo ()
>     S s;
>     #pragma omp task shared (s)
>       s.p = 0;
> +  // This fails in C++23, because "cannot bind non-const lvalue reference of
> +  // type 'S&' to an rvalue of type 'S'".
>     return s;
>   }
> diff --git a/gcc/testsuite/g++.dg/warn/Wno-return-local-addr.C b/gcc/testsuite/g++.dg/warn/Wno-return-local-addr.C
> index e15bfa24f54..cc9bb59770e 100644
> --- a/gcc/testsuite/g++.dg/warn/Wno-return-local-addr.C
> +++ b/gcc/testsuite/g++.dg/warn/Wno-return-local-addr.C
> @@ -4,7 +4,7 @@
>   int& bad1()
>   {
>     int x = 0;
> -  return x;
> +  return x; // { dg-error "cannot bind non-const lvalue reference" "" { target c++23 } }
>   }
>   
>   int* bad2()
> diff --git a/gcc/testsuite/g++.dg/warn/Wreturn-local-addr.C b/gcc/testsuite/g++.dg/warn/Wreturn-local-addr.C
> index 642a5767e84..4c18c2f06a0 100644
> --- a/gcc/testsuite/g++.dg/warn/Wreturn-local-addr.C
> +++ b/gcc/testsuite/g++.dg/warn/Wreturn-local-addr.C
> @@ -5,7 +5,7 @@
>   int& bad1()
>   {
>     int x = 0;
> -  return x;		// { dg-error "reference to local variable" }
> +  return x;		// { dg-error "reference to local variable|cannot bind non-const lvalue reference" }
>   }
>   
>   int* bad2()
> diff --git a/gcc/testsuite/g++.old-deja/g++.brendan/crash55.C b/gcc/testsuite/g++.old-deja/g++.brendan/crash55.C
> index fd4d4b65edb..b93e6e0c695 100644
> --- a/gcc/testsuite/g++.old-deja/g++.brendan/crash55.C
> +++ b/gcc/testsuite/g++.old-deja/g++.brendan/crash55.C
> @@ -8,5 +8,6 @@
>   
>             local = x+2;
>         
> -          return local; // { dg-warning "reference to local" }
> +          return local; // { dg-warning "reference to local" "" { target c++20_down } }
> +// { dg-error "non-const lvalue" "" { target c++23 } .-1 }
>         }
> diff --git a/gcc/testsuite/g++.old-deja/g++.jason/temporary2.C b/gcc/testsuite/g++.old-deja/g++.jason/temporary2.C
> index c855f8f4a07..2709b50e7f1 100644
> --- a/gcc/testsuite/g++.old-deja/g++.jason/temporary2.C
> +++ b/gcc/testsuite/g++.old-deja/g++.jason/temporary2.C
> @@ -8,7 +8,7 @@ public:
>     int i;
>   };
>   
> -X foo() { X x; return x; }
> +X foo() { X x; return x; } // { dg-error "cannot bind non-const lvalue reference" "" { target c++23 } }
>   
>   int main()
>   {
> diff --git a/gcc/testsuite/g++.old-deja/g++.mike/p2846b.C b/gcc/testsuite/g++.old-deja/g++.mike/p2846b.C
> index 57422fe64df..68826649cfc 100644
> --- a/gcc/testsuite/g++.old-deja/g++.mike/p2846b.C
> +++ b/gcc/testsuite/g++.old-deja/g++.mike/p2846b.C
> @@ -1,4 +1,4 @@
> -// { dg-do run  }
> +// { dg-do run { target c++20_down } }
>   // Shows that problem of initializing one object's secondary base from
>   // another object via a user defined copy constructor for that base,
>   // the pointer for the secondary vtable is not set after implicit
> @@ -11,6 +11,8 @@
>   
>   // prms-id: 2846
>   
> +// This test fails in C++23 due to P2266.
> +
>   extern "C" int printf(const char *, ...);
>   extern "C" void exit(int);
>   
> 
> base-commit: 10d6109fe183d984a0377a7afe2854a0d794ebeb


^ permalink raw reply	[flat|nested] 10+ messages in thread

* [PATCH v3] c++: Implement C++23 P2266R1, Simpler implicit move [PR101165]
  2022-09-26 17:29     ` Jason Merrill
@ 2022-09-27 20:26       ` Marek Polacek
  2022-09-27 21:44         ` Jason Merrill
  0 siblings, 1 reply; 10+ messages in thread
From: Marek Polacek @ 2022-09-27 20:26 UTC (permalink / raw)
  To: Jason Merrill; +Cc: GCC Patches

On Mon, Sep 26, 2022 at 01:29:35PM -0400, Jason Merrill wrote:
> On 9/20/22 14:19, Marek Polacek wrote:
> > > > There's one FIXME in elision1.C:five, which we should compile but reject
> > > > with "passing 'Mutt' as 'this' argument discards qualifiers".  That
> > > > looks bogus to me, I think I'll open a PR for it.
> > > 
> > > Let's fix that now, I think.
> > 
> > OK, copypasting this bit from the other email so that we can have one
> > thread:
> > 
> > > Can of worms.   The test is
> > > 
> > >     struct Mutt {
> > >         operator int*() &&;
> > >     };
> > > 
> > >     int* five(Mutt x) {
> > >         return x;  // OK since C++20 because P1155
> > >     }
> > > 
> > > 'x' should be treated as an rvalue, therefore the operator fn taking
> > > an rvalue ref to Mutt should be used to convert 'x' to int*.  We fail
> > > because we don't treat 'x' as an rvalue because the function doesn't
> > > return a class.  So the patch should be just
> > > 
> > > --- a/gcc/cp/typeck.cc
> > > +++ b/gcc/cp/typeck.cc
> > > @@ -10875,10 +10875,7 @@ check_return_expr (tree retval, bool *no_warning)
> > >             Note that these conditions are similar to, but not as strict as,
> > >        the conditions for the named return value optimization.  */
> > >          bool converted = false;
> > > -      tree moved;
> > > -      /* This is only interesting for class type.  */
> > > -      if (CLASS_TYPE_P (functype)
> > > -     && (moved = treat_lvalue_as_rvalue_p (retval, /*return*/true)))
> > > +      if (tree moved = treat_lvalue_as_rvalue_p (retval, /*return*/true))
> > >       {
> > >         if (cxx_dialect < cxx20)
> > >           {
> > > 
> > > which fixes the test, but breaks a lot of middle-end warnings.  For instance
> > > g++.dg/warn/nonnull3.C, where the patch above changes .gimple:
> > > 
> > >    bool A::foo<B> (struct A * const this, <<< Unknown tree: offset_type >>> p)
> > >    {
> > > -  bool D.2146;
> > > +  bool D.2150;
> > > -  D.2146 = p != -1;
> > > -  return D.2146;
> > > +  p.0_1 = p;
> > > +  D.2150 = p.0_1 != -1;
> > > +  return D.2150;
> > >    }
> > > 
> > > and we no longer get the warning.  I thought maybe I could undo the implicit
> > > rvalue conversion in cp_fold, when it sees implicit_rvalue_p, but that didn't
> > > work.  So currently I'm stuck.  Should we try to figure this out or push aside?
> > 
> > > Can you undo the implicit rvalue conversion within check_return_expr,
> > > where we can still refer back to the original expression?
> > 
> > Unfortunately no, one problem is that treat_lvalue_as_rvalue_p modifies
> > the underlying decl by setting TREE_ADDRESSABLE, which then presumably
> > breaks warnings.  That is, treat_ can get 'VCE<X>(x)' and produce
> > '*NLE<(X&) &x>' where 'x' flags have been modified, since we're taking
> > x's address.
> > 
> > > Or avoid the rvalue conversion if the return type is scalar?
> > 
> > I wish :(.  In the 'five' example above, the return type is a pointer,
> > a scalar, but we have to convert to rvalue.
> 
> OK, then when both the return type and the type of the return value are
> scalar?

Good news!  After more poking it seems we only need to do the rvalue
conversion when either to/from types is a class/reference!  That is,
if either is a non-scalar type.  And that doesn't upset the middle-end
diagnostics!  I'm still limiting the broader conversion to C++23, but
the whole condition could be:

  if ((!SCALAR_TYPE_P (functype) || !SCALAR_TYPE_P (rettype))
      && treat_lvalue_as_rvalue_p ())
  ...
 
Therefore I think we don't need ...

> > It's sort of sad that this corner case causes so much trouble: I think
> > we have to do the conversion only because of ref-qualifiers, so that
> > the correct operator function is chosen.
> > 
> > A way out may be setting a flag on the V_C_E that indicates it is an
> > rvalue, rather than performing the conversion above.  This was your
> > idea so I don't want to take credit for it.  Should I go ahead and
> > try it?
> 
> Sure, probably in build_static_cast_1.

... this, after all, which is just fantastic.  Besides the check_return_expr
hunk and removing a FIXME there are no other changes.

Next step: remove the double overload.

Bootstrapped/regtested on x86_64-pc-linux-gnu, ok for trunk?

-- >8 --
This patch implements https://wg21.link/p2266, which, once again,
changes the implicit move rules.  Here's a brief summary of various
changes in this area:

r125211: Introduced moving from certain lvalues when returning them
r171071: CWG 1148, enable move from value parameter on return
r212099: CWG 1579, it's OK to call a converting ctor taking an rvalue
r251035: CWG 1579, do maybe-rvalue overload resolution twice
r11-2411: Avoid calling const copy ctor on implicit move
r11-2412: C++20 implicit move changes, remove the fallback overload
          resolution, allow move on throw of parameters and implicit
	  move of rvalue references

P2266 enables the implicit move even for functions that return references.
That is, we will now perform a move in

  X&& foo (X&& x) {
    return x;
  }

P2266 also removes the fallback overload resolution, but this was
resolved by r11-2412: we only do convert_for_initialization with
LOOKUP_PREFER_RVALUE in C++17 and older.
P2266 also says that a returned move-eligible id-expression is always an
xvalue.  This required some further short, but nontrivial changes,
especially when it comes to deduction, because we have to pay attention
to whether we have auto, auto&& (which is like T&&), or decltype(auto)
with (un)parenthesized argument.  In C++23,

  decltype(auto) f(int&& x) { return (x); }
  auto&& f(int x) { return x; }

both should deduce to 'int&&' but

  decltype(auto) f(int x) { return x; }

should deduce to 'int'.  A cornucopia of tests attached.  I've also
verified that we behave like clang++.

xvalue_p seemed to be broken: since the introduction of clk_implicit_rval,
it cannot use '==' when checking for clk_rvalueref.

Since this change breaks code, it's only enabled in C++23.  In
particular, this code will not compile in C++23:

  int& g(int&& x) { return x; }

because x is now treated as an rvalue, and you can't bind a non-const lvalue
reference to an rvalue.

This patch also fixes PR106882 (the check_return_expr changes).

	PR c++/101165
	PR c++/106882

gcc/c-family/ChangeLog:

	* c-cppbuiltin.cc (c_cpp_builtins): Define __cpp_implicit_move.

gcc/cp/ChangeLog:

	* call.cc (reference_binding): Check clk_implicit_rval in C++20 only.
	* cp-tree.h (unparenthesized_id_or_class_member_access_p): Declare.
	* pt.cc (unparenthesized_id_or_class_member_access_p): New function,
	broken out of...
	(do_auto_deduction): ...here.  Use it.  In C++23, maybe call
	treat_lvalue_as_rvalue_p.
	* tree.cc (xvalue_p): Check & clk_rvalueref, not == clk_rvalueref.
	* typeck.cc (check_return_expr): Allow implicit move for functions
	returning a reference as well, or when the return value type is not
	a scalar type.

gcc/testsuite/ChangeLog:

	* g++.dg/conversion/pr41426.C: Add dg-error for C++23.
	* g++.dg/cpp0x/elision_weak.C: Likewise.
	* g++.dg/cpp0x/move-return3.C: Only link in c++20_down.
	* g++.dg/cpp1y/decltype-auto2.C: Add dg-error for C++23.
	* g++.dg/cpp1y/lambda-generic-89419.C: Likewise.
	* g++.dg/cpp23/feat-cxx2b.C: Test __cpp_implicit_move.
	* g++.dg/gomp/pr56217.C: Only compile in c++20_down.
	* g++.dg/warn/Wno-return-local-addr.C: Add dg-error for C++23.
	* g++.dg/warn/Wreturn-local-addr.C: Adjust dg-error.
	* g++.old-deja/g++.brendan/crash55.C: Add dg-error for C++23.
	* g++.old-deja/g++.jason/temporary2.C: Likewise.
	* g++.old-deja/g++.mike/p2846b.C: Only run in c++20_down.
	* g++.dg/cpp1y/decltype-auto6.C: New test.
	* g++.dg/cpp23/decltype1.C: New test.
	* g++.dg/cpp23/decltype2.C: New test.
	* g++.dg/cpp23/elision1.C: New test.
	* g++.dg/cpp23/elision2.C: New test.
	* g++.dg/cpp23/elision3.C: New test.
	* g++.dg/cpp23/elision4.C: New test.
	* g++.dg/cpp23/elision5.C: New test.
	* g++.dg/cpp23/elision6.C: New test.
	* g++.dg/cpp23/elision7.C: New test.
---
 gcc/c-family/c-cppbuiltin.cc                  |   1 +
 gcc/cp/call.cc                                |   6 +-
 gcc/cp/cp-tree.h                              |   1 +
 gcc/cp/pt.cc                                  |  50 ++++++--
 gcc/cp/tree.cc                                |   2 +-
 gcc/cp/typeck.cc                              |   9 +-
 gcc/testsuite/g++.dg/conversion/pr41426.C     |  10 +-
 gcc/testsuite/g++.dg/cpp0x/elision_weak.C     |   4 +-
 gcc/testsuite/g++.dg/cpp0x/move-return3.C     |   3 +-
 gcc/testsuite/g++.dg/cpp1y/decltype-auto2.C   |   2 +-
 gcc/testsuite/g++.dg/cpp1y/decltype-auto6.C   |  19 +++
 .../g++.dg/cpp1y/lambda-generic-89419.C       |   6 +-
 gcc/testsuite/g++.dg/cpp23/decltype1.C        | 113 +++++++++++++++++
 gcc/testsuite/g++.dg/cpp23/decltype2.C        |  49 ++++++++
 gcc/testsuite/g++.dg/cpp23/elision1.C         | 114 ++++++++++++++++++
 gcc/testsuite/g++.dg/cpp23/elision2.C         |  46 +++++++
 gcc/testsuite/g++.dg/cpp23/elision3.C         |  16 +++
 gcc/testsuite/g++.dg/cpp23/elision4.C         |  38 ++++++
 gcc/testsuite/g++.dg/cpp23/elision5.C         |  53 ++++++++
 gcc/testsuite/g++.dg/cpp23/elision6.C         |  20 +++
 gcc/testsuite/g++.dg/cpp23/elision7.C         |  72 +++++++++++
 gcc/testsuite/g++.dg/cpp23/feat-cxx2b.C       |   6 +
 gcc/testsuite/g++.dg/gomp/pr56217.C           |   4 +-
 .../g++.dg/warn/Wno-return-local-addr.C       |   2 +-
 .../g++.dg/warn/Wreturn-local-addr.C          |   2 +-
 .../g++.old-deja/g++.brendan/crash55.C        |   3 +-
 .../g++.old-deja/g++.jason/temporary2.C       |   2 +-
 gcc/testsuite/g++.old-deja/g++.mike/p2846b.C  |   4 +-
 28 files changed, 622 insertions(+), 35 deletions(-)
 create mode 100644 gcc/testsuite/g++.dg/cpp1y/decltype-auto6.C
 create mode 100644 gcc/testsuite/g++.dg/cpp23/decltype1.C
 create mode 100644 gcc/testsuite/g++.dg/cpp23/decltype2.C
 create mode 100644 gcc/testsuite/g++.dg/cpp23/elision1.C
 create mode 100644 gcc/testsuite/g++.dg/cpp23/elision2.C
 create mode 100644 gcc/testsuite/g++.dg/cpp23/elision3.C
 create mode 100644 gcc/testsuite/g++.dg/cpp23/elision4.C
 create mode 100644 gcc/testsuite/g++.dg/cpp23/elision5.C
 create mode 100644 gcc/testsuite/g++.dg/cpp23/elision6.C
 create mode 100644 gcc/testsuite/g++.dg/cpp23/elision7.C

diff --git a/gcc/c-family/c-cppbuiltin.cc b/gcc/c-family/c-cppbuiltin.cc
index ca5f500e079..d4de5a0dc57 100644
--- a/gcc/c-family/c-cppbuiltin.cc
+++ b/gcc/c-family/c-cppbuiltin.cc
@@ -1082,6 +1082,7 @@ c_cpp_builtins (cpp_reader *pfile)
 	  cpp_define (pfile, "__cpp_multidimensional_subscript=202110L");
 	  cpp_define (pfile, "__cpp_named_character_escapes=202207L");
 	  cpp_define (pfile, "__cpp_static_call_operator=202207L");
+	  cpp_define (pfile, "__cpp_implicit_move=202207L");
 	}
       if (flag_concepts)
         {
diff --git a/gcc/cp/call.cc b/gcc/cp/call.cc
index fc86b74a5a4..3506b0fcfbb 100644
--- a/gcc/cp/call.cc
+++ b/gcc/cp/call.cc
@@ -1880,8 +1880,10 @@ reference_binding (tree rto, tree rfrom, tree expr, bool c_cast_p, int flags,
 
       /* Nor the reverse.  */
       if (!is_lvalue && !TYPE_REF_IS_RVALUE (rto)
-	  /* Unless it's really an lvalue.  */
-	  && !(cxx_dialect >= cxx20
+	  /* Unless it's really a C++20 lvalue being treated as an xvalue.
+	     But in C++23, such an expression is just an xvalue, not a special
+	     lvalue, so the binding is once again ill-formed.  */
+	  && !(cxx_dialect == cxx20
 	       && (gl_kind & clk_implicit_rval))
 	  && (!CP_TYPE_CONST_NON_VOLATILE_P (to)
 	      || (flags & LOOKUP_NO_RVAL_BIND))
diff --git a/gcc/cp/cp-tree.h b/gcc/cp/cp-tree.h
index 99b486b8002..19bbfbc557f 100644
--- a/gcc/cp/cp-tree.h
+++ b/gcc/cp/cp-tree.h
@@ -7292,6 +7292,7 @@ extern tree make_constrained_decltype_auto	(tree, tree);
 extern tree make_template_placeholder		(tree);
 extern bool template_placeholder_p		(tree);
 extern bool ctad_template_p			(tree);
+extern bool unparenthesized_id_or_class_member_access_p (tree);
 extern tree do_auto_deduction                   (tree, tree, tree,
                                                  tsubst_flags_t
 						 = tf_warning_or_error,
diff --git a/gcc/cp/pt.cc b/gcc/cp/pt.cc
index 1c1e5735743..2d83dfd6954 100644
--- a/gcc/cp/pt.cc
+++ b/gcc/cp/pt.cc
@@ -30408,6 +30408,26 @@ do_class_deduction (tree ptype, tree tmpl, tree init,
 				  cp_type_quals (ptype));
 }
 
+/* Return true if INIT is an unparenthesized id-expression or an
+   unparenthesized class member access.  Used for the argument of
+   decltype(auto).  */
+
+bool
+unparenthesized_id_or_class_member_access_p (tree init)
+{
+  STRIP_ANY_LOCATION_WRAPPER (init);
+
+  /* We need to be able to tell '(r)' and 'r' apart (when it's of
+     reference type).  Only the latter is an id-expression.  */
+  if (REFERENCE_REF_P (init)
+      && !REF_PARENTHESIZED_P (init))
+    init = TREE_OPERAND (init, 0);
+  return (DECL_P (init)
+	  || ((TREE_CODE (init) == COMPONENT_REF
+	       || TREE_CODE (init) == SCOPE_REF)
+	      && !REF_PARENTHESIZED_P (init)));
+}
+
 /* Replace occurrences of 'auto' in TYPE with the appropriate type deduced
    from INIT.  AUTO_NODE is the TEMPLATE_TYPE_PARM used for 'auto' in TYPE.
    The CONTEXT determines the context in which auto deduction is performed
@@ -30443,6 +30463,23 @@ do_auto_deduction (tree type, tree init, tree auto_node,
      auto_node.  */
   complain &= ~tf_partial;
 
+  /* In C++23, we must deduce the type to int&& for code like
+       decltype(auto) f(int&& x) { return (x); }
+     or
+       auto&& f(int x) { return x; }
+     so we use treat_lvalue_as_rvalue_p.  But don't do it for
+       decltype(auto) f(int x) { return x; }
+     where we should deduce 'int' rather than 'int&&'; transmogrifying
+     INIT to an rvalue would break that.  */
+  tree r;
+  if (cxx_dialect >= cxx23
+      && context == adc_return_type
+      && (!AUTO_IS_DECLTYPE (auto_node)
+	  || !unparenthesized_id_or_class_member_access_p (init))
+      && (r = treat_lvalue_as_rvalue_p (maybe_undo_parenthesized_ref (init),
+					/*return*/true)))
+    init = r;
+
   if (tree tmpl = CLASS_PLACEHOLDER_TEMPLATE (auto_node))
     /* C++17 class template argument deduction.  */
     return do_class_deduction (type, tmpl, init, flags, complain);
@@ -30504,18 +30541,7 @@ do_auto_deduction (tree type, tree init, tree auto_node,
     }
   else if (AUTO_IS_DECLTYPE (auto_node))
     {
-      /* Figure out if INIT is an unparenthesized id-expression or an
-	 unparenthesized class member access.  */
-      tree stripped_init = tree_strip_any_location_wrapper (init);
-      /* We need to be able to tell '(r)' and 'r' apart (when it's of
-	 reference type).  Only the latter is an id-expression.  */
-      if (REFERENCE_REF_P (stripped_init)
-	  && !REF_PARENTHESIZED_P (stripped_init))
-	stripped_init = TREE_OPERAND (stripped_init, 0);
-      const bool id = (DECL_P (stripped_init)
-		       || ((TREE_CODE (stripped_init) == COMPONENT_REF
-			    || TREE_CODE (stripped_init) == SCOPE_REF)
-			   && !REF_PARENTHESIZED_P (stripped_init)));
+      const bool id = unparenthesized_id_or_class_member_access_p (init);
       tree deduced = finish_decltype_type (init, id, complain);
       deduced = canonicalize_type_argument (deduced, complain);
       if (deduced == error_mark_node)
diff --git a/gcc/cp/tree.cc b/gcc/cp/tree.cc
index d0bd41ae5a0..ea4dfc651bb 100644
--- a/gcc/cp/tree.cc
+++ b/gcc/cp/tree.cc
@@ -382,7 +382,7 @@ obvalue_p (const_tree ref)
 bool
 xvalue_p (const_tree ref)
 {
-  return (lvalue_kind (ref) == clk_rvalueref);
+  return (lvalue_kind (ref) & clk_rvalueref);
 }
 
 /* True if REF is a bit-field.  */
diff --git a/gcc/cp/typeck.cc b/gcc/cp/typeck.cc
index 4854b983765..771331b6df9 100644
--- a/gcc/cp/typeck.cc
+++ b/gcc/cp/typeck.cc
@@ -11042,8 +11042,13 @@ check_return_expr (tree retval, bool *no_warning)
 	 the conditions for the named return value optimization.  */
       bool converted = false;
       tree moved;
-      /* This is only interesting for class type.  */
-      if (CLASS_TYPE_P (functype)
+      /* Until C++23, this was only interesting for class type...  */
+      if ((CLASS_TYPE_P (functype)
+	   /* ...but in C++23, we should do the below when we're converting
+	      from/to a class/reference (a non-scalar type).  */
+	   || (cxx_dialect >= cxx23
+	       && (!SCALAR_TYPE_P (functype)
+		   || !SCALAR_TYPE_P (TREE_TYPE (retval)))))
 	  && (moved = treat_lvalue_as_rvalue_p (retval, /*return*/true)))
 	{
 	  if (cxx_dialect < cxx20)
diff --git a/gcc/testsuite/g++.dg/conversion/pr41426.C b/gcc/testsuite/g++.dg/conversion/pr41426.C
index 5493a91ecfa..b4ecbca5f3a 100644
--- a/gcc/testsuite/g++.dg/conversion/pr41426.C
+++ b/gcc/testsuite/g++.dg/conversion/pr41426.C
@@ -11,19 +11,20 @@ struct A
 A<float> g1()
 {
    float f[] = {1.1f, 2.3f};
-   return f;
+   return f; // { dg-error "cannot bind non-const" "" { target c++23 } }
 }
 
 const A<float> &g3()
 {
    float f[] = {1.1f, 2.3f};
-   return f; // { dg-warning "returning reference to temporary" }
+   return f; // { dg-warning "returning reference to temporary" "" { target c++20_down } }
+// { dg-error "non-const lvalue|invalid user-defined conversion" "" { target c++23 } .-1 }
 }
 
 A<float> &g4()
 {
    float f[] = {1.1f, 2.3f};
-   return f; // { dg-error "cannot bind non-const lvalue ref" }
+   return f; // { dg-error "cannot bind non-const lvalue ref|invalid user-defined conversion" }
 }
 
 struct B
@@ -35,6 +36,5 @@ struct B
 B g2()
 {
    int c[10];
-   return c;
+   return c; // { dg-error "non-const lvalue" "" { target c++23 } }
 }
-
diff --git a/gcc/testsuite/g++.dg/cpp0x/elision_weak.C b/gcc/testsuite/g++.dg/cpp0x/elision_weak.C
index e8ba7551d84..ddd12743130 100644
--- a/gcc/testsuite/g++.dg/cpp0x/elision_weak.C
+++ b/gcc/testsuite/g++.dg/cpp0x/elision_weak.C
@@ -9,11 +9,11 @@ struct S
 S f()
 {
   S s;
-  return s;
+  return s; // { dg-error "cannot bind non-const lvalue reference" "" { target c++23 } }
 }
 
 void g()
 {
   S s;
-  throw s;
+  throw s; // { dg-error "cannot bind non-const lvalue reference" "" { target c++23 } }
 }
diff --git a/gcc/testsuite/g++.dg/cpp0x/move-return3.C b/gcc/testsuite/g++.dg/cpp0x/move-return3.C
index c79f0591936..30a936fb35a 100644
--- a/gcc/testsuite/g++.dg/cpp0x/move-return3.C
+++ b/gcc/testsuite/g++.dg/cpp0x/move-return3.C
@@ -1,6 +1,7 @@
 // PR c++/91212
 // Test that C++11 implicit move semantics don't call the const copy.
-// { dg-do link }
+// In C++23, we call #2.
+// { dg-do link { target c++20_down } }
 
 struct T { int i; };
 
diff --git a/gcc/testsuite/g++.dg/cpp1y/decltype-auto2.C b/gcc/testsuite/g++.dg/cpp1y/decltype-auto2.C
index 56e011e36f4..24b32edfacf 100644
--- a/gcc/testsuite/g++.dg/cpp1y/decltype-auto2.C
+++ b/gcc/testsuite/g++.dg/cpp1y/decltype-auto2.C
@@ -8,5 +8,5 @@ auto constexpr RtoL1(T&& r) -> decltype(auto) {
 int main() {
     int t;
     int x{3};
-    decltype (RtoL1(x+0)) y = t;
+    decltype (RtoL1(x+0)) y = t; // { dg-error "cannot bind rvalue reference" "" { target c++23 } }
 }
diff --git a/gcc/testsuite/g++.dg/cpp1y/decltype-auto6.C b/gcc/testsuite/g++.dg/cpp1y/decltype-auto6.C
new file mode 100644
index 00000000000..da53278645c
--- /dev/null
+++ b/gcc/testsuite/g++.dg/cpp1y/decltype-auto6.C
@@ -0,0 +1,19 @@
+// PR c++/101165 - P2266R1 - Simpler implicit move
+// { dg-do compile { target c++14 } }
+// A variant of cxx23/elision1.C:eight, just with ().
+
+struct Widget {
+  Widget(Widget&&);
+};
+
+Widget val();
+
+decltype(auto)
+foo ()
+{
+  decltype(auto) x = val();  // OK, x is Widget
+  // We deduce the return type to int&&, therefore we're doing something
+  // we ought not to be doing -- returning a reference to a local variable!
+  // In C++20, we deduce to int&, but that has the same problem!
+  return (x); // { dg-warning "reference to local variable" }
+}
diff --git a/gcc/testsuite/g++.dg/cpp1y/lambda-generic-89419.C b/gcc/testsuite/g++.dg/cpp1y/lambda-generic-89419.C
index 46ce909f3b8..8e64d4e64ab 100644
--- a/gcc/testsuite/g++.dg/cpp1y/lambda-generic-89419.C
+++ b/gcc/testsuite/g++.dg/cpp1y/lambda-generic-89419.C
@@ -2,7 +2,7 @@
 // { dg-do compile { target c++14 } }
 
 struct A;
-struct B {
+struct B { // { dg-error "cannot bind" "" { target c++23 } }
   struct C { C (); C (C &); } b;
 };
 struct D { A operator* (); };
@@ -13,12 +13,12 @@ struct E {
   auto bar () { return e; }
   D e;
 };
-struct F { B f; int g; };
+struct F { B f; int g; }; // { dg-error "use of deleted function" "" { target c++23 } }
 
 int
 main ()
 {
   E e;
   auto f = *e.bar ();
-  auto i = [&] { F g; g.g = 1; auto h = [&](auto) { g.g = 0; }; f.foo (h); return g; };
+  auto i = [&] { F g; g.g = 1; auto h = [&](auto) { g.g = 0; }; f.foo (h); return g; }; // { dg-error "use of deleted function" "" { target c++23 } }
 }
diff --git a/gcc/testsuite/g++.dg/cpp23/decltype1.C b/gcc/testsuite/g++.dg/cpp23/decltype1.C
new file mode 100644
index 00000000000..6f3cd0d45d5
--- /dev/null
+++ b/gcc/testsuite/g++.dg/cpp23/decltype1.C
@@ -0,0 +1,113 @@
+// PR c++/101165 - P2266R1 - Simpler implicit move
+// { dg-do compile { target c++23 } }
+// Tests from P2266R1, decltype-related changes in
+// $ 3.2.1. Interaction with decltype and decltype(auto)
+
+template<typename T, typename U>
+struct same_type { static const bool value = false; };
+
+template<typename T>
+struct same_type<T, T> { static const bool value = true; };
+
+auto f1(int x) -> decltype(x) { return (x); }
+static_assert(same_type<decltype(f1), int (int)>::value);
+auto f2(int x) -> decltype((x)) { return (x); }   // { dg-error "cannot bind" }
+static_assert(same_type<decltype(f2), int& (int)>::value);
+auto f3(int x) -> decltype(auto) { return (x); }  // { dg-warning "reference to local variable" }
+static_assert(same_type<decltype(f3), int&& (int)>::value);
+auto g1(int x) -> decltype(x) { return x; }
+static_assert(same_type<decltype(g1), int (int)>::value);
+auto g2(int x) -> decltype((x)) { return x; }	  // { dg-error "cannot bind" }
+static_assert(same_type<decltype(g2), int& (int)>::value);
+auto g3(int x) -> decltype(auto) { return x; }
+static_assert(same_type<decltype(g3), int (int)>::value);
+
+// Note that f2 and g2 are well-formed in C++20, but we propose to make
+// f2 and g2 ill-formed, because they attempt to bind an lvalue reference
+// to a move-eligible xvalue expression.
+
+struct X { };
+
+auto
+f4 (X x)
+{
+  return x;
+}
+static_assert(same_type<decltype(f4), X(X)>::value);
+
+auto&
+f5 (X x)
+{
+  return x; // { dg-error "cannot bind non-const lvalue reference" }
+}
+static_assert(same_type<decltype(f5), X&(X)>::value);
+
+auto&&
+f6 (X x)
+{
+  return x; // { dg-warning "reference to local variable" }
+}
+static_assert(same_type<decltype(f6), X&&(X)>::value);
+
+auto
+f7 (X x)
+{
+  return (x);
+}
+static_assert(same_type<decltype(f7), X(X)>::value);
+
+auto&
+f8 (X x)
+{
+  return (x); // { dg-error "cannot bind non-const lvalue reference" }
+}
+static_assert(same_type<decltype(f8), X&(X)>::value);
+
+auto&&
+f9 (X x)
+{
+  return (x); // { dg-warning "reference to local variable" }
+}
+static_assert(same_type<decltype(f9), X&&(X)>::value);
+
+decltype(auto)
+f10 (X x)
+{
+  return x;
+}
+static_assert(same_type<decltype(f10), X(X)>::value);
+
+decltype(auto)
+f11 (X x)
+{
+  return (x); // { dg-warning "reference to local variable" }
+}
+static_assert(same_type<decltype(f11), X&&(X)>::value);
+
+decltype(auto)
+f12 (X& x)
+{
+  return x;
+}
+static_assert(same_type<decltype(f12), X&(X&)>::value);
+
+decltype(auto)
+f13 (X& x)
+{
+  return (x);
+}
+static_assert(same_type<decltype(f13), X&(X&)>::value);
+
+decltype(auto)
+f14 (X&& x)
+{
+  return x;
+}
+static_assert(same_type<decltype(f14), X&&(X&&)>::value);
+
+decltype(auto)
+f15 (X&& x)
+{
+  return (x);
+}
+static_assert(same_type<decltype(f15), X&&(X&&)>::value);
diff --git a/gcc/testsuite/g++.dg/cpp23/decltype2.C b/gcc/testsuite/g++.dg/cpp23/decltype2.C
new file mode 100644
index 00000000000..84679c48f82
--- /dev/null
+++ b/gcc/testsuite/g++.dg/cpp23/decltype2.C
@@ -0,0 +1,49 @@
+// PR c++/101165 - P2266R1 - Simpler implicit move
+// { dg-do compile { target c++23 } }
+// Test decltype(auto) more.
+
+template<typename T, typename U>
+struct same_type { static const bool value = false; };
+
+template<typename T>
+struct same_type<T, T> { static const bool value = true; };
+
+struct Widget {
+  int x;
+};
+
+Widget wg;
+
+decltype(auto) fn0(Widget&& x) {
+    return (::wg);
+}
+static_assert(same_type<decltype(fn0), Widget& (Widget&&)>::value);
+
+decltype(auto) fn1(Widget&& x) {
+    return ::wg;
+}
+static_assert(same_type<decltype(fn1), Widget (Widget&&)>::value);
+
+decltype(auto) fn2() {
+    Widget w;
+    return w;
+}
+static_assert(same_type<decltype(fn2), Widget ()>::value);
+
+decltype(auto) fn3() {
+    Widget w;
+    return (w); // { dg-warning "reference to local variable" }
+}
+static_assert(same_type<decltype(fn3), Widget&& ()>::value);
+
+decltype(auto) fn4() {
+    Widget w;
+    return w.x;
+}
+static_assert(same_type<decltype(fn4), int ()>::value);
+
+decltype(auto) fn5() {
+    Widget w;
+    return (w.x); // { dg-warning "reference to local variable" }
+}
+static_assert(same_type<decltype(fn5), int& ()>::value);
diff --git a/gcc/testsuite/g++.dg/cpp23/elision1.C b/gcc/testsuite/g++.dg/cpp23/elision1.C
new file mode 100644
index 00000000000..f44fd2a061c
--- /dev/null
+++ b/gcc/testsuite/g++.dg/cpp23/elision1.C
@@ -0,0 +1,114 @@
+// PR c++/101165 - P2266R1 - Simpler implicit move
+// { dg-do compile { target c++23 } }
+// Tests from P2266R1.
+
+namespace std {
+  template<typename _Tp>
+    struct remove_reference
+    { typedef _Tp   type; };
+
+  template<typename _Tp>
+    struct remove_reference<_Tp&>
+    { typedef _Tp   type; };
+
+  template<typename _Tp>
+    struct remove_reference<_Tp&&>
+    { typedef _Tp   type; };
+
+  template<typename _Tp>
+    constexpr typename std::remove_reference<_Tp>::type&&
+    move(_Tp&& __t) noexcept
+    { return static_cast<typename std::remove_reference<_Tp>::type&&>(__t); }
+}
+
+template<typename T, typename U>
+struct same_type { static const bool value = false; };
+
+template<typename T>
+struct same_type<T, T> { static const bool value = true; };
+
+struct Widget {
+    Widget(Widget&&);
+};
+
+struct RRefTaker {
+    RRefTaker(Widget&&);
+};
+
+struct Mutt {
+    operator int*() &&;
+};
+
+struct Jeff {
+    operator int&() &&;
+};
+
+struct Ella {
+    operator int() &&;
+};
+
+Widget one(Widget w) {
+    return w;  // OK since C++11
+}
+
+RRefTaker two(Widget w) {
+    return w;  // OK since C++11 + CWG1579
+}
+
+RRefTaker three(Widget&& w) {
+    return w;  // OK since C++20 because P0527
+}
+
+// Tests that implicit move applies even to functions that return references.
+Widget&& four(Widget&& w) {
+    return w;  // OK since C++23
+}
+
+// ... or pointers.
+int* five(Mutt x) {
+    return x;  // OK since C++20 because P1155
+}
+
+int& six(Jeff x) {
+    return x;
+}
+
+int test_ella(Ella e) {
+  return e;
+}
+
+template<class T>
+T&& seven(T&& x) { return x; }
+
+void test_seven(Widget w) {
+    Widget& r = seven(w);
+    Widget&& rr = seven(std::move(w));
+}
+
+Widget val();
+Widget& lref();
+Widget&& rref();
+
+decltype(auto) eight() {
+    decltype(auto) x = val();  // OK, x is Widget
+    return x;  // OK, return type is Widget, we get copy elision
+}
+
+decltype(auto) nine() {
+    decltype(auto) x = lref();  // OK, x is Widget&
+    return x;  // OK, return type is Widget&
+}
+
+decltype(auto) ten() {
+  decltype(auto) x = rref();  // OK, x is Widget&&
+  // This was an error: return type is Widget&&, cannot bind to x.
+  // But in C++23, x is treated as an rvalue.
+  return x;
+}
+
+// Now returns Widget&&, not Widget&.
+// This is from $ 3.2.1. Interaction with decltype and decltype(auto).
+decltype(auto) eleven(Widget&& x) {
+    return (x);
+}
+static_assert(same_type<decltype(eleven), Widget&& (Widget&&)>::value);
diff --git a/gcc/testsuite/g++.dg/cpp23/elision2.C b/gcc/testsuite/g++.dg/cpp23/elision2.C
new file mode 100644
index 00000000000..ce2c7aeef66
--- /dev/null
+++ b/gcc/testsuite/g++.dg/cpp23/elision2.C
@@ -0,0 +1,46 @@
+// PR c++/101165 - P2266R1 - Simpler implicit move
+// { dg-do compile { target c++20 } }
+// Test from P2266R1, $ 3.3. Two overload resolutions are overly confusing.
+
+struct Widget {
+    Widget();
+    Widget(Widget&&);
+};
+
+struct Frodo {
+    Frodo(Widget&);
+    Frodo(Widget&&) = delete;
+};
+
+struct Sam {
+    Sam(Widget&) = delete; // #1
+    Sam(const Widget&);  // #2
+};
+
+Sam twelve() {
+    Widget w;
+    // This is supposed to calls #2 since C++20 because P1155.
+    // But we actually choose #1 since r11-2411 (in C++20 only).
+    return w; // { dg-error "deleted" "" { target c++20_only } }
+}
+
+Frodo thirteen() {
+    Widget w;
+    // This is a correct error in both C++20 and C++23.
+    return w;  // { dg-error "use of deleted function" }
+}
+
+struct Merry {};
+struct Pippin {};
+struct Together : Merry, Pippin {};
+struct Quest {
+    Quest(Merry&&);
+    Quest(Pippin&&);
+    Quest(Together&);
+};
+
+Quest fourteen() {
+  Together t;
+  // C++20: calls Quest(Together&).  Proposed: ill-formed.
+  return t; // { dg-error "ambiguous" "" { target c++23 } }
+}
diff --git a/gcc/testsuite/g++.dg/cpp23/elision3.C b/gcc/testsuite/g++.dg/cpp23/elision3.C
new file mode 100644
index 00000000000..246342e64d3
--- /dev/null
+++ b/gcc/testsuite/g++.dg/cpp23/elision3.C
@@ -0,0 +1,16 @@
+// PR c++/101165 - P2266R1 - Simpler implicit move
+// { dg-do compile { target c++23 } }
+// Test from P2266R1, $ 3.4. A specific case involving reference_wrapper.
+
+#include <functional>
+
+struct Widget {
+    Widget();
+    Widget(Widget&&);
+};
+
+std::reference_wrapper<Widget> fifteen() {
+    Widget w;
+    // OK until CWG1579; OK after LWG2993.  Proposed: ill-formed
+    return w;  // { dg-error "could not convert" }
+}
diff --git a/gcc/testsuite/g++.dg/cpp23/elision4.C b/gcc/testsuite/g++.dg/cpp23/elision4.C
new file mode 100644
index 00000000000..c19b86b8b5f
--- /dev/null
+++ b/gcc/testsuite/g++.dg/cpp23/elision4.C
@@ -0,0 +1,38 @@
+// PR c++/101165 - P2266R1 - Simpler implicit move
+// { dg-do compile { target c++23 } }
+// Test from P2266R1, $ 5.2. LibreOffice OString constructor.
+
+struct X {
+    X(auto&);
+};
+
+// The following compiles in C++20 (deducing X(char (&)[10])) but not
+// after P2266 (because the returned expression now has type char (&&)[10],
+// which cannot bind to auto&).
+X f() {
+    char a[10];
+    return a; // { dg-error "cannot bind non-const lvalue reference" }
+}
+
+// The solution was to change it by making the return convert explicitly
+// rather than implicitly:
+X fixed() {
+    char a[10];
+    return X(a);
+}
+
+// $ 5.3. LibreOffice o3tl::temporary
+
+template<class T>
+T& temporary1(T&& x) { return x; } // { dg-error "cannot bind non-const lvalue reference" }
+
+// Fixed by:
+template<class T>
+T& temporary2(T&& x) { return static_cast<T&>(x); }
+
+void
+test ()
+{
+  int& r1 = temporary1 (42);
+  int& r2 = temporary2 (42);
+}
diff --git a/gcc/testsuite/g++.dg/cpp23/elision5.C b/gcc/testsuite/g++.dg/cpp23/elision5.C
new file mode 100644
index 00000000000..a7d3e7c27c4
--- /dev/null
+++ b/gcc/testsuite/g++.dg/cpp23/elision5.C
@@ -0,0 +1,53 @@
+// PR c++/101165 - P2266R1 - Simpler implicit move
+// { dg-do compile { target c++23 } }
+// Test from [class.copy.elision]/4.
+
+class Thing {
+public:
+  Thing();
+  ~Thing();
+  Thing(Thing&&);
+private:
+  Thing(const Thing&);
+};
+
+Thing f(bool b) {
+  Thing t;
+  if (b)
+    throw t;            // OK, Thing(Thing&&) used (or elided) to throw t
+  return t;             // OK, Thing(Thing&&) used (or elided) to return t
+}
+
+Thing t2 = f(false);    // OK, no extra copy/move performed, t2 constructed by call to f
+
+struct Weird {
+  Weird();
+  Weird(Weird&);
+};
+
+Weird g(bool b) {
+  static Weird w1;
+  Weird w2;
+  if (b) {
+    return w1;  // OK: Weird(Weird&)
+  } else {
+    return w2;  // { dg-error "cannot bind non-const lvalue reference" }
+  }
+}
+
+int& h(bool b, int i) {
+  static int s;
+  if (b)
+    return s;   // OK
+  else
+    return i;   // { dg-error "cannot bind non-const lvalue reference" }
+}
+
+decltype(auto) h2(Thing t) {
+  return t;     // OK, t is an xvalue and h2's return type is Thing
+}
+
+decltype(auto) h3(Thing t) {
+  // OK, (t) is an xvalue and h3's return type is Thing&&
+  return (t); // { dg-warning "reference to local variable" }
+}
diff --git a/gcc/testsuite/g++.dg/cpp23/elision6.C b/gcc/testsuite/g++.dg/cpp23/elision6.C
new file mode 100644
index 00000000000..5d58da9e577
--- /dev/null
+++ b/gcc/testsuite/g++.dg/cpp23/elision6.C
@@ -0,0 +1,20 @@
+// PR c++/101165 - P2266R1 - Simpler implicit move
+// { dg-do compile { target c++23 } }
+// From [diff.cpp20.expr].
+
+template<typename T, typename U>
+struct same_type { static const bool value = false; };
+
+template<typename T>
+struct same_type<T, T> { static const bool value = true; };
+
+// In C++23, returns int&&; previously returned int&.
+decltype(auto) f(int&& x) { return (x); }
+static_assert(same_type<decltype(f), int&& (int&&)>::value);
+
+// This used to work in C++20.
+int& g(int&& x) { return x; } // { dg-error "cannot bind non-const lvalue reference" }
+
+template<typename T>
+decltype(auto) h(T&& x) { return (x); }
+static_assert(same_type<decltype(h(42)), int&&>::value);
diff --git a/gcc/testsuite/g++.dg/cpp23/elision7.C b/gcc/testsuite/g++.dg/cpp23/elision7.C
new file mode 100644
index 00000000000..19fa89ae133
--- /dev/null
+++ b/gcc/testsuite/g++.dg/cpp23/elision7.C
@@ -0,0 +1,72 @@
+// PR c++/101165 - P2266R1 - Simpler implicit move
+// { dg-do compile { target c++23 } }
+
+struct X {
+  X ();
+  X(X&&);
+};
+
+X&& rref ();
+
+X&&
+f1 (X&& x)
+{
+  return x;
+}
+
+template<typename T> T&&
+f2 (T&& x)
+{
+  return x;
+}
+template X& f2<X&>(X&);
+template X&& f2<X>(X&&);
+
+X&&
+f3 ()
+{
+  X&& x = rref ();
+  return x;
+}
+
+void
+f4 ()
+try {
+  X x;
+  throw x;
+} catch (...) { }
+
+void
+f5 ()
+{
+  auto l1 = [](auto x) -> auto { return x; };
+  auto &&x1 = l1(X{});
+  auto l2 = [](auto x) -> auto& { return x; }; // { dg-error "cannot bind non-const lvalue reference" }
+  auto &&x2 = l2(X{});
+  auto l3 = [](auto x) -> auto&& { return x; }; // { dg-warning "reference to local" }
+  auto &&x3 = l3(X{});
+}
+
+constexpr int &
+f6 (int &&n)
+{
+  return n; // { dg-error "cannot bind non-const lvalue reference" }
+}
+
+void
+do_f6 ()
+{
+  auto x = f6 (42);
+}
+
+template<typename T> auto &
+f7 (T &&t)
+{
+  return t; // { dg-error "cannot bind non-const lvalue reference" }
+}
+
+void
+do_f7 ()
+{
+  const int &x = f7 (0);
+}
diff --git a/gcc/testsuite/g++.dg/cpp23/feat-cxx2b.C b/gcc/testsuite/g++.dg/cpp23/feat-cxx2b.C
index 2f6b21ead70..b52cf378b41 100644
--- a/gcc/testsuite/g++.dg/cpp23/feat-cxx2b.C
+++ b/gcc/testsuite/g++.dg/cpp23/feat-cxx2b.C
@@ -569,3 +569,9 @@
 #elif __cpp_static_call_operator != 202207
 #  error "__cpp_static_call_operator != 202207"
 #endif
+
+#ifndef __cpp_implicit_move
+#  error "__cpp_implicit_move"
+#elif __cpp_implicit_move != 202207
+#  error "__cpp_implicit_move != 202207"
+#endif
diff --git a/gcc/testsuite/g++.dg/gomp/pr56217.C b/gcc/testsuite/g++.dg/gomp/pr56217.C
index 03dfc5f180b..731c0c08811 100644
--- a/gcc/testsuite/g++.dg/gomp/pr56217.C
+++ b/gcc/testsuite/g++.dg/gomp/pr56217.C
@@ -1,5 +1,5 @@
 // PR middle-end/56217
-// { dg-do compile }
+// { dg-do compile { target c++20_down } }
 // { dg-options "-fopenmp" }
 
 struct S { int *p; S (); S (S &); };
@@ -10,5 +10,7 @@ foo ()
   S s;
   #pragma omp task shared (s)
     s.p = 0;
+  // This fails in C++23, because "cannot bind non-const lvalue reference of
+  // type 'S&' to an rvalue of type 'S'".
   return s;
 }
diff --git a/gcc/testsuite/g++.dg/warn/Wno-return-local-addr.C b/gcc/testsuite/g++.dg/warn/Wno-return-local-addr.C
index e15bfa24f54..cc9bb59770e 100644
--- a/gcc/testsuite/g++.dg/warn/Wno-return-local-addr.C
+++ b/gcc/testsuite/g++.dg/warn/Wno-return-local-addr.C
@@ -4,7 +4,7 @@
 int& bad1()
 {
   int x = 0;
-  return x;
+  return x; // { dg-error "cannot bind non-const lvalue reference" "" { target c++23 } }
 }
 
 int* bad2()
diff --git a/gcc/testsuite/g++.dg/warn/Wreturn-local-addr.C b/gcc/testsuite/g++.dg/warn/Wreturn-local-addr.C
index 642a5767e84..4c18c2f06a0 100644
--- a/gcc/testsuite/g++.dg/warn/Wreturn-local-addr.C
+++ b/gcc/testsuite/g++.dg/warn/Wreturn-local-addr.C
@@ -5,7 +5,7 @@
 int& bad1()
 {
   int x = 0;
-  return x;		// { dg-error "reference to local variable" }
+  return x;		// { dg-error "reference to local variable|cannot bind non-const lvalue reference" }
 }
 
 int* bad2()
diff --git a/gcc/testsuite/g++.old-deja/g++.brendan/crash55.C b/gcc/testsuite/g++.old-deja/g++.brendan/crash55.C
index fd4d4b65edb..b93e6e0c695 100644
--- a/gcc/testsuite/g++.old-deja/g++.brendan/crash55.C
+++ b/gcc/testsuite/g++.old-deja/g++.brendan/crash55.C
@@ -8,5 +8,6 @@
 
           local = x+2;
       
-          return local; // { dg-warning "reference to local" }
+          return local; // { dg-warning "reference to local" "" { target c++20_down } }
+// { dg-error "non-const lvalue" "" { target c++23 } .-1 }
       }
diff --git a/gcc/testsuite/g++.old-deja/g++.jason/temporary2.C b/gcc/testsuite/g++.old-deja/g++.jason/temporary2.C
index c855f8f4a07..2709b50e7f1 100644
--- a/gcc/testsuite/g++.old-deja/g++.jason/temporary2.C
+++ b/gcc/testsuite/g++.old-deja/g++.jason/temporary2.C
@@ -8,7 +8,7 @@ public:
   int i;
 };
 
-X foo() { X x; return x; }
+X foo() { X x; return x; } // { dg-error "cannot bind non-const lvalue reference" "" { target c++23 } }
 
 int main() 
 {
diff --git a/gcc/testsuite/g++.old-deja/g++.mike/p2846b.C b/gcc/testsuite/g++.old-deja/g++.mike/p2846b.C
index 57422fe64df..68826649cfc 100644
--- a/gcc/testsuite/g++.old-deja/g++.mike/p2846b.C
+++ b/gcc/testsuite/g++.old-deja/g++.mike/p2846b.C
@@ -1,4 +1,4 @@
-// { dg-do run  }
+// { dg-do run { target c++20_down } }
 // Shows that problem of initializing one object's secondary base from
 // another object via a user defined copy constructor for that base,
 // the pointer for the secondary vtable is not set after implicit
@@ -11,6 +11,8 @@
 
 // prms-id: 2846
 
+// This test fails in C++23 due to P2266.
+
 extern "C" int printf(const char *, ...);
 extern "C" void exit(int);
 

base-commit: e73d9fcafbd07bc3714fbaf8a82db71d50015c92
-- 
2.37.3


^ permalink raw reply	[flat|nested] 10+ messages in thread

* Re: [PATCH v3] c++: Implement C++23 P2266R1, Simpler implicit move [PR101165]
  2022-09-27 20:26       ` [PATCH v3] " Marek Polacek
@ 2022-09-27 21:44         ` Jason Merrill
  2022-09-27 23:39           ` Marek Polacek
  0 siblings, 1 reply; 10+ messages in thread
From: Jason Merrill @ 2022-09-27 21:44 UTC (permalink / raw)
  To: Marek Polacek; +Cc: GCC Patches

On 9/27/22 16:26, Marek Polacek wrote:
> On Mon, Sep 26, 2022 at 01:29:35PM -0400, Jason Merrill wrote:
>> On 9/20/22 14:19, Marek Polacek wrote:
>>>>> There's one FIXME in elision1.C:five, which we should compile but reject
>>>>> with "passing 'Mutt' as 'this' argument discards qualifiers".  That
>>>>> looks bogus to me, I think I'll open a PR for it.
>>>>
>>>> Let's fix that now, I think.
>>>
>>> OK, copypasting this bit from the other email so that we can have one
>>> thread:
>>>
>>>> Can of worms.   The test is
>>>>
>>>>      struct Mutt {
>>>>          operator int*() &&;
>>>>      };
>>>>
>>>>      int* five(Mutt x) {
>>>>          return x;  // OK since C++20 because P1155
>>>>      }
>>>>
>>>> 'x' should be treated as an rvalue, therefore the operator fn taking
>>>> an rvalue ref to Mutt should be used to convert 'x' to int*.  We fail
>>>> because we don't treat 'x' as an rvalue because the function doesn't
>>>> return a class.  So the patch should be just
>>>>
>>>> --- a/gcc/cp/typeck.cc
>>>> +++ b/gcc/cp/typeck.cc
>>>> @@ -10875,10 +10875,7 @@ check_return_expr (tree retval, bool *no_warning)
>>>>              Note that these conditions are similar to, but not as strict as,
>>>>         the conditions for the named return value optimization.  */
>>>>           bool converted = false;
>>>> -      tree moved;
>>>> -      /* This is only interesting for class type.  */
>>>> -      if (CLASS_TYPE_P (functype)
>>>> -     && (moved = treat_lvalue_as_rvalue_p (retval, /*return*/true)))
>>>> +      if (tree moved = treat_lvalue_as_rvalue_p (retval, /*return*/true))
>>>>        {
>>>>          if (cxx_dialect < cxx20)
>>>>            {
>>>>
>>>> which fixes the test, but breaks a lot of middle-end warnings.  For instance
>>>> g++.dg/warn/nonnull3.C, where the patch above changes .gimple:
>>>>
>>>>     bool A::foo<B> (struct A * const this, <<< Unknown tree: offset_type >>> p)
>>>>     {
>>>> -  bool D.2146;
>>>> +  bool D.2150;
>>>> -  D.2146 = p != -1;
>>>> -  return D.2146;
>>>> +  p.0_1 = p;
>>>> +  D.2150 = p.0_1 != -1;
>>>> +  return D.2150;
>>>>     }
>>>>
>>>> and we no longer get the warning.  I thought maybe I could undo the implicit
>>>> rvalue conversion in cp_fold, when it sees implicit_rvalue_p, but that didn't
>>>> work.  So currently I'm stuck.  Should we try to figure this out or push aside?
>>>
>>>> Can you undo the implicit rvalue conversion within check_return_expr,
>>>> where we can still refer back to the original expression?
>>>
>>> Unfortunately no, one problem is that treat_lvalue_as_rvalue_p modifies
>>> the underlying decl by setting TREE_ADDRESSABLE, which then presumably
>>> breaks warnings.  That is, treat_ can get 'VCE<X>(x)' and produce
>>> '*NLE<(X&) &x>' where 'x' flags have been modified, since we're taking
>>> x's address.
>>>
>>>> Or avoid the rvalue conversion if the return type is scalar?
>>>
>>> I wish :(.  In the 'five' example above, the return type is a pointer,
>>> a scalar, but we have to convert to rvalue.
>>
>> OK, then when both the return type and the type of the return value are
>> scalar?
> 
> Good news!  After more poking it seems we only need to do the rvalue
> conversion when either to/from types is a class/reference!  That is,
> if either is a non-scalar type.  And that doesn't upset the middle-end
> diagnostics!  I'm still limiting the broader conversion to C++23, but
> the whole condition could be:
> 
>    if ((!SCALAR_TYPE_P (functype) || !SCALAR_TYPE_P (rettype))
>        && treat_lvalue_as_rvalue_p ())
>    ...
>   
> Therefore I think we don't need ...
> 
>>> It's sort of sad that this corner case causes so much trouble: I think
>>> we have to do the conversion only because of ref-qualifiers, so that
>>> the correct operator function is chosen.
>>>
>>> A way out may be setting a flag on the V_C_E that indicates it is an
>>> rvalue, rather than performing the conversion above.  This was your
>>> idea so I don't want to take credit for it.  Should I go ahead and
>>> try it?
>>
>> Sure, probably in build_static_cast_1.
> 
> ... this, after all, which is just fantastic.  Besides the check_return_expr
> hunk and removing a FIXME there are no other changes.

Yay!

> Next step: remove the double overload.
> 
> Bootstrapped/regtested on x86_64-pc-linux-gnu, ok for trunk?
> 
> -- >8 --
> This patch implements https://wg21.link/p2266, which, once again,
> changes the implicit move rules.  Here's a brief summary of various
> changes in this area:
> 
> r125211: Introduced moving from certain lvalues when returning them
> r171071: CWG 1148, enable move from value parameter on return
> r212099: CWG 1579, it's OK to call a converting ctor taking an rvalue
> r251035: CWG 1579, do maybe-rvalue overload resolution twice
> r11-2411: Avoid calling const copy ctor on implicit move
> r11-2412: C++20 implicit move changes, remove the fallback overload
>            resolution, allow move on throw of parameters and implicit
> 	  move of rvalue references
> 
> P2266 enables the implicit move even for functions that return references.
> That is, we will now perform a move in
> 
>    X&& foo (X&& x) {
>      return x;
>    }
> 
> P2266 also removes the fallback overload resolution, but this was
> resolved by r11-2412: we only do convert_for_initialization with
> LOOKUP_PREFER_RVALUE in C++17 and older.
> P2266 also says that a returned move-eligible id-expression is always an
> xvalue.  This required some further short, but nontrivial changes,
> especially when it comes to deduction, because we have to pay attention
> to whether we have auto, auto&& (which is like T&&), or decltype(auto)
> with (un)parenthesized argument.  In C++23,
> 
>    decltype(auto) f(int&& x) { return (x); }
>    auto&& f(int x) { return x; }
> 
> both should deduce to 'int&&' but
> 
>    decltype(auto) f(int x) { return x; }
> 
> should deduce to 'int'.  A cornucopia of tests attached.  I've also
> verified that we behave like clang++.
> 
> xvalue_p seemed to be broken: since the introduction of clk_implicit_rval,
> it cannot use '==' when checking for clk_rvalueref.
> 
> Since this change breaks code, it's only enabled in C++23.  In
> particular, this code will not compile in C++23:
> 
>    int& g(int&& x) { return x; }
> 
> because x is now treated as an rvalue, and you can't bind a non-const lvalue
> reference to an rvalue.
> 
> This patch also fixes PR106882 (the check_return_expr changes).
> 
> 	PR c++/101165
> 	PR c++/106882
> 
> gcc/c-family/ChangeLog:
> 
> 	* c-cppbuiltin.cc (c_cpp_builtins): Define __cpp_implicit_move.
> 
> gcc/cp/ChangeLog:
> 
> 	* call.cc (reference_binding): Check clk_implicit_rval in C++20 only.
> 	* cp-tree.h (unparenthesized_id_or_class_member_access_p): Declare.
> 	* pt.cc (unparenthesized_id_or_class_member_access_p): New function,
> 	broken out of...
> 	(do_auto_deduction): ...here.  Use it.  In C++23, maybe call
> 	treat_lvalue_as_rvalue_p.
> 	* tree.cc (xvalue_p): Check & clk_rvalueref, not == clk_rvalueref.
> 	* typeck.cc (check_return_expr): Allow implicit move for functions
> 	returning a reference as well, or when the return value type is not
> 	a scalar type.
> 
> gcc/testsuite/ChangeLog:
> 
> 	* g++.dg/conversion/pr41426.C: Add dg-error for C++23.
> 	* g++.dg/cpp0x/elision_weak.C: Likewise.
> 	* g++.dg/cpp0x/move-return3.C: Only link in c++20_down.
> 	* g++.dg/cpp1y/decltype-auto2.C: Add dg-error for C++23.
> 	* g++.dg/cpp1y/lambda-generic-89419.C: Likewise.
> 	* g++.dg/cpp23/feat-cxx2b.C: Test __cpp_implicit_move.
> 	* g++.dg/gomp/pr56217.C: Only compile in c++20_down.
> 	* g++.dg/warn/Wno-return-local-addr.C: Add dg-error for C++23.
> 	* g++.dg/warn/Wreturn-local-addr.C: Adjust dg-error.
> 	* g++.old-deja/g++.brendan/crash55.C: Add dg-error for C++23.
> 	* g++.old-deja/g++.jason/temporary2.C: Likewise.
> 	* g++.old-deja/g++.mike/p2846b.C: Only run in c++20_down.
> 	* g++.dg/cpp1y/decltype-auto6.C: New test.
> 	* g++.dg/cpp23/decltype1.C: New test.
> 	* g++.dg/cpp23/decltype2.C: New test.
> 	* g++.dg/cpp23/elision1.C: New test.
> 	* g++.dg/cpp23/elision2.C: New test.
> 	* g++.dg/cpp23/elision3.C: New test.
> 	* g++.dg/cpp23/elision4.C: New test.
> 	* g++.dg/cpp23/elision5.C: New test.
> 	* g++.dg/cpp23/elision6.C: New test.
> 	* g++.dg/cpp23/elision7.C: New test.
> ---
>   gcc/c-family/c-cppbuiltin.cc                  |   1 +
>   gcc/cp/call.cc                                |   6 +-
>   gcc/cp/cp-tree.h                              |   1 +
>   gcc/cp/pt.cc                                  |  50 ++++++--
>   gcc/cp/tree.cc                                |   2 +-
>   gcc/cp/typeck.cc                              |   9 +-
>   gcc/testsuite/g++.dg/conversion/pr41426.C     |  10 +-
>   gcc/testsuite/g++.dg/cpp0x/elision_weak.C     |   4 +-
>   gcc/testsuite/g++.dg/cpp0x/move-return3.C     |   3 +-
>   gcc/testsuite/g++.dg/cpp1y/decltype-auto2.C   |   2 +-
>   gcc/testsuite/g++.dg/cpp1y/decltype-auto6.C   |  19 +++
>   .../g++.dg/cpp1y/lambda-generic-89419.C       |   6 +-
>   gcc/testsuite/g++.dg/cpp23/decltype1.C        | 113 +++++++++++++++++
>   gcc/testsuite/g++.dg/cpp23/decltype2.C        |  49 ++++++++
>   gcc/testsuite/g++.dg/cpp23/elision1.C         | 114 ++++++++++++++++++
>   gcc/testsuite/g++.dg/cpp23/elision2.C         |  46 +++++++
>   gcc/testsuite/g++.dg/cpp23/elision3.C         |  16 +++
>   gcc/testsuite/g++.dg/cpp23/elision4.C         |  38 ++++++
>   gcc/testsuite/g++.dg/cpp23/elision5.C         |  53 ++++++++
>   gcc/testsuite/g++.dg/cpp23/elision6.C         |  20 +++
>   gcc/testsuite/g++.dg/cpp23/elision7.C         |  72 +++++++++++
>   gcc/testsuite/g++.dg/cpp23/feat-cxx2b.C       |   6 +
>   gcc/testsuite/g++.dg/gomp/pr56217.C           |   4 +-
>   .../g++.dg/warn/Wno-return-local-addr.C       |   2 +-
>   .../g++.dg/warn/Wreturn-local-addr.C          |   2 +-
>   .../g++.old-deja/g++.brendan/crash55.C        |   3 +-
>   .../g++.old-deja/g++.jason/temporary2.C       |   2 +-
>   gcc/testsuite/g++.old-deja/g++.mike/p2846b.C  |   4 +-
>   28 files changed, 622 insertions(+), 35 deletions(-)
>   create mode 100644 gcc/testsuite/g++.dg/cpp1y/decltype-auto6.C
>   create mode 100644 gcc/testsuite/g++.dg/cpp23/decltype1.C
>   create mode 100644 gcc/testsuite/g++.dg/cpp23/decltype2.C
>   create mode 100644 gcc/testsuite/g++.dg/cpp23/elision1.C
>   create mode 100644 gcc/testsuite/g++.dg/cpp23/elision2.C
>   create mode 100644 gcc/testsuite/g++.dg/cpp23/elision3.C
>   create mode 100644 gcc/testsuite/g++.dg/cpp23/elision4.C
>   create mode 100644 gcc/testsuite/g++.dg/cpp23/elision5.C
>   create mode 100644 gcc/testsuite/g++.dg/cpp23/elision6.C
>   create mode 100644 gcc/testsuite/g++.dg/cpp23/elision7.C
> 
> diff --git a/gcc/c-family/c-cppbuiltin.cc b/gcc/c-family/c-cppbuiltin.cc
> index ca5f500e079..d4de5a0dc57 100644
> --- a/gcc/c-family/c-cppbuiltin.cc
> +++ b/gcc/c-family/c-cppbuiltin.cc
> @@ -1082,6 +1082,7 @@ c_cpp_builtins (cpp_reader *pfile)
>   	  cpp_define (pfile, "__cpp_multidimensional_subscript=202110L");
>   	  cpp_define (pfile, "__cpp_named_character_escapes=202207L");
>   	  cpp_define (pfile, "__cpp_static_call_operator=202207L");
> +	  cpp_define (pfile, "__cpp_implicit_move=202207L");
>   	}
>         if (flag_concepts)
>           {
> diff --git a/gcc/cp/call.cc b/gcc/cp/call.cc
> index fc86b74a5a4..3506b0fcfbb 100644
> --- a/gcc/cp/call.cc
> +++ b/gcc/cp/call.cc
> @@ -1880,8 +1880,10 @@ reference_binding (tree rto, tree rfrom, tree expr, bool c_cast_p, int flags,
>   
>         /* Nor the reverse.  */
>         if (!is_lvalue && !TYPE_REF_IS_RVALUE (rto)
> -	  /* Unless it's really an lvalue.  */
> -	  && !(cxx_dialect >= cxx20
> +	  /* Unless it's really a C++20 lvalue being treated as an xvalue.
> +	     But in C++23, such an expression is just an xvalue, not a special
> +	     lvalue, so the binding is once again ill-formed.  */
> +	  && !(cxx_dialect == cxx20
>   	       && (gl_kind & clk_implicit_rval))
>   	  && (!CP_TYPE_CONST_NON_VOLATILE_P (to)
>   	      || (flags & LOOKUP_NO_RVAL_BIND))
> diff --git a/gcc/cp/cp-tree.h b/gcc/cp/cp-tree.h
> index 99b486b8002..19bbfbc557f 100644
> --- a/gcc/cp/cp-tree.h
> +++ b/gcc/cp/cp-tree.h
> @@ -7292,6 +7292,7 @@ extern tree make_constrained_decltype_auto	(tree, tree);
>   extern tree make_template_placeholder		(tree);
>   extern bool template_placeholder_p		(tree);
>   extern bool ctad_template_p			(tree);
> +extern bool unparenthesized_id_or_class_member_access_p (tree);
>   extern tree do_auto_deduction                   (tree, tree, tree,
>                                                    tsubst_flags_t
>   						 = tf_warning_or_error,
> diff --git a/gcc/cp/pt.cc b/gcc/cp/pt.cc
> index 1c1e5735743..2d83dfd6954 100644
> --- a/gcc/cp/pt.cc
> +++ b/gcc/cp/pt.cc
> @@ -30408,6 +30408,26 @@ do_class_deduction (tree ptype, tree tmpl, tree init,
>   				  cp_type_quals (ptype));
>   }
>   
> +/* Return true if INIT is an unparenthesized id-expression or an
> +   unparenthesized class member access.  Used for the argument of
> +   decltype(auto).  */
> +
> +bool
> +unparenthesized_id_or_class_member_access_p (tree init)
> +{
> +  STRIP_ANY_LOCATION_WRAPPER (init);
> +
> +  /* We need to be able to tell '(r)' and 'r' apart (when it's of
> +     reference type).  Only the latter is an id-expression.  */
> +  if (REFERENCE_REF_P (init)
> +      && !REF_PARENTHESIZED_P (init))
> +    init = TREE_OPERAND (init, 0);
> +  return (DECL_P (init)
> +	  || ((TREE_CODE (init) == COMPONENT_REF
> +	       || TREE_CODE (init) == SCOPE_REF)
> +	      && !REF_PARENTHESIZED_P (init)));
> +}
> +
>   /* Replace occurrences of 'auto' in TYPE with the appropriate type deduced
>      from INIT.  AUTO_NODE is the TEMPLATE_TYPE_PARM used for 'auto' in TYPE.
>      The CONTEXT determines the context in which auto deduction is performed
> @@ -30443,6 +30463,23 @@ do_auto_deduction (tree type, tree init, tree auto_node,
>        auto_node.  */
>     complain &= ~tf_partial;
>   
> +  /* In C++23, we must deduce the type to int&& for code like
> +       decltype(auto) f(int&& x) { return (x); }
> +     or
> +       auto&& f(int x) { return x; }
> +     so we use treat_lvalue_as_rvalue_p.  But don't do it for
> +       decltype(auto) f(int x) { return x; }
> +     where we should deduce 'int' rather than 'int&&'; transmogrifying
> +     INIT to an rvalue would break that.  */
> +  tree r;
> +  if (cxx_dialect >= cxx23
> +      && context == adc_return_type
> +      && (!AUTO_IS_DECLTYPE (auto_node)
> +	  || !unparenthesized_id_or_class_member_access_p (init))
> +      && (r = treat_lvalue_as_rvalue_p (maybe_undo_parenthesized_ref (init),
> +					/*return*/true)))
> +    init = r;
> +
>     if (tree tmpl = CLASS_PLACEHOLDER_TEMPLATE (auto_node))
>       /* C++17 class template argument deduction.  */
>       return do_class_deduction (type, tmpl, init, flags, complain);
> @@ -30504,18 +30541,7 @@ do_auto_deduction (tree type, tree init, tree auto_node,
>       }
>     else if (AUTO_IS_DECLTYPE (auto_node))
>       {
> -      /* Figure out if INIT is an unparenthesized id-expression or an
> -	 unparenthesized class member access.  */
> -      tree stripped_init = tree_strip_any_location_wrapper (init);
> -      /* We need to be able to tell '(r)' and 'r' apart (when it's of
> -	 reference type).  Only the latter is an id-expression.  */
> -      if (REFERENCE_REF_P (stripped_init)
> -	  && !REF_PARENTHESIZED_P (stripped_init))
> -	stripped_init = TREE_OPERAND (stripped_init, 0);
> -      const bool id = (DECL_P (stripped_init)
> -		       || ((TREE_CODE (stripped_init) == COMPONENT_REF
> -			    || TREE_CODE (stripped_init) == SCOPE_REF)
> -			   && !REF_PARENTHESIZED_P (stripped_init)));
> +      const bool id = unparenthesized_id_or_class_member_access_p (init);
>         tree deduced = finish_decltype_type (init, id, complain);
>         deduced = canonicalize_type_argument (deduced, complain);
>         if (deduced == error_mark_node)
> diff --git a/gcc/cp/tree.cc b/gcc/cp/tree.cc
> index d0bd41ae5a0..ea4dfc651bb 100644
> --- a/gcc/cp/tree.cc
> +++ b/gcc/cp/tree.cc
> @@ -382,7 +382,7 @@ obvalue_p (const_tree ref)
>   bool
>   xvalue_p (const_tree ref)
>   {
> -  return (lvalue_kind (ref) == clk_rvalueref);
> +  return (lvalue_kind (ref) & clk_rvalueref);
>   }
>   
>   /* True if REF is a bit-field.  */
> diff --git a/gcc/cp/typeck.cc b/gcc/cp/typeck.cc
> index 4854b983765..771331b6df9 100644
> --- a/gcc/cp/typeck.cc
> +++ b/gcc/cp/typeck.cc
> @@ -11042,8 +11042,13 @@ check_return_expr (tree retval, bool *no_warning)
>   	 the conditions for the named return value optimization.  */
>         bool converted = false;
>         tree moved;
> -      /* This is only interesting for class type.  */
> -      if (CLASS_TYPE_P (functype)
> +      /* Until C++23, this was only interesting for class type...  */
> +      if ((CLASS_TYPE_P (functype)
> +	   /* ...but in C++23, we should do the below when we're converting
> +	      from/to a class/reference (a non-scalar type).  */
> +	   || (cxx_dialect >= cxx23
> +	       && (!SCALAR_TYPE_P (functype)
> +		   || !SCALAR_TYPE_P (TREE_TYPE (retval)))))

You might reformat this as
(cxx_dialect < cxx23
  ? CLASS...
  : (!SCALAR...

>   	  && (moved = treat_lvalue_as_rvalue_p (retval, /*return*/true)))
>   	{
>   	  if (cxx_dialect < cxx20)
> diff --git a/gcc/testsuite/g++.dg/conversion/pr41426.C b/gcc/testsuite/g++.dg/conversion/pr41426.C
> index 5493a91ecfa..b4ecbca5f3a 100644
> --- a/gcc/testsuite/g++.dg/conversion/pr41426.C
> +++ b/gcc/testsuite/g++.dg/conversion/pr41426.C
> @@ -11,19 +11,20 @@ struct A
>   A<float> g1()
>   {
>      float f[] = {1.1f, 2.3f};
> -   return f;
> +   return f; // { dg-error "cannot bind non-const" "" { target c++23 } }
>   }
>   
>   const A<float> &g3()
>   {
>      float f[] = {1.1f, 2.3f};
> -   return f; // { dg-warning "returning reference to temporary" }
> +   return f; // { dg-warning "returning reference to temporary" "" { target c++20_down } }
> +// { dg-error "non-const lvalue|invalid user-defined conversion" "" { target c++23 } .-1 }
>   }
>   
>   A<float> &g4()
>   {
>      float f[] = {1.1f, 2.3f};
> -   return f; // { dg-error "cannot bind non-const lvalue ref" }
> +   return f; // { dg-error "cannot bind non-const lvalue ref|invalid user-defined conversion" }
>   }
>   
>   struct B
> @@ -35,6 +36,5 @@ struct B
>   B g2()
>   {
>      int c[10];
> -   return c;
> +   return c; // { dg-error "non-const lvalue" "" { target c++23 } }
>   }
> -
> diff --git a/gcc/testsuite/g++.dg/cpp0x/elision_weak.C b/gcc/testsuite/g++.dg/cpp0x/elision_weak.C
> index e8ba7551d84..ddd12743130 100644
> --- a/gcc/testsuite/g++.dg/cpp0x/elision_weak.C
> +++ b/gcc/testsuite/g++.dg/cpp0x/elision_weak.C
> @@ -9,11 +9,11 @@ struct S
>   S f()
>   {
>     S s;
> -  return s;
> +  return s; // { dg-error "cannot bind non-const lvalue reference" "" { target c++23 } }
>   }
>   
>   void g()
>   {
>     S s;
> -  throw s;
> +  throw s; // { dg-error "cannot bind non-const lvalue reference" "" { target c++23 } }
>   }
> diff --git a/gcc/testsuite/g++.dg/cpp0x/move-return3.C b/gcc/testsuite/g++.dg/cpp0x/move-return3.C
> index c79f0591936..30a936fb35a 100644
> --- a/gcc/testsuite/g++.dg/cpp0x/move-return3.C
> +++ b/gcc/testsuite/g++.dg/cpp0x/move-return3.C
> @@ -1,6 +1,7 @@
>   // PR c++/91212
>   // Test that C++11 implicit move semantics don't call the const copy.
> -// { dg-do link }
> +// In C++23, we call #2.

I guess that behavior is tested by elision2.C:twelve()?

> +// { dg-do link { target c++20_down } }
>   
>   struct T { int i; };
>   
> diff --git a/gcc/testsuite/g++.dg/cpp1y/decltype-auto2.C b/gcc/testsuite/g++.dg/cpp1y/decltype-auto2.C
> index 56e011e36f4..24b32edfacf 100644
> --- a/gcc/testsuite/g++.dg/cpp1y/decltype-auto2.C
> +++ b/gcc/testsuite/g++.dg/cpp1y/decltype-auto2.C
> @@ -8,5 +8,5 @@ auto constexpr RtoL1(T&& r) -> decltype(auto) {
>   int main() {
>       int t;
>       int x{3};
> -    decltype (RtoL1(x+0)) y = t;
> +    decltype (RtoL1(x+0)) y = t; // { dg-error "cannot bind rvalue reference" "" { target c++23 } }
>   }
> diff --git a/gcc/testsuite/g++.dg/cpp1y/decltype-auto6.C b/gcc/testsuite/g++.dg/cpp1y/decltype-auto6.C
> new file mode 100644
> index 00000000000..da53278645c
> --- /dev/null
> +++ b/gcc/testsuite/g++.dg/cpp1y/decltype-auto6.C
> @@ -0,0 +1,19 @@
> +// PR c++/101165 - P2266R1 - Simpler implicit move
> +// { dg-do compile { target c++14 } }
> +// A variant of cxx23/elision1.C:eight, just with ().
> +
> +struct Widget {
> +  Widget(Widget&&);
> +};
> +
> +Widget val();
> +
> +decltype(auto)
> +foo ()
> +{
> +  decltype(auto) x = val();  // OK, x is Widget
> +  // We deduce the return type to int&&, therefore we're doing something
> +  // we ought not to be doing -- returning a reference to a local variable!
> +  // In C++20, we deduce to int&, but that has the same problem!
> +  return (x); // { dg-warning "reference to local variable" }
> +}
> diff --git a/gcc/testsuite/g++.dg/cpp1y/lambda-generic-89419.C b/gcc/testsuite/g++.dg/cpp1y/lambda-generic-89419.C
> index 46ce909f3b8..8e64d4e64ab 100644
> --- a/gcc/testsuite/g++.dg/cpp1y/lambda-generic-89419.C
> +++ b/gcc/testsuite/g++.dg/cpp1y/lambda-generic-89419.C
> @@ -2,7 +2,7 @@
>   // { dg-do compile { target c++14 } }
>   
>   struct A;
> -struct B {
> +struct B { // { dg-error "cannot bind" "" { target c++23 } }
>     struct C { C (); C (C &); } b;
>   };
>   struct D { A operator* (); };
> @@ -13,12 +13,12 @@ struct E {
>     auto bar () { return e; }
>     D e;
>   };
> -struct F { B f; int g; };
> +struct F { B f; int g; }; // { dg-error "use of deleted function" "" { target c++23 } }
>   
>   int
>   main ()
>   {
>     E e;
>     auto f = *e.bar ();
> -  auto i = [&] { F g; g.g = 1; auto h = [&](auto) { g.g = 0; }; f.foo (h); return g; };
> +  auto i = [&] { F g; g.g = 1; auto h = [&](auto) { g.g = 0; }; f.foo (h); return g; }; // { dg-error "use of deleted function" "" { target c++23 } }
>   }
> diff --git a/gcc/testsuite/g++.dg/cpp23/decltype1.C b/gcc/testsuite/g++.dg/cpp23/decltype1.C
> new file mode 100644
> index 00000000000..6f3cd0d45d5
> --- /dev/null
> +++ b/gcc/testsuite/g++.dg/cpp23/decltype1.C
> @@ -0,0 +1,113 @@
> +// PR c++/101165 - P2266R1 - Simpler implicit move
> +// { dg-do compile { target c++23 } }
> +// Tests from P2266R1, decltype-related changes in
> +// $ 3.2.1. Interaction with decltype and decltype(auto)
> +
> +template<typename T, typename U>
> +struct same_type { static const bool value = false; };
> +
> +template<typename T>
> +struct same_type<T, T> { static const bool value = true; };
> +
> +auto f1(int x) -> decltype(x) { return (x); }
> +static_assert(same_type<decltype(f1), int (int)>::value);
> +auto f2(int x) -> decltype((x)) { return (x); }   // { dg-error "cannot bind" }
> +static_assert(same_type<decltype(f2), int& (int)>::value);
> +auto f3(int x) -> decltype(auto) { return (x); }  // { dg-warning "reference to local variable" }
> +static_assert(same_type<decltype(f3), int&& (int)>::value);
> +auto g1(int x) -> decltype(x) { return x; }
> +static_assert(same_type<decltype(g1), int (int)>::value);
> +auto g2(int x) -> decltype((x)) { return x; }	  // { dg-error "cannot bind" }
> +static_assert(same_type<decltype(g2), int& (int)>::value);
> +auto g3(int x) -> decltype(auto) { return x; }
> +static_assert(same_type<decltype(g3), int (int)>::value);
> +
> +// Note that f2 and g2 are well-formed in C++20, but we propose to make
> +// f2 and g2 ill-formed, because they attempt to bind an lvalue reference
> +// to a move-eligible xvalue expression.
> +
> +struct X { };
> +
> +auto
> +f4 (X x)
> +{
> +  return x;
> +}
> +static_assert(same_type<decltype(f4), X(X)>::value);
> +
> +auto&
> +f5 (X x)
> +{
> +  return x; // { dg-error "cannot bind non-const lvalue reference" }
> +}
> +static_assert(same_type<decltype(f5), X&(X)>::value);
> +
> +auto&&
> +f6 (X x)
> +{
> +  return x; // { dg-warning "reference to local variable" }
> +}
> +static_assert(same_type<decltype(f6), X&&(X)>::value);
> +
> +auto
> +f7 (X x)
> +{
> +  return (x);
> +}
> +static_assert(same_type<decltype(f7), X(X)>::value);
> +
> +auto&
> +f8 (X x)
> +{
> +  return (x); // { dg-error "cannot bind non-const lvalue reference" }
> +}
> +static_assert(same_type<decltype(f8), X&(X)>::value);
> +
> +auto&&
> +f9 (X x)
> +{
> +  return (x); // { dg-warning "reference to local variable" }
> +}
> +static_assert(same_type<decltype(f9), X&&(X)>::value);
> +
> +decltype(auto)
> +f10 (X x)
> +{
> +  return x;
> +}
> +static_assert(same_type<decltype(f10), X(X)>::value);
> +
> +decltype(auto)
> +f11 (X x)
> +{
> +  return (x); // { dg-warning "reference to local variable" }
> +}
> +static_assert(same_type<decltype(f11), X&&(X)>::value);
> +
> +decltype(auto)
> +f12 (X& x)
> +{
> +  return x;
> +}
> +static_assert(same_type<decltype(f12), X&(X&)>::value);
> +
> +decltype(auto)
> +f13 (X& x)
> +{
> +  return (x);
> +}
> +static_assert(same_type<decltype(f13), X&(X&)>::value);
> +
> +decltype(auto)
> +f14 (X&& x)
> +{
> +  return x;
> +}
> +static_assert(same_type<decltype(f14), X&&(X&&)>::value);
> +
> +decltype(auto)
> +f15 (X&& x)
> +{
> +  return (x);
> +}
> +static_assert(same_type<decltype(f15), X&&(X&&)>::value);
> diff --git a/gcc/testsuite/g++.dg/cpp23/decltype2.C b/gcc/testsuite/g++.dg/cpp23/decltype2.C
> new file mode 100644
> index 00000000000..84679c48f82
> --- /dev/null
> +++ b/gcc/testsuite/g++.dg/cpp23/decltype2.C
> @@ -0,0 +1,49 @@
> +// PR c++/101165 - P2266R1 - Simpler implicit move
> +// { dg-do compile { target c++23 } }
> +// Test decltype(auto) more.
> +
> +template<typename T, typename U>
> +struct same_type { static const bool value = false; };
> +
> +template<typename T>
> +struct same_type<T, T> { static const bool value = true; };
> +
> +struct Widget {
> +  int x;
> +};
> +
> +Widget wg;
> +
> +decltype(auto) fn0(Widget&& x) {
> +    return (::wg);
> +}
> +static_assert(same_type<decltype(fn0), Widget& (Widget&&)>::value);
> +
> +decltype(auto) fn1(Widget&& x) {
> +    return ::wg;
> +}
> +static_assert(same_type<decltype(fn1), Widget (Widget&&)>::value);
> +
> +decltype(auto) fn2() {
> +    Widget w;
> +    return w;
> +}
> +static_assert(same_type<decltype(fn2), Widget ()>::value);
> +
> +decltype(auto) fn3() {
> +    Widget w;
> +    return (w); // { dg-warning "reference to local variable" }
> +}
> +static_assert(same_type<decltype(fn3), Widget&& ()>::value);
> +
> +decltype(auto) fn4() {
> +    Widget w;
> +    return w.x;
> +}
> +static_assert(same_type<decltype(fn4), int ()>::value);
> +
> +decltype(auto) fn5() {
> +    Widget w;
> +    return (w.x); // { dg-warning "reference to local variable" }
> +}
> +static_assert(same_type<decltype(fn5), int& ()>::value);
> diff --git a/gcc/testsuite/g++.dg/cpp23/elision1.C b/gcc/testsuite/g++.dg/cpp23/elision1.C
> new file mode 100644
> index 00000000000..f44fd2a061c
> --- /dev/null
> +++ b/gcc/testsuite/g++.dg/cpp23/elision1.C
> @@ -0,0 +1,114 @@
> +// PR c++/101165 - P2266R1 - Simpler implicit move
> +// { dg-do compile { target c++23 } }
> +// Tests from P2266R1.
> +
> +namespace std {
> +  template<typename _Tp>
> +    struct remove_reference
> +    { typedef _Tp   type; };
> +
> +  template<typename _Tp>
> +    struct remove_reference<_Tp&>
> +    { typedef _Tp   type; };
> +
> +  template<typename _Tp>
> +    struct remove_reference<_Tp&&>
> +    { typedef _Tp   type; };
> +
> +  template<typename _Tp>
> +    constexpr typename std::remove_reference<_Tp>::type&&
> +    move(_Tp&& __t) noexcept
> +    { return static_cast<typename std::remove_reference<_Tp>::type&&>(__t); }
> +}
> +
> +template<typename T, typename U>
> +struct same_type { static const bool value = false; };
> +
> +template<typename T>
> +struct same_type<T, T> { static const bool value = true; };
> +
> +struct Widget {
> +    Widget(Widget&&);
> +};
> +
> +struct RRefTaker {
> +    RRefTaker(Widget&&);
> +};
> +
> +struct Mutt {
> +    operator int*() &&;
> +};
> +
> +struct Jeff {
> +    operator int&() &&;
> +};
> +
> +struct Ella {
> +    operator int() &&;
> +};
> +
> +Widget one(Widget w) {
> +    return w;  // OK since C++11
> +}
> +
> +RRefTaker two(Widget w) {
> +    return w;  // OK since C++11 + CWG1579
> +}
> +
> +RRefTaker three(Widget&& w) {
> +    return w;  // OK since C++20 because P0527
> +}
> +
> +// Tests that implicit move applies even to functions that return references.
> +Widget&& four(Widget&& w) {
> +    return w;  // OK since C++23
> +}
> +
> +// ... or pointers.
> +int* five(Mutt x) {
> +    return x;  // OK since C++20 because P1155
> +}
> +
> +int& six(Jeff x) {
> +    return x;
> +}
> +
> +int test_ella(Ella e) {
> +  return e;
> +}
> +
> +template<class T>
> +T&& seven(T&& x) { return x; }
> +
> +void test_seven(Widget w) {
> +    Widget& r = seven(w);
> +    Widget&& rr = seven(std::move(w));
> +}
> +
> +Widget val();
> +Widget& lref();
> +Widget&& rref();
> +
> +decltype(auto) eight() {
> +    decltype(auto) x = val();  // OK, x is Widget
> +    return x;  // OK, return type is Widget, we get copy elision
> +}
> +
> +decltype(auto) nine() {
> +    decltype(auto) x = lref();  // OK, x is Widget&
> +    return x;  // OK, return type is Widget&
> +}
> +
> +decltype(auto) ten() {
> +  decltype(auto) x = rref();  // OK, x is Widget&&
> +  // This was an error: return type is Widget&&, cannot bind to x.
> +  // But in C++23, x is treated as an rvalue.
> +  return x;
> +}
> +
> +// Now returns Widget&&, not Widget&.
> +// This is from $ 3.2.1. Interaction with decltype and decltype(auto).
> +decltype(auto) eleven(Widget&& x) {
> +    return (x);
> +}
> +static_assert(same_type<decltype(eleven), Widget&& (Widget&&)>::value);
> diff --git a/gcc/testsuite/g++.dg/cpp23/elision2.C b/gcc/testsuite/g++.dg/cpp23/elision2.C
> new file mode 100644
> index 00000000000..ce2c7aeef66
> --- /dev/null
> +++ b/gcc/testsuite/g++.dg/cpp23/elision2.C
> @@ -0,0 +1,46 @@
> +// PR c++/101165 - P2266R1 - Simpler implicit move
> +// { dg-do compile { target c++20 } }
> +// Test from P2266R1, $ 3.3. Two overload resolutions are overly confusing.
> +
> +struct Widget {
> +    Widget();
> +    Widget(Widget&&);
> +};
> +
> +struct Frodo {
> +    Frodo(Widget&);
> +    Frodo(Widget&&) = delete;
> +};
> +
> +struct Sam {
> +    Sam(Widget&) = delete; // #1
> +    Sam(const Widget&);  // #2
> +};
> +
> +Sam twelve() {
> +    Widget w;
> +    // This is supposed to calls #2 since C++20 because P1155.
> +    // But we actually choose #1 since r11-2411 (in C++20 only).
> +    return w; // { dg-error "deleted" "" { target c++20_only } }
> +}
> +
> +Frodo thirteen() {
> +    Widget w;
> +    // This is a correct error in both C++20 and C++23.
> +    return w;  // { dg-error "use of deleted function" }
> +}
> +
> +struct Merry {};
> +struct Pippin {};
> +struct Together : Merry, Pippin {};
> +struct Quest {
> +    Quest(Merry&&);
> +    Quest(Pippin&&);
> +    Quest(Together&);
> +};
> +
> +Quest fourteen() {
> +  Together t;
> +  // C++20: calls Quest(Together&).  Proposed: ill-formed.
> +  return t; // { dg-error "ambiguous" "" { target c++23 } }
> +}
> diff --git a/gcc/testsuite/g++.dg/cpp23/elision3.C b/gcc/testsuite/g++.dg/cpp23/elision3.C
> new file mode 100644
> index 00000000000..246342e64d3
> --- /dev/null
> +++ b/gcc/testsuite/g++.dg/cpp23/elision3.C
> @@ -0,0 +1,16 @@
> +// PR c++/101165 - P2266R1 - Simpler implicit move
> +// { dg-do compile { target c++23 } }
> +// Test from P2266R1, $ 3.4. A specific case involving reference_wrapper.
> +
> +#include <functional>
> +
> +struct Widget {
> +    Widget();
> +    Widget(Widget&&);
> +};
> +
> +std::reference_wrapper<Widget> fifteen() {
> +    Widget w;
> +    // OK until CWG1579; OK after LWG2993.  Proposed: ill-formed
> +    return w;  // { dg-error "could not convert" }
> +}
> diff --git a/gcc/testsuite/g++.dg/cpp23/elision4.C b/gcc/testsuite/g++.dg/cpp23/elision4.C
> new file mode 100644
> index 00000000000..c19b86b8b5f
> --- /dev/null
> +++ b/gcc/testsuite/g++.dg/cpp23/elision4.C
> @@ -0,0 +1,38 @@
> +// PR c++/101165 - P2266R1 - Simpler implicit move
> +// { dg-do compile { target c++23 } }
> +// Test from P2266R1, $ 5.2. LibreOffice OString constructor.
> +
> +struct X {
> +    X(auto&);
> +};
> +
> +// The following compiles in C++20 (deducing X(char (&)[10])) but not
> +// after P2266 (because the returned expression now has type char (&&)[10],
> +// which cannot bind to auto&).
> +X f() {
> +    char a[10];
> +    return a; // { dg-error "cannot bind non-const lvalue reference" }
> +}
> +
> +// The solution was to change it by making the return convert explicitly
> +// rather than implicitly:
> +X fixed() {
> +    char a[10];
> +    return X(a);
> +}
> +
> +// $ 5.3. LibreOffice o3tl::temporary
> +
> +template<class T>
> +T& temporary1(T&& x) { return x; } // { dg-error "cannot bind non-const lvalue reference" }
> +
> +// Fixed by:
> +template<class T>
> +T& temporary2(T&& x) { return static_cast<T&>(x); }
> +
> +void
> +test ()
> +{
> +  int& r1 = temporary1 (42);
> +  int& r2 = temporary2 (42);
> +}
> diff --git a/gcc/testsuite/g++.dg/cpp23/elision5.C b/gcc/testsuite/g++.dg/cpp23/elision5.C
> new file mode 100644
> index 00000000000..a7d3e7c27c4
> --- /dev/null
> +++ b/gcc/testsuite/g++.dg/cpp23/elision5.C
> @@ -0,0 +1,53 @@
> +// PR c++/101165 - P2266R1 - Simpler implicit move
> +// { dg-do compile { target c++23 } }
> +// Test from [class.copy.elision]/4.
> +
> +class Thing {
> +public:
> +  Thing();
> +  ~Thing();
> +  Thing(Thing&&);
> +private:
> +  Thing(const Thing&);
> +};
> +
> +Thing f(bool b) {
> +  Thing t;
> +  if (b)
> +    throw t;            // OK, Thing(Thing&&) used (or elided) to throw t
> +  return t;             // OK, Thing(Thing&&) used (or elided) to return t
> +}
> +
> +Thing t2 = f(false);    // OK, no extra copy/move performed, t2 constructed by call to f
> +
> +struct Weird {
> +  Weird();
> +  Weird(Weird&);
> +};
> +
> +Weird g(bool b) {
> +  static Weird w1;
> +  Weird w2;
> +  if (b) {
> +    return w1;  // OK: Weird(Weird&)
> +  } else {
> +    return w2;  // { dg-error "cannot bind non-const lvalue reference" }
> +  }
> +}
> +
> +int& h(bool b, int i) {
> +  static int s;
> +  if (b)
> +    return s;   // OK
> +  else
> +    return i;   // { dg-error "cannot bind non-const lvalue reference" }
> +}
> +
> +decltype(auto) h2(Thing t) {
> +  return t;     // OK, t is an xvalue and h2's return type is Thing
> +}
> +
> +decltype(auto) h3(Thing t) {
> +  // OK, (t) is an xvalue and h3's return type is Thing&&
> +  return (t); // { dg-warning "reference to local variable" }
> +}
> diff --git a/gcc/testsuite/g++.dg/cpp23/elision6.C b/gcc/testsuite/g++.dg/cpp23/elision6.C
> new file mode 100644
> index 00000000000..5d58da9e577
> --- /dev/null
> +++ b/gcc/testsuite/g++.dg/cpp23/elision6.C
> @@ -0,0 +1,20 @@
> +// PR c++/101165 - P2266R1 - Simpler implicit move
> +// { dg-do compile { target c++23 } }
> +// From [diff.cpp20.expr].
> +
> +template<typename T, typename U>
> +struct same_type { static const bool value = false; };
> +
> +template<typename T>
> +struct same_type<T, T> { static const bool value = true; };
> +
> +// In C++23, returns int&&; previously returned int&.
> +decltype(auto) f(int&& x) { return (x); }
> +static_assert(same_type<decltype(f), int&& (int&&)>::value);
> +
> +// This used to work in C++20.
> +int& g(int&& x) { return x; } // { dg-error "cannot bind non-const lvalue reference" }
> +
> +template<typename T>
> +decltype(auto) h(T&& x) { return (x); }
> +static_assert(same_type<decltype(h(42)), int&&>::value);
> diff --git a/gcc/testsuite/g++.dg/cpp23/elision7.C b/gcc/testsuite/g++.dg/cpp23/elision7.C
> new file mode 100644
> index 00000000000..19fa89ae133
> --- /dev/null
> +++ b/gcc/testsuite/g++.dg/cpp23/elision7.C
> @@ -0,0 +1,72 @@
> +// PR c++/101165 - P2266R1 - Simpler implicit move
> +// { dg-do compile { target c++23 } }
> +
> +struct X {
> +  X ();
> +  X(X&&);
> +};
> +
> +X&& rref ();
> +
> +X&&
> +f1 (X&& x)
> +{
> +  return x;
> +}
> +
> +template<typename T> T&&
> +f2 (T&& x)
> +{
> +  return x;
> +}
> +template X& f2<X&>(X&);
> +template X&& f2<X>(X&&);
> +
> +X&&
> +f3 ()
> +{
> +  X&& x = rref ();
> +  return x;
> +}
> +
> +void
> +f4 ()
> +try {
> +  X x;
> +  throw x;
> +} catch (...) { }
> +
> +void
> +f5 ()
> +{
> +  auto l1 = [](auto x) -> auto { return x; };
> +  auto &&x1 = l1(X{});
> +  auto l2 = [](auto x) -> auto& { return x; }; // { dg-error "cannot bind non-const lvalue reference" }
> +  auto &&x2 = l2(X{});
> +  auto l3 = [](auto x) -> auto&& { return x; }; // { dg-warning "reference to local" }
> +  auto &&x3 = l3(X{});
> +}
> +
> +constexpr int &
> +f6 (int &&n)
> +{
> +  return n; // { dg-error "cannot bind non-const lvalue reference" }
> +}
> +
> +void
> +do_f6 ()
> +{
> +  auto x = f6 (42);
> +}
> +
> +template<typename T> auto &
> +f7 (T &&t)
> +{
> +  return t; // { dg-error "cannot bind non-const lvalue reference" }
> +}
> +
> +void
> +do_f7 ()
> +{
> +  const int &x = f7 (0);
> +}
> diff --git a/gcc/testsuite/g++.dg/cpp23/feat-cxx2b.C b/gcc/testsuite/g++.dg/cpp23/feat-cxx2b.C
> index 2f6b21ead70..b52cf378b41 100644
> --- a/gcc/testsuite/g++.dg/cpp23/feat-cxx2b.C
> +++ b/gcc/testsuite/g++.dg/cpp23/feat-cxx2b.C
> @@ -569,3 +569,9 @@
>   #elif __cpp_static_call_operator != 202207
>   #  error "__cpp_static_call_operator != 202207"
>   #endif
> +
> +#ifndef __cpp_implicit_move
> +#  error "__cpp_implicit_move"
> +#elif __cpp_implicit_move != 202207
> +#  error "__cpp_implicit_move != 202207"
> +#endif
> diff --git a/gcc/testsuite/g++.dg/gomp/pr56217.C b/gcc/testsuite/g++.dg/gomp/pr56217.C
> index 03dfc5f180b..731c0c08811 100644
> --- a/gcc/testsuite/g++.dg/gomp/pr56217.C
> +++ b/gcc/testsuite/g++.dg/gomp/pr56217.C
> @@ -1,5 +1,5 @@
>   // PR middle-end/56217
> -// { dg-do compile }
> +// { dg-do compile { target c++20_down } }
>   // { dg-options "-fopenmp" }
>   
>   struct S { int *p; S (); S (S &); };
> @@ -10,5 +10,7 @@ foo ()
>     S s;
>     #pragma omp task shared (s)
>       s.p = 0;
> +  // This fails in C++23, because "cannot bind non-const lvalue reference of
> +  // type 'S&' to an rvalue of type 'S'".
>     return s;
>   }
> diff --git a/gcc/testsuite/g++.dg/warn/Wno-return-local-addr.C b/gcc/testsuite/g++.dg/warn/Wno-return-local-addr.C
> index e15bfa24f54..cc9bb59770e 100644
> --- a/gcc/testsuite/g++.dg/warn/Wno-return-local-addr.C
> +++ b/gcc/testsuite/g++.dg/warn/Wno-return-local-addr.C
> @@ -4,7 +4,7 @@
>   int& bad1()
>   {
>     int x = 0;
> -  return x;
> +  return x; // { dg-error "cannot bind non-const lvalue reference" "" { target c++23 } }
>   }
>   
>   int* bad2()
> diff --git a/gcc/testsuite/g++.dg/warn/Wreturn-local-addr.C b/gcc/testsuite/g++.dg/warn/Wreturn-local-addr.C
> index 642a5767e84..4c18c2f06a0 100644
> --- a/gcc/testsuite/g++.dg/warn/Wreturn-local-addr.C
> +++ b/gcc/testsuite/g++.dg/warn/Wreturn-local-addr.C
> @@ -5,7 +5,7 @@
>   int& bad1()
>   {
>     int x = 0;
> -  return x;		// { dg-error "reference to local variable" }
> +  return x;		// { dg-error "reference to local variable|cannot bind non-const lvalue reference" }
>   }
>   
>   int* bad2()
> diff --git a/gcc/testsuite/g++.old-deja/g++.brendan/crash55.C b/gcc/testsuite/g++.old-deja/g++.brendan/crash55.C
> index fd4d4b65edb..b93e6e0c695 100644
> --- a/gcc/testsuite/g++.old-deja/g++.brendan/crash55.C
> +++ b/gcc/testsuite/g++.old-deja/g++.brendan/crash55.C
> @@ -8,5 +8,6 @@
>   
>             local = x+2;
>         
> -          return local; // { dg-warning "reference to local" }
> +          return local; // { dg-warning "reference to local" "" { target c++20_down } }
> +// { dg-error "non-const lvalue" "" { target c++23 } .-1 }
>         }
> diff --git a/gcc/testsuite/g++.old-deja/g++.jason/temporary2.C b/gcc/testsuite/g++.old-deja/g++.jason/temporary2.C
> index c855f8f4a07..2709b50e7f1 100644
> --- a/gcc/testsuite/g++.old-deja/g++.jason/temporary2.C
> +++ b/gcc/testsuite/g++.old-deja/g++.jason/temporary2.C
> @@ -8,7 +8,7 @@ public:
>     int i;
>   };
>   
> -X foo() { X x; return x; }
> +X foo() { X x; return x; } // { dg-error "cannot bind non-const lvalue reference" "" { target c++23 } }
>   
>   int main()
>   {
> diff --git a/gcc/testsuite/g++.old-deja/g++.mike/p2846b.C b/gcc/testsuite/g++.old-deja/g++.mike/p2846b.C
> index 57422fe64df..68826649cfc 100644
> --- a/gcc/testsuite/g++.old-deja/g++.mike/p2846b.C
> +++ b/gcc/testsuite/g++.old-deja/g++.mike/p2846b.C
> @@ -1,4 +1,4 @@
> -// { dg-do run  }
> +// { dg-do run { target c++20_down } }
>   // Shows that problem of initializing one object's secondary base from
>   // another object via a user defined copy constructor for that base,
>   // the pointer for the secondary vtable is not set after implicit
> @@ -11,6 +11,8 @@
>   
>   // prms-id: 2846
>   
> +// This test fails in C++23 due to P2266.

Instead of disabling this test for C++23, let's add a cast to B& in the 
return statement.

OK with that change and optionally the ?: reformatting above.

>   extern "C" int printf(const char *, ...);
>   extern "C" void exit(int);
>   
> 
> base-commit: e73d9fcafbd07bc3714fbaf8a82db71d50015c92


^ permalink raw reply	[flat|nested] 10+ messages in thread

* Re: [PATCH v3] c++: Implement C++23 P2266R1, Simpler implicit move [PR101165]
  2022-09-27 21:44         ` Jason Merrill
@ 2022-09-27 23:39           ` Marek Polacek
  0 siblings, 0 replies; 10+ messages in thread
From: Marek Polacek @ 2022-09-27 23:39 UTC (permalink / raw)
  To: Jason Merrill; +Cc: GCC Patches

On Tue, Sep 27, 2022 at 05:44:12PM -0400, Jason Merrill wrote:
> On 9/27/22 16:26, Marek Polacek wrote:
> > --- a/gcc/cp/typeck.cc
> > +++ b/gcc/cp/typeck.cc
> > @@ -11042,8 +11042,13 @@ check_return_expr (tree retval, bool *no_warning)
> >   	 the conditions for the named return value optimization.  */
> >         bool converted = false;
> >         tree moved;
> > -      /* This is only interesting for class type.  */
> > -      if (CLASS_TYPE_P (functype)
> > +      /* Until C++23, this was only interesting for class type...  */
> > +      if ((CLASS_TYPE_P (functype)
> > +	   /* ...but in C++23, we should do the below when we're converting
> > +	      from/to a class/reference (a non-scalar type).  */
> > +	   || (cxx_dialect >= cxx23
> > +	       && (!SCALAR_TYPE_P (functype)
> > +		   || !SCALAR_TYPE_P (TREE_TYPE (retval)))))
> 
> You might reformat this as
> (cxx_dialect < cxx23
>  ? CLASS...
>  : (!SCALAR...

Done, I like that better.
 
> > --- a/gcc/testsuite/g++.dg/cpp0x/move-return3.C
> > +++ b/gcc/testsuite/g++.dg/cpp0x/move-return3.C
> > @@ -1,6 +1,7 @@
> >   // PR c++/91212
> >   // Test that C++11 implicit move semantics don't call the const copy.
> > -// { dg-do link }
> > +// In C++23, we call #2.
> 
> I guess that behavior is tested by elision2.C:twelve()?

Yeah, I think that's exactly the same case.
 
> > --- a/gcc/testsuite/g++.old-deja/g++.mike/p2846b.C
> > +++ b/gcc/testsuite/g++.old-deja/g++.mike/p2846b.C
> > @@ -1,4 +1,4 @@
> > -// { dg-do run  }
> > +// { dg-do run { target c++20_down } }
> >   // Shows that problem of initializing one object's secondary base from
> >   // another object via a user defined copy constructor for that base,
> >   // the pointer for the secondary vtable is not set after implicit
> > @@ -11,6 +11,8 @@
> >   // prms-id: 2846
> > +// This test fails in C++23 due to P2266.
> 
> Instead of disabling this test for C++23, let's add a cast to B& in the
> return statement.

Fixed.

> OK with that change and optionally the ?: reformatting above.

Thanks a lot; patch pushed.

Marek


^ permalink raw reply	[flat|nested] 10+ messages in thread

end of thread, other threads:[~2022-09-27 23:39 UTC | newest]

Thread overview: 10+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2022-09-03 16:42 [PATCH] c++: Implement C++23 P2266R1, Simpler implicit move [PR101165] Marek Polacek
2022-09-07  2:38 ` Jason Merrill
2022-09-08 22:54   ` Marek Polacek
2022-09-12 20:27     ` Jason Merrill
2022-09-20 18:21       ` Marek Polacek
2022-09-20 18:19   ` [PATCH v2] " Marek Polacek
2022-09-26 17:29     ` Jason Merrill
2022-09-27 20:26       ` [PATCH v3] " Marek Polacek
2022-09-27 21:44         ` Jason Merrill
2022-09-27 23:39           ` Marek Polacek

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for read-only IMAP folder(s) and NNTP newsgroup(s).