c++: Implement C++20 implicit move changes. [PR91427]
authorJason Merrill <jason@redhat.com>
Tue, 21 Jul 2020 04:19:49 +0000 (00:19 -0400)
committerJason Merrill <jason@redhat.com>
Wed, 29 Jul 2020 18:24:59 +0000 (14:24 -0400)
P1825R0 extends the C++11 implicit move on return by removing the
constraints on the called constructor: previously, it needed to take an
rvalue reference to the type of the returned variable.  The paper also
allows move on throw of parameters and implicit move of rvalue references.

Discussion on the CWG reflector about how to avoid breaking the PR91212 test
in the new model settled on the model of doing only a single overload
resolution, with the variable treated as an xvalue that can bind to
non-const lvalue references.  So this patch implements that approach.  The
implementation does not use the existing LOOKUP_PREFER_RVALUE flag, but
instead sets a flag on the representation of the static_cast turning the
variable into an xvalue.

For the time being I'm limiting the new semantics to C++20 mode; since it
was moved as a DR, we will probably want to apply the change to other
standard modes as well once we have a better sense of the impact on existing
code, probably in GCC 12.

gcc/cp/ChangeLog:

PR c++/91427
* cp-tree.h (IMPLICIT_RVALUE_P): New.
(enum cp_lvalue_kind_flags): Add clk_implicit_rval.
(implicit_rvalue_p, set_implicit_rvalue_p): New.
* call.c (reference_binding): Check clk_implicit_rval.
(build_over_call): Adjust C++20 implicit move.
* coroutines.cc (finish_co_return_stmt): Simplify implicit move.
* except.c (build_throw): Adjust C++20 implicit move.
* pt.c (tsubst_copy_and_build) [STATIC_CAST_EXPR]: Propagate
IMPLICIT_RVALUE_P.
* tree.c (lvalue_kind): Set clk_implicit_rval.
* typeck.c (treat_lvalue_as_rvalue_p): Overhaul.
(maybe_warn_pessimizing_move): Adjust.
(check_return_expr): Adjust C++20 implicit move.

gcc/testsuite/ChangeLog:

PR c++/91427
* g++.dg/coroutines/co-return-syntax-10-movable.C: Extend.
* g++.dg/cpp0x/Wredundant-move1.C: Adjust for C++20.
* g++.dg/cpp0x/Wredundant-move7.C: Adjust for C++20.
* g++.dg/cpp0x/Wredundant-move9.C: Adjust for C++20.
* g++.dg/cpp0x/elision_neg.C: Adjust for C++20.
* g++.dg/cpp0x/move-return2.C: Adjust for C++20.
* g++.dg/cpp0x/ref-qual20.C: Adjust for C++20.
* g++.dg/cpp2a/implicit-move1.C: New test.
* g++.dg/cpp2a/implicit-move2.C: New test.
* g++.dg/cpp2a/implicit-move3.C: New test.

17 files changed:
gcc/cp/call.c
gcc/cp/coroutines.cc
gcc/cp/cp-tree.h
gcc/cp/except.c
gcc/cp/pt.c
gcc/cp/tree.c
gcc/cp/typeck.c
gcc/testsuite/g++.dg/coroutines/co-return-syntax-10-movable.C
gcc/testsuite/g++.dg/cpp0x/Wredundant-move1.C
gcc/testsuite/g++.dg/cpp0x/Wredundant-move7.C
gcc/testsuite/g++.dg/cpp0x/Wredundant-move9.C
gcc/testsuite/g++.dg/cpp0x/elision_neg.C
gcc/testsuite/g++.dg/cpp0x/move-return2.C
gcc/testsuite/g++.dg/cpp0x/ref-qual20.C
gcc/testsuite/g++.dg/cpp2a/implicit-move1.C [new file with mode: 0644]
gcc/testsuite/g++.dg/cpp2a/implicit-move2.C [new file with mode: 0644]
gcc/testsuite/g++.dg/cpp2a/implicit-move3.C [new file with mode: 0644]

