C++:ValueCategories
lastmod 2018-08-17created 2018-06-081 edits
About
Before C++11, C++ value categories formed a simple classification of just lvalue and rvalue. With C++11, however, xvalue, glvalue, and prvalue were added, making the classification more complex. This post looks at the value categories as changed in C++11.
- until c++03
- Before learning the values newly added in C++11, we must first be clear about what lvalue and rvalue, which existed up to C++03, are. A common mistake is to think that an lvalue is a value that can appear on the left of the assign operator (operator =) and an rvalue is one that cannot. When the concept of lvalue first appeared in C, distinguishing lvalues by the assign operator was correct. But since the const qualifier was added in C89, that no longer holds, because a const variable is an lvalue yet cannot appear on the left of the assign operator.
- So C89 defined lvalue not as left value but as locator value, that is, a locator value that refers not to the actual value but to the address where the value is. In other words, a variable is a locator and therefore an lvalue, while numeric literals like 1 and 2, character literals like 'a' and 'b', or the result of a function call do not refer to a specific address and so are not lvalues. Among lvalues, those without the const qualifier are classified separately as modifiable lvalues, and only modifiable lvalues can appear on the left of the assign operator. C++98, which came later, also followed the C89 definition of lvalue, so an lvalue in C++98 can be seen as a locator too. However, unlike C89, there are functions that return references, so when a function's result type is a reference, the result of calling the function is an lvalue.
- identity
- C++11 describes the property that existing lvalues have as having identity. Saying an expression has identity means that one can compare whether that expression and another expression refer to the same thing (the original says entity, and I couldn't find a translation for it). For example, given variables a and b, we can compare their addresses to see whether they point to the same value, so variables are lvalues. But for numeric literals or boolean literals, we cannot compare whether two expressions are the same entity, so they are rvalues. Also, temporaries that vanish once the expression is passed, such as calls to functions whose return type is not a reference or type casts, are rvalues. That is, since an lvalue is an expression whose entity exists beyond the expression, having identity is also described as having persistence.
- move
- C++11 added one more property besides identity, because move, the most famous of C++11's new features, was added. Not every value can be moved, so in C++ whether a value can be moved into another value has now become a very important property too.
- From here on, a value that can be moved is called m, and one that cannot is called M. Also, a value that has the identity described above is called i, and one that does not is called I. Combining whether it has identity and whether it can be moved gives four combinations: iM, im, Im, IM. But among these, IM, that is, a value that has no identity and cannot be moved, has no reason to exist, so we only need to look at the three combinations iM, im, and Im.
- iM - lvalue
- iM, that is, an expression that has identity but cannot be moved, is called an lvalue in C++11. It is the lvalue definition up to C++03 with the added condition that it cannot be moved. The biggest characteristic of an lvalue is that only lvalues can be used as operands of the & operator; Im cannot be used as an operand of the & operator because it has no identity, and iM because it may get moved.
- im - xvalue
- im, which has identity but can be moved, is called xvalue, from eXpiring. As the name says, an xvalue is a value that is expiring. So when the expression ends and you access the address the expression referred to, the value may or may not exist. The result of a function returning an rvalue reference, or an expression type-casting to an rvalue rference, is an xvalue. std::move is also a function that returns an rvalue reference, so a moved value belongs to xvalue, and it is used to make an lvalue movable.
- Im - prvalue
- Finally, Im, which has no identity, is called prvalue, meaning pure rvalue. It can be considered the same as the rvalue up to C++03. prvalue is the only category in C++ without identity. The result of calling a function that returns a non-reference, a type cast to a non-reference, or numeric and boolean literals belong here.
- Also, because it has no identity, a prvalue, unlike other values, cannot be of an incomplete type, since the value must exist at the point where the expression is used.
- m - rvalue
- The reason Im is pure rvalue rather than rvalue is that rvalue exists separately. Before C++03, values without identity were called rvalues, but in C++11, m, that is, a value that can be moved, is called an rvalue. So both xvalue (im) and prvalue (Im) belong to rvalue, and im, the lvalue, which cannot be moved, is not an rvalue.
- i - glvalue
- Just as lvalue and rvalue are divided by whether something can be moved, glvalue and prvalue are divided by whether it has identity. So iM (lvalue) and im (xvalue) are classified as glvalue. Having identity means the object remains even after the expression ends, and the expression is merely something that points to the object. So lvalues or rvalues belonging to glvalue can be of an incomplete type and can be polymorphic.