So you have heard about lvalues and rvalues for sure, but there actually are also xvalues, prvalues and glvalues. It’s easy to get lost in it, so let’s have a look at what actually are those things.
The picture shows the division of the value categories defined in the draft standard. In this particular case, it’s here
So, first of all, we can see, that glvalue and
Glvalues
Let’s first focus on the glvalues and let’s understand what this actually means, that the expression is a glvalue. So first what actually standard says about the glvalues is that glvalues expressions evaluation determines the identity of an object. In other words, it means, that results of such expressions refer to the concrete object (those are just references to the objects).
How I like to think about about glvalues is: if expression returns reference (T&), or refers to the variable by it’s name, or rvalue reference (T&&) to the object it’s a glvalue expression. Let’s see examples of the glvalues. For the given definitions:
int& foo(){ static int i=0; return ++i; } int a; struct Bar{ int m; }; Bar bar;
Following expressions are glvalues:
"foo()" // expression because returned value refers to static 'i' variable. The return type is int& "a" // expression as it refers to the 'a' can be assigned to int& "std::move(a)" // expression as it still refers to the a variable. return type is int&& "bar" // refers to the bar variable. Can be assigned to Bar& "bar.m" // refers to the m member inside the bar object return type. Can be assigned to int&
But glvalues are divided into lvalues and xvalues, so let’s first see what xvalues are since lvalues are defined as all glvalues which are not xvalues 🙂
So xvalues as already mentioned are glvalues, so still if expression yields a xvalue reference, then this reference references some concrete instance of the object. The xvalues have one more important attribute: citing the standard: An xvalue is a glvalue that denotes an object or bit-field whose resources can be reused (usually because it is near the end of its lifetime) .
So what xvalue means in practice? Well, xvalue used to be an lvalue, but for some reason, it was marked, that its resources can be reused or in other words, the object should be moved instead of copied. That kind of additional marking to the type can be achieved when calling std::move function on the object. Another way of getting the xvalue is through temporary materialization conversion, but we will talk about it in the conversion section.
One more time my way of thinking about xvalues is, that if expression returns type of rvalue reference (T&&), then expression is xvalue. We can get expression of xvalue category type with following rules:
//any call of the function (member function included), which result type is rvalue reference struct Foo{}; Foo&& bar(); int main(){ bar(); // "bar()" is the xvalue expression }
//cast to the rvalue reference. Usually with std::move() function struct Foo{/* definition */}; int main() { Foo a; std::move(a); // "std::move(a)" is xvalue expression since move casts the a to rvalue static_cast<Foo&&>(a); // "static_cast<Foo&&>(a)" is also xvalue expressions because of explicit cast. }
//in case of arrays the "xvalue property" is "propagated" also to the subscript operator int main(){ Foo arr[10] = {}; std::move(arr)[0]; // "std::move(arr)[0]" yields rvalue reference to the first element in the array }
// same thing happens with the access to the class members. template <typename T> struct Foo{ T member; }; int main(){ using non_reference_type=/**/; Foo<non_reference_type> a{}; std::move(a).member; //yields rvalue reference to the member }
// should be no surprise that similar thing happens with pointers to members int main(){ int Foo<int>::* pointer = &Foo<int>::member; Foo<int> foo{}; std::move(foo).*pointer; //yields rvalue reference to the member on same conditions as with the member access case. return 0; }
(offtopic) : If you didn’t have a possibility to see pointers to members in action, then this syntax must be a pain in your eyes. My apologies for that. What you need to know
Some other examples from the standard about xvalues can be found below, where for given definitions:
struct A { int m; }; A&& operator+(A, A); A&& f(); A a; A&& ar = static_cast<A&&>(a);
Following expressions are xvalues:
- f()
- f().m
- static_cast<A&&>(a)
- a + a
So once we know what glvalues are and what xvalues are we also know what
As a summary, we should say, that all expressions, that are glvalues actually yield a handle to some variable with some value. Thus it’s not possible to have the expression of glvalue type, that would have (const volatile) void type (it’s not possible to have
Rvalues
Rvalues as can be seen on the image is a set containing all xvalues and also prvalues. Since we know what xvalues are, we need to have a look at prvalues a little bit closer.
Prvalues are “pure”
I think it’s best to explain the prvalues on the examples.
struct Foo {}; Foo(); // prvalue Foo test(){/*implementation*/}; test(); // prvalue
Type completeness requirements
The glvalues types (lvalue and xvalue) have different characteristics than the prvalues. Because of their nature (being the handle of the object) the expression can yield a value of incomplete type. Since prvalues are values itself they need to have a
template <typename T> T& first_val(T& first, T& second){return first;}
The above function does not need a complete definition of type T, that it operates on. Let’s consider following usage of the function:
// separate translation unit struct foo { }; foo& get_first(){ static foo foo1; return foo1; } foo& get_second(){ static foo foo2; return foo2; }
//the main function, does not see another translation unit struct foo; foo& get_first(); foo& get_second(); template <typename T> T& first_value(T& first, T& second){ return first; } int main() { auto& first = get_first(); auto& second = get_second(); auto& result = first_value(first, second); return 0; }
The whole program compiles and can be run even though in the main translation unit the type foo is not complete. It’s possible because nowhere in the program foo is used as prvalue plus there are no operations directly invoked on the foo type, that would require it’s definition.
Let’s consider the same example, but with changed return type of the first_value template function:
//the main function, does not see another translation unit struct foo; foo& get_first(); foo& get_second(); template <typename T> T first_value(T& first, T& second){ return first; } int main() { auto& const first = get_first(); auto& const second = get_second(); auto& const result = first_value(first, second); return 0; }
Now the return value of the first_value function is prvalue, since it’s not a handle to any of the objects. The program now will fail to compile as the prvalue expressions need complete definition of the type they yield.
On my environment the compilation results in following message:
error: invalid use of incomplete type 'struct foo' error: return type 'struct foo' is incomplete
Expression types conversions
To make developers life easier, there are lots of conversions defined, when types of expressions do not match regarding its value category. The conversions, that will apply might be from glvalue to prvalue expressions or prvalue to xvalue conversions
Glvalue to prvalue conversion
Glvalue to prvalue happens very often and there are many such conversions defined in the C++. They are:
Array to pointer conversion:
// in case you expect the prvalue of type pointer to array // the glvalue of type array is implicitly cast to the prvalue of // required pointer type //here str is accepted as the prvalue of type pointer to const char void printme(const char* str){/*definition*/} int main(){ char str[] = {'a', 'b', 'c', 'd', '\0'}; printme(str); // compiles fine. the glvalue of type char[5] is implicitly converted to // rvalue of type pointer to char. }
The above conversion is called array to pointer
void foo(){} // it's a function void foo2(void(*)()) // a function that accepts prvalue pointer to function void main(){ foo; // it's a glvalue expression, that refers to the function foo foo2(foo); // foo glvalue expression gets implicitly converted to the pointer to function prvalue };
The last conversion from glvalue expression to rvalue expression is simply called lvalue to rvalue conversion (though the name is not precise
First of all, the conversion does not apply to the array and function types as this would otherwise conflict the array to pointer and function to pointer conversions. Another precondition for such conversion is, that when such conversion takes place, the T must be a complete type. This requirement comes from already mentioned prvalues expression requirements. Since prvalue represents the value, the complete type of this value must be known.
Once the preconditions are met the conversion is following:
- For non-class types (class types: class, struct, union) the resulting type is cv unqualified. The resulting object is obtained by copy initialization
- Otherwise (for class types) the resulting type preserves it’s const and volatile qualifications.
I personally think it’s the most often used conversion (no statistics about it) and
void foo(Bar value); Bar bar; foo(bar); // here bar is lvalue but get's converted to the prvalue with copy initialization
Looking at above example it’s easy to realize, that almost every function call in the program relies on the lvalue to rvalue conversion.
Prvalue to xvalue conversion.
There is also vice versa conversion, that is applied if you are trying to pass the prvalue in the place, where you are actually expecting the glvalue. In such a
Such C++ feature, that actually does the object instantiation and casts
When needed, the temporary materialization conversion creates a temporary variable, that is initialized with the value, that prvalue expression holds, and marks that temporary variable as a xvalue. The reason its xvalue is because the temporary variable will soon expire, thus it’s resources are ready to be reused. Thanks to this conversion we can do things like:
struct Foo{int member;}; Foo().member; // Foo() is a temporary materialization conversion, since access to the member requires object itself void bar(const Foo& foo); bar(Foo());
We can see that in action in detail when we try to get the address of the variable. Let’s see the example. If we have got a prvalue expression, we cannot take the address of the result of such expression, because there is no variable, that stands behind the value ( it’s
struct Foo{}; Foo* ptr = &Foo(); // ill-formed
In such case clang produces following error:
error: taking the address of a temporary object of type 'Foo'
But we can take address of the result of such expression after temporary materialization conversion:
void foo(Foo&& test){ std::cout << "ptr to test: " << &test << std::endl; } int main() { foo(Foo()); }
So we can see, that we successfully have taken the address of the variable, thus there must exist some
Expression types and bit fields
The bit fields are a bit special in the C++ world. As you cannot take the pointer to the bit-field itself. That’s something natural as bitfields can occupy less memory than one byte, which is the
But the fact that you cannot take the address to the bit-field doesn’t mean you cannot have the glvalue expression to the bit field.
This example also shows, that expression category does not always have one to one correlation to the type they return.
struct Foo{ // definition char a:3; }; Foo().a;// it is actually a glvalue Foo foo; foo.a = 0; // foo.a is lvalue, you can assign it. auto i = foo.a; // foo.a is a bit-field, so i should be deduced to also be the bit-field, but as bit-fields are automatically converted to bit-field's type, then i is of type character. The statement is valid. auto& j = foo.a; // this actually is ill-formed. you cannot have modifiable glvalue to the bit-field. const auto& k = foo.a; // it's fine k refers to foo.a, but since it's const compiler is free to make a copy of the bitfield.
That’s also one of few differences between the pointers and references 🙂 it’s not possible to take the pointer to the bitfield, be it const or not, but you can take a const reference to the bitfield.
So you can see, that you can have lvalue expression pointing to the bitfield, since you can assign to them. It’s not on the other hand possible to save the reference to the bitfield to the variable manually. This is the slight difference between the expression type and type itself.