index e283d635d60ebc2ad875eec3d68ca42bf69f960a..f164b211c9f58585f9bc9894f4be74141233b51a 100644 (file)
@@ -1822,6 +1822,9 @@ 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
+              && (gl_kind & clk_implicit_rval))
          && (!CP_TYPE_CONST_NON_VOLATILE_P (to)
              || (flags & LOOKUP_NO_RVAL_BIND))
          && TREE_CODE (to) != FUNCTION_TYPE)
@@ -8678,7 +8681,8 @@ build_over_call (struct z_candidate *cand, int flags, tsubst_flags_t complain)
          parm = TREE_CHAIN (parm);
        }
 
-      if (cand->flags & LOOKUP_PREFER_RVALUE)
+      if (cxx_dialect < cxx20
+         && (cand->flags & LOOKUP_PREFER_RVALUE))
        {
          /* The implicit move specified in 15.8.3/3 fails "...if the type of
             the first parameter of the selected constructor is not an rvalue
index 84b6a4edec580a0295a0948755b4167266b9d604..8bebbe3f9e1aea8dd9d73816af652ec6a593797f 100644 (file)
@@ -1189,29 +1189,15 @@ finish_co_return_stmt (location_t kw, tree expr)
         treating the object as an rvalue, if that fails, then we fall back
         to regular overload resolution.  */
 
-      if (treat_lvalue_as_rvalue_p (expr, /*parm_ok*/true)
-         && CLASS_TYPE_P (TREE_TYPE (expr))
-         && !TYPE_VOLATILE (TREE_TYPE (expr)))
-       {
-         /* It's OK if this fails... */
-         vec<tree, va_gc> *args = make_tree_vector_single (move (expr));
-         co_ret_call
-           = coro_build_promise_expression (current_function_decl, NULL,
-                                            coro_return_value_identifier, kw,
-                                            &args, /*musthave=*/false);
-         release_tree_vector (args);
-       }
-
-      if (!co_ret_call || co_ret_call == error_mark_node)
-       {
-         /* ... but this must succeed if we didn't get the move variant.  */
-         vec<tree, va_gc> *args = make_tree_vector_single (expr);
-         co_ret_call
-           = coro_build_promise_expression (current_function_decl, NULL,
-                                            coro_return_value_identifier, kw,
-                                            &args, /*musthave=*/true);
-         release_tree_vector (args);
-       }
+      tree arg = expr;
+      if (tree moved = treat_lvalue_as_rvalue_p (expr, /*return*/true))
+       arg = moved;
+
+      releasing_vec args = make_tree_vector_single (arg);
+      co_ret_call
+       = coro_build_promise_expression (current_function_decl, NULL,
+                                        coro_return_value_identifier, kw,
+                                        &args, /*musthave=*/true);
     }
 
   /* Makes no sense for a co-routine really. */
index 2377fc052bbe31a2c58c8bd83669ee1e34397ad2..ea4871f836a54a2b1072d2f127c87c51d3038bb7 100644 (file)
@@ -466,7 +466,7 @@ extern GTY(()) tree cp_global_trees[CPTI_MAX];
       IMPLICIT_CONV_EXPR_BRACED_INIT (in IMPLICIT_CONV_EXPR)
       TINFO_VAR_DECLARED_CONSTINIT (in TEMPLATE_INFO)
       CALL_FROM_NEW_OR_DELETE_P (in CALL_EXPR)
-   3: (TREE_REFERENCE_EXPR) (in NON_LVALUE_EXPR) (commented-out).
+   3: IMPLICIT_RVALUE_P (in NON_LVALUE_EXPR or STATIC_CAST_EXPR)
       ICS_BAD_FLAG (in _CONV)
       FN_TRY_BLOCK_P (in TRY_BLOCK)
       BIND_EXPR_BODY_BLOCK (in BIND_EXPR)
@@ -3803,6 +3803,11 @@ struct GTY(()) lang_decl {
    && TREE_TYPE (TREE_OPERAND (NODE, 0))               \
    && TYPE_REF_P (TREE_TYPE (TREE_OPERAND ((NODE), 0))))
 
