]> git.ipfire.org Git - thirdparty/gcc.git/commitdiff
c++: Implement C++23 P2266R1, Simpler implicit move [PR101165]
authorMarek Polacek <polacek@redhat.com>
Wed, 31 Aug 2022 21:37:59 +0000 (17:37 -0400)
committerMarek Polacek <polacek@redhat.com>
Tue, 27 Sep 2022 22:49:25 +0000 (18:49 -0400)
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: Adjust.
* 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.

28 files changed:
gcc/c-family/c-cppbuiltin.cc
gcc/cp/call.cc
gcc/cp/cp-tree.h
gcc/cp/pt.cc
gcc/cp/tree.cc
gcc/cp/typeck.cc
gcc/testsuite/g++.dg/conversion/pr41426.C
gcc/testsuite/g++.dg/cpp0x/elision_weak.C
gcc/testsuite/g++.dg/cpp0x/move-return3.C
gcc/testsuite/g++.dg/cpp1y/decltype-auto2.C
gcc/testsuite/g++.dg/cpp1y/decltype-auto6.C [new file with mode: 0644]
gcc/testsuite/g++.dg/cpp1y/lambda-generic-89419.C
gcc/testsuite/g++.dg/cpp23/decltype1.C [new file with mode: 0644]
gcc/testsuite/g++.dg/cpp23/decltype2.C [new file with mode: 0644]
gcc/testsuite/g++.dg/cpp23/elision1.C [new file with mode: 0644]
gcc/testsuite/g++.dg/cpp23/elision2.C [new file with mode: 0644]
gcc/testsuite/g++.dg/cpp23/elision3.C [new file with mode: 0644]
gcc/testsuite/g++.dg/cpp23/elision4.C [new file with mode: 0644]
gcc/testsuite/g++.dg/cpp23/elision5.C [new file with mode: 0644]
gcc/testsuite/g++.dg/cpp23/elision6.C [new file with mode: 0644]
gcc/testsuite/g++.dg/cpp23/elision7.C [new file with mode: 0644]
gcc/testsuite/g++.dg/cpp23/feat-cxx2b.C
gcc/testsuite/g++.dg/gomp/pr56217.C
gcc/testsuite/g++.dg/warn/Wno-return-local-addr.C
gcc/testsuite/g++.dg/warn/Wreturn-local-addr.C
gcc/testsuite/g++.old-deja/g++.brendan/crash55.C
gcc/testsuite/g++.old-deja/g++.jason/temporary2.C
gcc/testsuite/g++.old-deja/g++.mike/p2846b.C

index ca5f500e079b5c45721504b6b587a98bdbf4fa51..d4de5a0dc57d1d61802e4ab738a4855b7c64a9c9 100644 (file)
@@ -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)
         {
index fc86b74a5a43e610a0bb16dde0b6dbbc6795bbe4..3506b0fcfbb5b9cbe5f9109daf77b68807117a25 100644 (file)
@@ -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))
index 99b486b80027ed7c80fc8e61b48d9ec060abdf57..19bbfbc557fd076273938aad7b00f94e9cbb549c 100644 (file)
@@ -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,
index 1c1e5735743b23406e86e8526a2d82944b2cb3b8..2d83dfd6954cbc9639128cfdb2aeaf2b925078b9 100644 (file)
@@ -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)
index d0bd41ae5a004dbd315e19e0ed52a733b656a184..ea4dfc651bbbef94d0765438bd49bf8875b690e2 100644 (file)
@@ -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.  */
index 4854b98376533338489ed57769cc3cb487a4c50b..5f16c4d242603f4194b9952c8296aca9e341b96b 100644 (file)
@@ -11042,9 +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)
-         && (moved = treat_lvalue_as_rvalue_p (retval, /*return*/true)))
+      /* Until C++23, this was only interesting for class type, but in C++23,
+        we should do the below when we're converting rom/to a class/reference
+        (a non-scalar type).  */
+       if ((cxx_dialect < cxx23
+            ? CLASS_TYPE_P (functype)
+            : !SCALAR_TYPE_P (functype) || !SCALAR_TYPE_P (TREE_TYPE (retval)))
+           && (moved = treat_lvalue_as_rvalue_p (retval, /*return*/true)))
        {
          if (cxx_dialect < cxx20)
            {
index 5493a91ecfad23915fa3ad5ba6c4066e6cdfcd69..b4ecbca5f3ac1516000b97337a240bdb751f54d2 100644 (file)
@@ -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 } }
 }
-
index e8ba7551d84e9c87499e09f632a4ae727cae7470..ddd1274313057dd130557d9635d02a4ff6d5ab11 100644 (file)
@@ -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 } }
 }
index c79f059193671df318963dada255a2f07008a52e..30a936fb35a8979ad0f41059b3b98ecd11e9029d 100644 (file)
@@ -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; };
 
index 56e011e36f4622f1b47371c69f8cb687751a2c0e..24b32edfacf92513b9560bf5b865b247ade04b79 100644 (file)
@@ -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 (file)
index 0000000..da53278
--- /dev/null
@@ -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" }
+}
index 46ce909f3b8fa4f367c534b4a870785c602ff786..8e64d4e64abbfb3c92c9c54215b8f0d3435d2079 100644 (file)
@@ -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 (file)
index 0000000..6f3cd0d
--- /dev/null
@@ -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 (file)
index 0000000..84679c4
--- /dev/null
@@ -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 (file)
index 0000000..f44fd2a
--- /dev/null
@@ -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 (file)
index 0000000..a698fc9
--- /dev/null
@@ -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 call #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 (file)
index 0000000..246342e
--- /dev/null
@@ -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 (file)
index 0000000..c19b86b
--- /dev/null
@@ -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 (file)
index 0000000..a7d3e7c
--- /dev/null
@@ -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 (file)
index 0000000..5d58da9
--- /dev/null
@@ -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 (file)
index 0000000..19fa89a
--- /dev/null
@@ -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);
+}
index 2f6b21ead7006ecb6ac848f4bf0abe41d5dd20d5..b52cf378b410e05225a172c4886466e48842b0bb 100644 (file)
 #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
index 03dfc5f180bfc820f681695efcaf1ef7412e6402..731c0c08811a86f9159271f8ebabe71227e2f1f6 100644 (file)
@@ -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;
 }
index e15bfa24f54baa14abf6e799ea9888663f3a25bf..cc9bb59770ee253bdaa32dae9a386158aac1f2b4 100644 (file)
@@ -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()
index 642a5767e841820f9be0ad44e038616d79833025..4c18c2f06a040be44160a5d3261b342fc1fa6d80 100644 (file)
@@ -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()
index fd4d4b65edb982c11df7cbb7dac586a2ff7b7eb1..b93e6e0c69566c3c29c17b79e1c9a06687abfaef 100644 (file)
@@ -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 }
       }
index c855f8f4a076ec21380a5440052f5738d90d5e5c..2709b50e7f1f07ce1d257de8f5efae3333147ca4 100644 (file)
@@ -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() 
 {
index 57422fe64df21aeda233593a12508b058611fb50..5bcf9e3624093ca21d18fa3fdd970a27de7328e9 100644 (file)
@@ -42,7 +42,7 @@ public:
 B A::compute(void) const
 {
   B sub(*this, 1);
-  return sub;
+  return static_cast<B&>(sub);
 }
 
 int main ()