From: Jason Merrill Date: Tue, 21 Jul 2020 04:19:49 +0000 (-0400) Subject: c++: Implement C++20 implicit move changes. [PR91427] X-Git-Url: https://git.libre-soc.org/?a=commitdiff_plain;h=1722e2013f05f1f1f99379dbaa0c0df356da731f;p=gcc.git c++: Implement C++20 implicit move changes. [PR91427] 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. --- diff --git a/gcc/cp/call.c b/gcc/cp/call.c index e283d635d60..f164b211c9f 100644 --- a/gcc/cp/call.c +++ b/gcc/cp/call.c @@ -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 diff --git a/gcc/cp/coroutines.cc b/gcc/cp/coroutines.cc index 84b6a4edec5..8bebbe3f9e1 100644 --- a/gcc/cp/coroutines.cc +++ b/gcc/cp/coroutines.cc @@ -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 *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 *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. */ diff --git a/gcc/cp/cp-tree.h b/gcc/cp/cp-tree.h index 2377fc052bb..ea4871f836a 100644 --- a/gcc/cp/cp-tree.h +++ b/gcc/cp/cp-tree.h @@ -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 diff --git a/gcc/cp/except.c b/gcc/cp/except.c index aca54f136ba..cb1a4105dae 100644 --- a/gcc/cp/except.c +++ b/gcc/cp/except.c @@ -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. */ diff --git a/gcc/cp/pt.c b/gcc/cp/pt.c index d9db44f919d..6a42cf93dcc 100644 --- a/gcc/cp/pt.c +++ b/gcc/cp/pt.c @@ -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 (); diff --git a/gcc/cp/tree.c b/gcc/cp/tree.c index a830c90a78f..e8606602bd2 100644 --- a/gcc/cp/tree.c +++ b/gcc/cp/tree.c @@ -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; diff --git a/gcc/cp/typeck.c b/gcc/cp/typeck.c index adc088ce1d7..a557f3439a8 100644 --- a/gcc/cp/typeck.c +++ b/gcc/cp/typeck.c @@ -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. */ diff --git a/gcc/testsuite/g++.dg/coroutines/co-return-syntax-10-movable.C b/gcc/testsuite/g++.dg/coroutines/co-return-syntax-10-movable.C index e2c47a9ec1b..c6f36a7143f 100644 --- a/gcc/testsuite/g++.dg/coroutines/co-return-syntax-10-movable.C +++ b/gcc/testsuite/g++.dg/coroutines/co-return-syntax-10-movable.C @@ -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 -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; } diff --git a/gcc/testsuite/g++.dg/cpp0x/Wredundant-move1.C b/gcc/testsuite/g++.dg/cpp0x/Wredundant-move1.C index e70f3cde625..ce4087b476f 100644 --- a/gcc/testsuite/g++.dg/cpp0x/Wredundant-move1.C +++ b/gcc/testsuite/g++.dg/cpp0x/Wredundant-move1.C @@ -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 diff --git a/gcc/testsuite/g++.dg/cpp0x/Wredundant-move7.C b/gcc/testsuite/g++.dg/cpp0x/Wredundant-move7.C index 015d7c4f7a4..3fec525879d 100644 --- a/gcc/testsuite/g++.dg/cpp0x/Wredundant-move7.C +++ b/gcc/testsuite/g++.dg/cpp0x/Wredundant-move7.C @@ -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 } } } diff --git a/gcc/testsuite/g++.dg/cpp0x/Wredundant-move9.C b/gcc/testsuite/g++.dg/cpp0x/Wredundant-move9.C index fdd3ce16092..ca1e23b7a4b 100644 --- a/gcc/testsuite/g++.dg/cpp0x/Wredundant-move9.C +++ b/gcc/testsuite/g++.dg/cpp0x/Wredundant-move9.C @@ -61,7 +61,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 diff --git a/gcc/testsuite/g++.dg/cpp0x/elision_neg.C b/gcc/testsuite/g++.dg/cpp0x/elision_neg.C index 4995acd50a5..6a181b27d37 100644 --- a/gcc/testsuite/g++.dg/cpp0x/elision_neg.C +++ b/gcc/testsuite/g++.dg/cpp0x/elision_neg.C @@ -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() diff --git a/gcc/testsuite/g++.dg/cpp0x/move-return2.C b/gcc/testsuite/g++.dg/cpp0x/move-return2.C index 681e9ecaca1..999f2c95c49 100644 --- a/gcc/testsuite/g++.dg/cpp0x/move-return2.C +++ b/gcc/testsuite/g++.dg/cpp0x/move-return2.C @@ -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 } } } diff --git a/gcc/testsuite/g++.dg/cpp0x/ref-qual20.C b/gcc/testsuite/g++.dg/cpp0x/ref-qual20.C index c8bd43643af..cfbef300226 100644 --- a/gcc/testsuite/g++.dg/cpp0x/ref-qual20.C +++ b/gcc/testsuite/g++.dg/cpp0x/ref-qual20.C @@ -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 index 00000000000..4c284a13cae --- /dev/null +++ b/gcc/testsuite/g++.dg/cpp2a/implicit-move1.C @@ -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 index 00000000000..cb1379648c9 --- /dev/null +++ b/gcc/testsuite/g++.dg/cpp2a/implicit-move2.C @@ -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 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 index 00000000000..a1f0b3d0740 --- /dev/null +++ b/gcc/testsuite/g++.dg/cpp2a/implicit-move3.C @@ -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 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(); +}