+/* True iff this represents an lvalue being treated as an rvalue during return
+   or throw as per [class.copy.elision].  */
+#define IMPLICIT_RVALUE_P(NODE) \
+  TREE_LANG_FLAG_3 (TREE_CHECK2 ((NODE), NON_LVALUE_EXPR, STATIC_CAST_EXPR))
+
 #define NEW_EXPR_USE_GLOBAL(NODE) \
   TREE_LANG_FLAG_0 (NEW_EXPR_CHECK (NODE))
 #define DELETE_EXPR_USE_GLOBAL(NODE) \
@@ -5184,7 +5189,8 @@ enum cp_lvalue_kind_flags {
   clk_rvalueref = 2,/* An xvalue (rvalue formed using an rvalue reference) */
   clk_class = 4,    /* A prvalue of class or array type.  */
   clk_bitfield = 8, /* An lvalue for a bit-field.  */
-  clk_packed = 16   /* An lvalue for a packed field.  */
+  clk_packed = 16,  /* An lvalue for a packed field.  */
+  clk_implicit_rval = 1<<5 /* An lvalue being treated as an xvalue.  */
 };
 
 /* This type is used for parameters and variables which hold
@@ -5572,6 +5578,8 @@ enum overload_flags { NO_SPECIAL = 0, DTOR_FLAG, TYPENAME_FLAG };
    not found by lookup.)  */
 #define LOOKUP_HIDDEN (LOOKUP_PREFER_NAMESPACES << 1)
 /* We're trying to treat an lvalue as an rvalue.  */
+/* FIXME remove when we extend the P1825 semantics to all standard modes, the
+   C++20 approach uses IMPLICIT_RVALUE_P instead.  */
 #define LOOKUP_PREFER_RVALUE (LOOKUP_HIDDEN << 1)
 /* We're inside an init-list, so narrowing conversions are ill-formed.  */
 #define LOOKUP_NO_NARROWING (LOOKUP_PREFER_RVALUE << 1)
@@ -7645,7 +7653,7 @@ extern tree cp_perform_integral_promotions      (tree, tsubst_flags_t);
 extern tree finish_left_unary_fold_expr      (tree, int);
 extern tree finish_right_unary_fold_expr     (tree, int);
 extern tree finish_binary_fold_expr          (tree, tree, int);
-extern bool treat_lvalue_as_rvalue_p        (tree, bool);
+extern tree treat_lvalue_as_rvalue_p        (tree, bool);
 extern bool decl_in_std_namespace_p         (tree);
 
 /* in typeck2.c */
@@ -8116,6 +8124,27 @@ concept_check_p (const_tree t)
   return false;
 }
 
+/* Helpers for IMPLICIT_RVALUE_P to look through automatic dereference.  */
+
+inline bool
+implicit_rvalue_p (const_tree t)
+{
+  if (REFERENCE_REF_P (t))
+    t = TREE_OPERAND (t, 0);
+  return ((TREE_CODE (t) == NON_LVALUE_EXPR
+          || TREE_CODE (t) == STATIC_CAST_EXPR)
+         && IMPLICIT_RVALUE_P (t));
+}
+inline tree
+set_implicit_rvalue_p (tree ot)
+{
+  tree t = ot;
+  if (REFERENCE_REF_P (t))
+    t = TREE_OPERAND (t, 0);
+  IMPLICIT_RVALUE_P (t) = 1;
+  return ot;
+}
+
 /* True if t is a "constrained auto" type-specifier.  */
 
 inline bool
index aca54f136ba1e5095b6cbd1f5d3fec83f5846615..cb1a4105dae16c81c0f2cb589b63d556ecabd6e1 100644 (file)
@@ -696,21 +696,25 @@ build_throw (location_t loc, tree exp)
          /* Under C++0x [12.8/16 class.copy], a thrown lvalue is sometimes
             treated as an rvalue for the purposes of overload resolution
             to favor move constructors over copy constructors.  */
