What's Hidden Behind the Letters l, r, x, gl, and pr? Understanding Value Categories in C++
\n\nIf you've ever written C++ code and gotten an error like "cannot bind non-const lvalue reference to an rvalue", then you've already encountered this "magic." Value categories — lvalue, rvalue, xvalue, glvalue, and prvalue — are not just an academic classification. They are the foundation upon which move semantics and perfect forwarding are built.
Let's finally understand what each letter means, how to distinguish them in practice, and why without this knowledge it's impossible to write modern, efficient C++.
\n\nA Brief History: Where Did These Five Terms Come From?
\n\nOriginally, back in the days of C, there were only two concepts: lvalue (left value — something that can appear on the left side of an assignment) and rvalue (right value — something that appears on the right side). It was simple: a variable is an lvalue, a number is an rvalue.
\n\nWith the advent of C++11 and move semantics, this simple picture stopped working. Rvalue references (&&) appeared, along with the need to distinguish "dying" temporary objects. Thus, a new classification was born, which the C++ standard describes through five categories.
Take a look at the hierarchy (text diagram):
\n\n expression\n / \\\n glvalue rvalue\n / \\ / \\\nlvalue xvalue prvalue\n\nRemember this picture — it's the key to everything.
\n\nlvalue (left value) — "Persistent" Objects
\n\nlvalue is an expression that refers to an object that has a memory address. Such an object will not disappear after the expression ends. You can take its address, modify it, and it will continue to exist.
\n\nExamples of lvalues:
\n- \n
- Variable name:
int x = 5; xis an lvalue. \n - Dereferenced pointer:
*ptr. \n - Reference to a variable:
int& ref = x; ref. \n - String literal:
"hello"(yes, it's an lvalue, albeit a const one). \n
Code:
\nint main() {\n int a = 10;\n int* p = &a; // Taking address — ok, a is an lvalue\n int& ref = a; // lvalue reference\n\n // &(a + 1); // Error! a+1 is an rvalue, no address\n return 0;\n}\n\nrvalue (right value) — "Disposable" Values
\n\nrvalue is an expression that does not have a permanent memory address. It exists only at the moment the expression is evaluated. You cannot take its address (with rare exceptions).
\n\nrvalues include:
\n- \n
- Literals (except string literals):
42,3.14f,true. \n - Results of arithmetic operations:
a + b. \n - Temporary objects:
std::string("temp"). \n - Lambda expressions. \n
Code:
\nint foo() { return 5; }\n\nint main() {\n int x = 42; // 42 is an rvalue\n int y = x + 10; // x+10 is an rvalue\n // int* p = &(x + 10); // Error! Cannot take address of an rvalue\n return 0;\n}\n\nprvalue (pure rvalue) — "Pure" rvalues
\n\nprvalue is the classic rvalue from C++98. It is a temporary value that has no name and is not bound to memory. This is what we usually call an rvalue in casual conversation.
\n\nExamples of prvalues:
\n- \n
- Numbers, enumerations, results of operators. \n
- Call to a function returning a non-reference:
std::to_string(123). \n - Explicitly created temporary object:
MyClass(). \n
xvalue (expiring value) — "Dying" Objects
\n\nNow here's where it gets interesting. xvalue is an object that is about to be destroyed, but its resources can still be "saved" (moved). This is a mos