Introduction
Before parsing through the Rvalue references draft in C++11 standard, I never took Lvalues and Rvalues seriously. I even never overhead them among my colleges or 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 Lvalues started appearing as Rvalues. 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 goes to the original authors.
Definitions
An object can be viewed as an 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 valuess 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 lets 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
1. Numeric literals, such as 3 and 3.14159, are Rvalues. So are character literals, such as 'a'.
2. An identifier that names an enumeration constant is an Rvalue. For example:
enum Color { red, green, blue };
Color enumColor;
enumColor = green; blue = green;
3. The result of binary + operator is always an Rvalue
m + 1 = n
4. 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, it's result is an Rvalue.
int n, *p;
p = &n; &n = p;
5. 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;
6. Pre-increment operator expressions results LValues
int nCount = 0; ++nCount;
++nCount = 5;
7. 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;
}
8. 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); }
9. Rvalues are temporaries and doesn't necessarily point to an 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'; }
10. Post-increment operator expressions results RValues
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 preposterous to carry on with a temporary.
Note : Both Lvalues and Rvalues 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
1.
http://msdn.microsoft.com/en-us/library/f90831hc.aspx
2. http://www.eetimes.com/discussion/programming-pointers/4023341/Lvalues-and-Rvalues