-         if (treat_lvalue_as_rvalue_p (exp, /*parm_ok*/false)
-             /* The variable must not have the `volatile' qualifier.  */
-             && !CP_TYPE_VOLATILE_P (TREE_TYPE (exp)))
+         if (tree moved = treat_lvalue_as_rvalue_p (exp, /*return*/false))
            {
-             tree moved = move (exp);
-             releasing_vec exp_vec (make_tree_vector_single (moved));
-             moved = (build_special_member_call
-                      (object, complete_ctor_identifier, &exp_vec,
-                       TREE_TYPE (object), flags|LOOKUP_PREFER_RVALUE,
-                       tf_none));
-             if (moved != error_mark_node)
+             if (cxx_dialect < cxx20)
                {
-                 exp = moved;
-                 converted = true;
+                 releasing_vec exp_vec (make_tree_vector_single (moved));
+                 moved = (build_special_member_call
+                          (object, complete_ctor_identifier, &exp_vec,
+                           TREE_TYPE (object), flags|LOOKUP_PREFER_RVALUE,
+                           tf_none));
+                 if (moved != error_mark_node)
+                   {
+                     exp = moved;
+                     converted = true;
+                   }
                }
+             else
+               /* In C++20 we just treat the return value as an rvalue that
+                  can bind to lvalue refs.  */
+               exp = moved;
            }
 
          /* Call the copy constructor.  */
index d9db44f919d7719e211bee8ac6ae8cc6dd4eb968..6a42cf93dccb4518d50020465d20bfc035505951 100644 (file)
@@ -19411,6 +19411,8 @@ tsubst_copy_and_build (tree t,
            break;
          case STATIC_CAST_EXPR:
            r = build_static_cast (input_location, type, op, complain);
+           if (IMPLICIT_RVALUE_P (t))
+             set_implicit_rvalue_p (r);
            break;
          default:
            gcc_unreachable ();
index a830c90a78f0d2dfa8c36ed5787035e404ca2812..e8606602bd21589bbdaec17a516f836dc43523e9 100644 (file)
@@ -73,7 +73,12 @@ lvalue_kind (const_tree ref)
          && TREE_CODE (ref) != COMPONENT_REF
          /* Functions are always lvalues.  */
          && TREE_CODE (TREE_TYPE (TREE_TYPE (ref))) != FUNCTION_TYPE)
-       return clk_rvalueref;
+       {
+         op1_lvalue_kind = clk_rvalueref;
+         if (implicit_rvalue_p (ref))
+           op1_lvalue_kind |= clk_implicit_rval;
+         return op1_lvalue_kind;
+       }
 
       /* lvalue references and named rvalue references are lvalues.  */
       return clk_ordinary;
index adc088ce1d7b3ef5116eb2f8fa45cdde6627d627..a557f3439a8f49111ddda5533c3ab24cc1d5c120 100644 (file)
@@ -9719,19 +9719,62 @@ can_do_nrvo_p (tree retval, tree functype)
          && !TYPE_VOLATILE (TREE_TYPE (retval)));
 }
 
-/* Returns true if we should treat RETVAL, an expression being returned,
-   as if it were designated by an rvalue.  See [class.copy.elision].
-   PARM_P is true if a function parameter is OK in this context.  */
+/* If we should treat RETVAL, an expression being returned, as if it were
+   designated by an rvalue, returns it adjusted accordingly; otherwise, returns
+   NULL_TREE.  See [class.copy.elision].  RETURN_P is true if this is a return
+   context (rather than throw).  */
 
