Introduction
Before parsing through the Rvalue
references draft in C++11 standard, I never took Lvalue
s and Rvalue
s seriously. I even never overhead them among my colleagues or read about them in any C++ books (or may be I would have skipped that part thinking it to be of no importance). The only place I find them often is in compilation errors, like : error C2106: '=' : left operand must be Lvalue
. And just by looking at the statement/expression that generated this error, I would understand my stupidity and would graciously correct it with no trouble.
int NextVal_1(int* p) { return *(p+1); }
int* NextVal_2(int* p) { return (p+1); }
int main()
{
int a[] = {1,2,3,4,5};
NextVal_1(a) = 9; *NextVal_2(a) = 9; }
I hope with the above code you got what I am saying.
When I went on to read that RValue
reference section of C++0x, my vision and confidence started shaking a bit. What I took for granted as Lvalue
s started appearing as Rvalue
s. In this article, I will try to simplify and consolidate various concepts related to L & R values. And I feel it necessary to upload this article first, before updating C++11 – A Glance [part 1 of n]. I promise to update it also soon.
Please note that this effort mainly involves gathering scattered information and organizing it in a simple form so that one may not need to Google it again. All credits go to the original authors.
Definitions
An object can be viewed as a region of storage and this storage region can either be just observable or modifiable or both depending on the access specifier associated with it. What I mean is:
int i; const int j = 8;
Before proceeding to the definitions, please memorize this phase: "The notion of Lvalueness or Rvalueness is solely on the expression and nothing to do with the object." Let me simplify it:
double d;
Now d
is just an object of type double
[and thrusting l/r valueness upon d
at this stage is meaningless]. Now once this goes into an expression say like:
d = 3.1414 * 2;
then the whole concept of l/r values originates. Here, we are having an assignment expression with d
on one side and a numerical expression on another side which evaluates to a temporary value and will disappear after semicolon. The 'd
' which points to an identifiable memory location is an Lvalue
and (3.1414*2) which is a temporary is an Rvalue
.
At this point, let's define them
Lvalue
: An Lvalue
is an expression referring to an object, [which holds some memory location] [The C Programming Language - Kernighan and Ritchie]
Rvalue
: The C++ standard defines r-value by exclusion - "Every expression is either an Lvalue
or an Rvalue
." So an Rvalue
is any expression that is not an Lvalue
. To be precise, it is an expression that does not necessarily represent an object holding identifiable memory region (it may be temporary).
Points on Lvalues and Rvalues
- Numeric literals, such as
3
and 3.14159
, are Rvalue
s. So are character literals, such as 'a
'. - An identifier that names an enumeration constant is an
Rvalue
. For example:
enum Color { red, green, blue };
Color enumColor;
enumColor = green; blue = green;
- The result of binary
+
operator is always an Rvalue
.
m + 1 = n
- The unary
&
(address-of) operator requires an Lvalue
as its operand. That is, &n
is a valid expression only if n
is an Lvalue
. Thus, an expression such as &3
is an error. Again, 3
does not refer to an object, so it's not addressable. Although the unary &
requires an Lvalue
as its operand, its result is an Rvalue
.
int n, *p;
p = &n; &n = p;
- In contrast to unary &, unary * produces an
lvalue
as its result. A non-null
(valid) pointer p
always points to an object, so *p
is an lvalue
. For example:
int a[N];
int *p = a;
*p = 3;
*(p + 1) = 4;
- Pre-increment operator expressions results
LValue
s:
int nCount = 0; ++nCount;
++nCount = 5;
- A function call is an
Lvalue
if and only if the result type is a reference.
int& GetBig(int& a, int& b) {
return ( a > b ? a : b );
}
void main()
{
int i = 10, j = 50;
GetBig( i, j ) *= 5;
}
- A reference is a name, so a reference bound to an
Rvalue
is itself an Lvalue
:
int GetBig(int& a, int& b) {
return ( a > b ? a : b );
}
void main()
{
int i = 10, j = 50;
const int& big = GetBig( i, j );
int& big2 = GetBig(i, j); }
Rvalue
s are temporaries and don't necessarily point to a memory region but they may hold memory in some cases. It is not advisable to catch this address and do any further operations as it would be a booby trap to work on these temporaries.
char* fun() { return "Hellow"; }
int main()
{
char* q = fun();
q[0]='h'; }
- Post-increment operator expressions results
RValue
s:
int nCount = 0; nCount++
nCount++ = 5;
By summarizing the above points, we can blindly state that: If we can take address of an expression (for further operations) safely, then it is a lvalue
expression, else it is an rvalue
expression. It makes sense right, as it is preposterous to carry on with a temporary.
Note: Both Lvalue
s and Rvalue
s could be modifiable or non-modifiable. Here are the examples:
string strName("Hello"); const string strConstName("Hello"); string JunkFunction() { return "Hellow World"; }const string Fun() { return "Hellow World"; }
Conversion between Lvalues and Rvalues
Can an Lvalue
appear in a context that requires an Rvalue
? YES, it can. For example:
int a, b;
a = 8;
b = 5;
a = b;
This =
expression uses the Lvalue
sub-expression b
as Rvalue
. In this case, the compiler performs what is called lvalue
-to-rvalue
conversion to obtain the value stored in b
.
Now, can an r-value appear in a context that requires an l-value. NO, it can't.
3 = a
Acknowledgments
Thanks to Clement Emerson for readily helping me in gathering and organizing this information.
External Resources
- http://msdn.microsoft.com/en-us/library/f90831hc.aspx