/* 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)
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
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. */
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)
&& 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) \
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
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)
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 */
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
/* 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. */
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 ();
&& 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;
&& !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
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);
/* 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),
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. */
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() { }
~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;
}
{
// 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
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 {
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 {
{
// 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 } }
}
{
// 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
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()
S1
f (S2 s)
{
- return s; // { dg-error "use of deleted function" }
+ return s; // { dg-error "use of deleted function" "" { target c++17_down } }
}
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)
--- /dev/null
+// 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
+}
--- /dev/null
+// 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();
+}
--- /dev/null
+// 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();
+}