-bool
-treat_lvalue_as_rvalue_p (tree retval, bool parm_ok)
+tree
+treat_lvalue_as_rvalue_p (tree expr, bool return_p)
 {
+  if (cxx_dialect == cxx98)
+    return NULL_TREE;
+
+  tree retval = expr;
   STRIP_ANY_LOCATION_WRAPPER (retval);
-  return ((cxx_dialect != cxx98)
-         && ((VAR_P (retval) && !DECL_HAS_VALUE_EXPR_P (retval))
-             || (parm_ok && TREE_CODE (retval) == PARM_DECL))
-         && DECL_CONTEXT (retval) == current_function_decl
-         && !TREE_STATIC (retval));
+  if (REFERENCE_REF_P (retval))
+    retval = TREE_OPERAND (retval, 0);
+
+  /* An implicitly movable entity is a variable of automatic storage duration
+     that is either a non-volatile object or (C++20) an rvalue reference to a
+     non-volatile object type.  */
+  if (!(((VAR_P (retval) && !DECL_HAS_VALUE_EXPR_P (retval))
+        || TREE_CODE (retval) == PARM_DECL)
+       && !TREE_STATIC (retval)
+       && !CP_TYPE_VOLATILE_P (non_reference (TREE_TYPE (retval)))
+       && (TREE_CODE (TREE_TYPE (retval)) != REFERENCE_TYPE
+           || (cxx_dialect >= cxx20
+               && TYPE_REF_IS_RVALUE (TREE_TYPE (retval))))))
+    return NULL_TREE;
+
+  /* If the expression in a return or co_return statement is a (possibly
+     parenthesized) id-expression that names an implicitly movable entity
+     declared in the body or parameter-declaration-clause of the innermost
+     enclosing function or lambda-expression, */
+  if (DECL_CONTEXT (retval) != current_function_decl)
+    return NULL_TREE;
+  if (return_p)
+    return set_implicit_rvalue_p (move (expr));
+
+  /* if the operand of a throw-expression is a (possibly parenthesized)
+     id-expression that names an implicitly movable entity whose scope does not
+     extend beyond the compound-statement of the innermost try-block or
+     function-try-block (if any) whose compound-statement or ctor-initializer
+     encloses the throw-expression, */
+
+  /* C++20 added move on throw of parms.  */
+  if (TREE_CODE (retval) == PARM_DECL && cxx_dialect < cxx20)
+    return NULL_TREE;
+
+  for (cp_binding_level *b = current_binding_level;
+       ; b = b->level_chain)
+    {
+      for (tree decl = b->names; decl; decl = TREE_CHAIN (decl))
+       if (decl == retval)
+         return set_implicit_rvalue_p (move (expr));
+      if (b->kind == sk_function_parms || b->kind == sk_try)
+       return NULL_TREE;
+    }
 }
 
 /* Warn about wrong usage of std::move in a return statement.  RETVAL
@@ -9767,6 +9810,7 @@ maybe_warn_pessimizing_move (tree retval, tree functype)
       if (is_std_move_p (fn))
        {
          tree arg = CALL_EXPR_ARG (fn, 0);
+         tree moved;
          if (TREE_CODE (arg) != NOP_EXPR)
            return;
          arg = TREE_OPERAND (arg, 0);
@@ -9786,12 +9830,12 @@ maybe_warn_pessimizing_move (tree retval, tree functype)
          /* Warn if the move is redundant.  It is redundant when we would
             do maybe-rvalue overload resolution even without std::move.  */
          else if (warn_redundant_move
-                  && treat_lvalue_as_rvalue_p (arg, /*parm_ok*/true))
+                  && (moved = treat_lvalue_as_rvalue_p (arg, /*return*/true)))
            {
              /* Make sure that the overload resolution would actually succeed
                 if we removed the std::move call.  */
              tree t = convert_for_initialization (NULL_TREE, functype,
-                                                  move (arg),
+                                                  moved,
                                                   (LOOKUP_NORMAL
                                                    | LOOKUP_ONLYCONVERTING
                                                    | LOOKUP_PREFER_RVALUE),
@@ -10089,19 +10133,26 @@ 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;
-      if (treat_lvalue_as_rvalue_p (retval, /*parm_ok*/true)
-         /* This is only interesting for class type.  */
-         && CLASS_TYPE_P (functype))
-       {
-         tree moved = move (retval);
-         moved = convert_for_initialization
-           (NULL_TREE, functype, moved, flags|LOOKUP_PREFER_RVALUE,
-            ICR_RETURN, NULL_TREE, 0, tf_none);
-         if (moved != error_mark_node)
+      tree moved;
+      /* This is only interesting for class type.  */
+      if (CLASS_TYPE_P (functype)
+         && (moved = treat_lvalue_as_rvalue_p (retval, /*return*/true)))
+       {
+         if (cxx_dialect < cxx20)
            {
-             retval = moved;
-             converted = true;
+             moved = convert_for_initialization
+               (NULL_TREE, functype, moved, flags|LOOKUP_PREFER_RVALUE,
+                ICR_RETURN, NULL_TREE, 0, tf_none);
+             if (moved != error_mark_node)
+               {
+                 retval = moved;
+                 converted = true;
+               }
            }
+         else
+           /* In C++20 we just treat the return value as an rvalue that
+              can bind to lvalue refs.  */
+           retval = moved;
        }
 
       /* The call in a (lambda) thunk needs no conversions.  */
index e2c47a9ec1b2fb1c26195552334f59e3e1958779..c6f36a7143f19e3c07ac96c372aa4cb70b60ea39 100644 (file)
@@ -35,6 +35,7 @@ struct coro1 {
   coro::suspend_always final_suspend () const {  return {}; }
 
   void return_value(T&& v) noexcept { value = std::move(v); }
+  void return_value(const T&) noexcept = delete;
   
   T get_value (void) { return value; }
   void unhandled_exception() { }
@@ -59,9 +60,16 @@ struct MoveOnlyType
   ~MoveOnlyType() { value_ = -2; }
 };
 
+bool b1, b2;
+
 coro1<MoveOnlyType> 
-my_coro ()
+my_coro (MoveOnlyType p, MoveOnlyType &&r)
 {
   MoveOnlyType x{10};
-  co_return x;
+  if (b1)
+    co_return p;
+  else if (b2)
+    co_return r;
+  else
+    co_return x;
 }
index e70f3cde625bb95f22a25c3bb55ee39212127a69..ce4087b476fe375504dfe71762751da0ff260e49 100644 (file)
@@ -60,7 +60,7 @@ fn4 (const T t)
 {
   // t is const: will decay into copy despite std::move, so it's redundant.
   // We used to warn about this, but no longer since c++/87378.
-  return std::move (t);
+  return std::move (t);         // { dg-warning "redundant move" "" { target c++20 } }
 }
 
 int
index 015d7c4f7a447f075e865aa7b9133e15bb9cb5c8..3fec525879d8fbf487329f798c330fc5563c6518 100644 (file)
@@ -28,7 +28,7 @@ struct S2 : S1 {};
 S1
 f (S2 s)
 {
-  return std::move(s); // { dg-bogus "redundant move in return statement" }
+  return std::move(s); // { dg-warning "redundant move in return statement" "" { target c++20 } }
 }
 
 struct R1 {
@@ -40,7 +40,7 @@ struct R2 : R1 {};
 R1
 f2 (const R2 s)
 {
-  return std::move(s); // { dg-bogus "redundant move in return statement" }
+  return std::move(s); // { dg-warning "redundant move in return statement" "" { target c++20 } }
 }
 
 struct T1 {
@@ -55,5 +55,5 @@ f3 (const T2 s)
 {
   // Without std::move: const T1 &
   // With std::move: const T1 &&
-  return std::move(s); // { dg-bogus "redundant move in return statement" }
+  return std::move(s); // { dg-warning "redundant move in return statement" "" { target c++20 } }
 }
index fdd3ce1609206054af41426d67d81bc929fa4404..ca1e23b7a4bcae3b9898e8f7e4c2f0f78b6fa840 100644 (file)
@@ -61,7 +61,7 @@ fn4 (const T<int> t)
 {
   // t is const: will decay into copy despite std::move, so it's redundant.
   // We used to warn about this, but no longer since c++/87378.
-  return std::move (t);
+  return std::move (t);         // { dg-warning "redundant move" "" { target c++20 } }
 }
 
 int
index 4995acd50a5f59d182b376767ce3c1b8e1ac18b8..6a181b27d3761586d1b65df7b60a2084fee98404 100644 (file)
@@ -30,7 +30,7 @@ test1()
 move_only
 test2(move_only&& x)
 {
-    return x;  //  { dg-error "within this context" }
+    return x;  //  { dg-error "within this context" "" { target c++17_down } }
 }
 
 int main()
index 681e9ecaca10cd318f6e21d409727dca8c46b9ba..999f2c95c4935bff6c1cd2bef6a9b67be1e2c118 100644 (file)
@@ -7,5 +7,5 @@ struct S2 : S1 {};
 S1
 f (S2 s)
 {
-  return s; // { dg-error "use of deleted function" }
+  return s; // { dg-error "use of deleted function" "" { target c++17_down } }
 }
index c8bd43643af64612d14e520af37f529dceed7e32..cfbef30022604bc5288651ef5dabec5bb898791b 100644 (file)
@@ -52,14 +52,15 @@ f5 ()
 int
 main ()
 {
+  int return_lval = __cplusplus > 201703L ? -1 : 2;
   Y y1 = f (A());
-  if (y1.y != 2)
+  if (y1.y != return_lval)
     __builtin_abort ();
   Y y2 = f2 (A());
   if (y2.y != -1)
     __builtin_abort ();
   Y y3 = f3 ();
-  if (y3.y != 2)
+  if (y3.y != return_lval)
     __builtin_abort ();
   Y y4 = f4 ();
   if (y4.y != -1)
diff --git a/gcc/testsuite/g++.dg/cpp2a/implicit-move1.C b/gcc/testsuite/g++.dg/cpp2a/implicit-move1.C
new file mode 100644 (file)
index 0000000..4c284a1
--- /dev/null
@@ -0,0 +1,17 @@
+// testcase from P1825R0
+// { dg-do compile { target c++20 } }
+
+struct base {
+    base();
+    base(base const &);
+private:
+    base(base &&);
+};
+
+struct derived : base {};
+
+base f(base b) {
+    throw b;        // { dg-error "" } base(base &&) is private
+    derived d;
+    return d;       // { dg-error "" } base(base &&) is private
+}
diff --git a/gcc/testsuite/g++.dg/cpp2a/implicit-move2.C b/gcc/testsuite/g++.dg/cpp2a/implicit-move2.C
new file mode 100644 (file)
index 0000000..cb13796
--- /dev/null
@@ -0,0 +1,49 @@
+// Testcase from P1825R0
+// { dg-do compile { target c++17 } }
+
+extern "C" void abort();
+
+int m;
+
+struct T
+{
+  int i;
+  T(): i (42) { }
+  T(const T& t) = delete;
+  T(T&& t): i(t.i) { t.i = 0; ++m; }
+};
+
+struct U
+{
+  int i;
+  U(): i (42) { }
+  U(const U& t): i(t.i) { }
+  U(U&& t) = delete;
+};
+
+template <class V> void g(const V&);
+void h();
+
+bool b;
+
+void f()
+{
+  U x;
+  try {
+    T y;
+    try { h(); }
+    catch(...) {
+      if (b)
+        throw x;                // does not move
+      throw y;                  // moves
+    }
+    g(y);
+  } catch(...) {
+    g(x);
+  }
+}
+
+int main()
+{
+  f();
+}
diff --git a/gcc/testsuite/g++.dg/cpp2a/implicit-move3.C b/gcc/testsuite/g++.dg/cpp2a/implicit-move3.C
new file mode 100644 (file)
index 0000000..a1f0b3d
--- /dev/null
@@ -0,0 +1,49 @@
+// Testcase from P1825R0, modified for rvalue refs.
+// { dg-do compile { target c++20 } }
+
+extern "C" void abort();
+
+int m;
+
+struct T
+{
+  int i;
+  T(): i (42) { }
+  T(const T& t) = delete;
+  T(T&& t): i(t.i) { t.i = 0; ++m; }
+};
+
+struct U
+{
+  int i;
+  U(): i (42) { }
+  U(const U& t): i(t.i) { }
+  U(U&& t) = delete;
+};
+
+template <class V> void g(const V&);
+void h();
+
+bool b;
+
+void f()
+{
+  U&& x = U();
+  try {
+    T&& y = T();
+    try { h(); }
+    catch(...) {
+      if (b)
+        throw x;                // does not move
+      throw y;                  // moves
+    }
+    g(y);
+  } catch(...) {
+    g(x);
+  }
+}
+
+int main()
+{
+  f();
+}