When a variable that is about to go out of scope is returned or thrown, its resources can be reused i.e. it can be moved from, as the following C++ program shows:
#include <iostream>
struct X {
X() {
std::cout << "X()\n";
}
X(X&&) {
std::cout << "X(X&&)\n";
}
~X() {
std::cout << "~X()\n";
}
};
X f() {
X x;
std::cout << "return\n";
return x;
}
int main() {
try {
X x = f();
std::cout << "throw\n";
throw x;
} catch (...) {
std::cout << "catch\n";
}
return 0;
}
Compilation (we turn off copy/move elision with the flag -fno-elide-constructors
), linkage, and execution:
clang++ -std=c++17 -O2 -Wall -pedantic -pthread -fno-elide-constructors\
main.cpp && ./a.out
Output:
X()
return
X(X&&)
~X()
throw
X(X&&)
~X()
catch
~X()
x
in the above statements return x;
and throw x;
denotes an object whose resources can be reused.
In the Working Draft, Standard for Programming Language C++, [basic.lval-1], we have the following definitions for value categories:
- A glvalue is an expression whose evaluation determines the identity of an object or function.
- An xvalue is a glvalue that denotes an object whose resources can be reused (usually because it is near the end of its lifetime).
- An lvalue is a glvalue that is not an xvalue.
So is x
an lvalue or an xvalue?
x
is an lvalue itself.
The following expressions are lvalue expressions:
- the name of a variable, a function
, a template parameter object (since C++20)
, or a data member, regardless of type, such as std::cin or std::endl. Even if the variable's type is rvalue reference, the expression consisting of its name is an lvalue expression;
For local variables as x
, in return statement and throw expression, overload resolution for initialization is performed in two-phases; firstly as if x
is an rvalue expression (then move constructor might be selected).
In return statement:
(Since C++11) then overload resolution to select the constructor to use for initialization of the returned value
or, for co_return, to select the overload of promise.return_value() (since C++20)
is performed twice:
- first as if expression were an rvalue expression (thus it may select the move constructor), and
In throw expression:
- This may also call the move constructor for lvalue expressions if they name local variables or function or catch-clause parameters whose scope does not extend past the innermost enclosing try-block (if any), by same two-step overload resolution as in return statement (since C++17)
As the effect the move constructor is selected in both cases. This is just special for return
and throw
, doesn't mean x
is rvalue
or xvalue
entirely. if you write sth like X x2(x);
in f()
, the copy constructor will be selected (and causes error because the copy constructor is implicitly-deleted).
From the standard, [class.copy.elision]/3:
An implicitly movable entity is a variable of automatic storage duration that is either a non-volatile object or an rvalue reference to a non-volatile object type. In the following copy-initialization contexts, a move operation is first considered before attempting a copy operation:
(3.1) - If the expression in a return ([stmt.return]) or co_return ([stmt.return.coroutine]) 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, or
(3.2) - if the operand of a throw-expression ([expr.throw]) is a (possibly parenthesized) id-expression that names an implicitly movable entity that belongs to a scope that does not contain the compound-statement of the innermost try-block or function-try-block (if any) whose compound-statement or ctor-initializer contains the throw-expression,
overload resolution to select the constructor for the copy or the return_value overload to call is first performed as if the expression or operand were an rvalue. If the first overload resolution fails or was not performed, overload resolution is performed again, considering the expression or operand as an lvalue.
The expression is an lvalue if the entity is a function, variable, structured binding, data member, or template parameter object and a prvalue otherwise ([basic.lval]);
(1.1) - A glvalue is an expression whose evaluation determines the identity of an object or function.
(1.2) - A prvalue is an expression whose evaluation initializes an object or computes the value of an operand of an operator, as specified by the context in which it appears, or an expression that has type cv void.
(1.3) - An xvalue is a glvalue that denotes an object whose resources can be reused (usually because it is near the end of its lifetime).
(1.4) - An lvalue is a glvalue that is not an xvalue.
(1.5) - An rvalue is a prvalue or an xvalue.
[Note 3: An expression is an xvalue if it is:
(4.1) - the result of calling a function, whether implicitly or explicitly, whose return type is an rvalue reference to object type ([expr.call]),
(4.2) - a cast to an rvalue reference to object type ([expr.type.conv], [expr.dynamic.cast], [expr.static.cast] [expr.reinterpret.cast], [expr.const.cast], [expr.cast]),
(4.3) - a subscripting operation with an xvalue array operand ([expr.sub]),
(4.4) - a class member access expression designating a non-static data member of non-reference type in which the object expression is an xvalue ([expr.ref]), or
(4.5) - a .* pointer-to-member expression in which the first operand is an xvalue and the second operand is a pointer to data member ([expr.mptr.oper]).