C++ – Understanding lvalues and rvalues

It is important to know the distinction between an l-value and an r-value if you are to be a serious C++ programmer. This article is intended to provide a basic understanding of their differences.

Every object in C++ is either an l-value or an r-value. Those objects (ie, the identifier that names the object) that can be deferenced to obtain their addresses (in other words, those objects whose addresses can be obtained) are called as l-values. And any object that is not an l-value is referred to as an r-value.

In an assignment operation, an r-value can only appear on the right side. However, an l-value can appear on either side. All literals (6, ‘c’ etc) are r-values. So are enumeration constants and temporary objects. Temporary objects are those that are unnamed and created by the compiler.

int main(){

    int a = 5; // 'a' is an l-value and '5' is an r-value
    
    int *b = new int(4); // b is an l-value
    
    int e = 5 + a; // 5 and (5+a) are r-values
    
    Enum day{Monday, Tuesday, Wednesday};
    day d = Monday // Here, 'd' is an l-value and 'Monday' is an r-value
}

Sometimes, its useful to get a reference to the temporary unnamed objects a.k.a r-values. For example, C++11 introduced something known a ‘move semantics‘ which, in a nutshell, allows the moving of resources in a more optimal way by avoiding the creation of unnecessary copies of temporary unnamed objects. I am not going to get into the details of move semantics in this post. It deserves a separate article.

C++11 renamed the so called ‘references’ in C++03 to ‘l-value references’. This was done to accommodate the new ‘r-value references’ which bind to r-values. Move semantics are implemented via the use of these r-value references. The behavior of ‘l-value references’ in C++11 is pretty much similar to ‘references’ in C++03. An l-value reference can only be bind to an l-value unless the l-value reference is to a constant type. However, an r-value reference can only bind to an r-value.

int main(){

    int a1 = 5;
    int& b1 = a1; // OK. 'b1' is an l-value reference. 'a1' is an l-value.

    int&c1 = 1; // ERROR. An l-value reference cannot bind to an r-value.

    const int&d1 = 2; // OK. An l-value reference to a const type can
                      // bind to an r-value

    int a2 = 5;
    int&&b2 = a2; // ERROR. An r-value reference cannot bind to an l-value
    int&&c2 = 1; // OK.
}

The single most important use of ‘r-value references’ is to implement move operations including move construction and move assignment. Like I said before, I am going to dedicate a separate post to discuss it.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google photo

You are commenting using your Google account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s