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.
This commit is contained in:
Jason Merrill 2020-07-21 00:19:49 -04:00
parent 81bc0ec3e9
commit 1722e2013f
17 changed files with 277 additions and 72 deletions

View 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

View 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);
}
tree arg = expr;
if (tree moved = treat_lvalue_as_rvalue_p (expr, /*return*/true))
arg = moved;
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);
}
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. */

View 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

View 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. */

View 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 ();

View 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;

View 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;
/* This is only interesting for class type. */
if (CLASS_TYPE_P (functype)
&& (moved = treat_lvalue_as_rvalue_p (retval, /*return*/true)))
{
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)
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. */

View 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;
}

View 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

View 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 } }
}

View 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

View 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()

View 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 } }
}

View 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)

View File

@ -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
}

View File

@ -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();
}

View File

@ -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();
}