This paper addresses only the first case. While we believe that reliable NRVO ("named return value optimization", the second bullet) is an important feature to allow reasoning about performance, the cases where NRVO is possible are subtle and a simple guarantee is difficult to give.
struct NonMoveable { /* ... */ }; NonMoveable make() { /* how to make this work without a copy? */ }As a result, programmers are forced to work around this limitation via dynamic memory allocation or similar.
auto x = make(); // error, can't perform the move you didn't want, // even though compiler would not actually call it
struct NonMoveable { NonMoveable(int); NonMoveable(NonMoveable&) = delete; void NonMoveable(NonMoveable&) = delete; std::array<int, 1024> arr; }; NonMoveable make() { return NonMoveable(42); // ok, directly constructs returned object } auto nm = make(); // ok, directly constructs 'nm'
However, these rules are hard to internalize and confusing -- for instance, an expression that creates a temporary object designates an object, so why is it not an lvalue? Why is NonMoveable().arr an xvalue rather than a prvalue? This paper suggests a rewording of these rules to clarify their intent. In particular, we suggest the following definitions for glvalue and prvalue:
- An lvalue (so called, historically, because lvalues could appear on the left-hand side of an assignment expression) designates a function or an object. [ Example: If E is an expression of pointer type, then *E is an lvalue expression referring to the object or function to which E points. As another example, the result of calling a function whose return type is an lvalue reference is an lvalue. - end example ]
- An xvalue (an "eXpiring" value) also refers to an object, usually near the end of its lifetime (so that its resources may be moved, for example). Certain kinds of expressions involving rvalue references (8.3.2) yield xvalues. [ Example: The result of calling a function whose return type is an rvalue reference to an object type is an xvalue (5.2.2). - end example ]
- A glvalue ("generalized" lvalue) is an lvalue or an xvalue.
- An rvalue (so called, historically, because rvalues could appear on the right-hand side of an assignment expression) is an xvalue, a temporary object (12.2) or subobject thereof, or a value that is not associated with an object.
- A prvalue ("pure" rvalue) is an rvalue that is not an xvalue. [ Example: The result of calling a function whose return type is not a reference is a prvalue. The value of a literal such as 12, 7.3e5, or true is also a prvalue. - end example ]
Denotationally, we have:
glvalue :: Environment -> (Environment, Location)
prvalue :: (Environment, Location) -> Environment
So far, this is not a functional change to C++; it does not change the classification of any existing expression. However, it makes it simpler to reason about why expressions are classified as they are:
struct X { int n; }; extern X x; X{4}; // prvalue: represents initialization of an X object x.n; // glvalue: represents the location of x's member n X{4}.n; // glvalue: represents the location of X{4}'s member n; // in particular, xvalue, as member is expiring using T = X[2]; T{{5}, {6}}; // prvalue: represents initialization of an array of 2 X's T{{5}, {6}}[0]; // xvalue: represents location of expiring array element
Now we have a simple description of value categories, we can reconsider how expressions in those categories should behave. In particular, given a class type A, the expression A() is currently specified as creating a temporary object, but this is not necessary: because the purpose of a prvalue is to performs initialization, it should not be the responsibility of the A() expression to create a temporary object. That should instead be performed by the context in which the expression appears, if necessary. However, in many contexts, it is not necessary to create this temporary object. For instance:
// make() is a prvalue (it returns "by value"). Therefore, it models the // initialization of an object of type NonMoveable. NonMoveable make() { // The object initialized by 'make()' is initialized by the following // constructor call. return NonMoveable(42); } // Use 'make()' to directly initialize 'nm'. No temporary objects are created. auto nm = make(); NonMoveable x = {5}; // ok today NonMoveable x = 5; // equivalent to NonMoveable x = NonMoveable(5), // ill-formed today (creates a temporary but can't move it), // ok under this proposal (does not create a temporary object)
We conclude that a prvalue expression of class or array type should not create a temporary object. Instead, the temporary object is created by the context where the expression appears, if it is necessary. The contexts that require a temporary object to be created ("materialized") are as follows: