Lvalues and Rvalues in C++

Summary

In this post, I will introduce lvalues and rvalues in C/C++. But I leave rvalue references and move semantics to the next post.

Details

Definition

  1. Lvalues: (locator value) represents an object that occupies some unique location in memory (has an address in some scope).
  2. Rvalues: not lvalues.

Important points and examples

1. Rvalues can’t be assigned

An assignment expects an lvalue as its left operand. See the codes below.

// lvalue in left operand
int var;
var = 4;

// rvalue in left operand
4 = var;       // ERROR!
(var + 1) = 4; // ERROR!

2. Function can return lvalue or rvalue

We can assign value returned by a function. See the codes below.

// error: lvalue required as left operand of assignment
int foo() {return 2;}

int main()
{
    foo() = 2;

    return 0;
}

// good 
int globalvar = 20;

int& foo()
{
    return globalvar;
}

int main()
{
    foo() = 10;
    return 0;
}

Actually [] in map works by using this.

std::map<int, float> mymap;
// [] returns a reference that can be assigned to
mymap[10] = 5.6;

3. Unmodifiable lvalues

const int a = 10; // 'a' is an lvalue
a = 10;           // but it can't be assigned!

4. Conversions between lvalues and rvalues

Implicit lvalues-to-rvalues

Language constructs operating on object values require rvalues as arguments. All lvalues that aren’t arrays or functions can be converted to rvalues.

int a = 1;     // a is an lvalue
int b = 2;     // b is an lvalue
int c = a + b; // + needs rvalues, so a and b are converted to rvalues
               // and an rvalue is returned
Explicit lvalues-to-rvalues

The unary address-of operator & takes an lvalue argument and produces an rvalue.

int var = 10;
int* bad_addr = &(var + 1); // ERROR: lvalue required as unary '&' operand
int* addr = &var;           // OK: var is an lvalue
&var = 40;                  // ERROR: lvalue required as left operand
                            // of assignment. 
Explicit rvalues-to-lvalues

Implicit rvalues-to-lvalues is not possible. But we can use things like unary operator * (dereference), which takes an rvalue argument but produces an lvalue as a result.

int arr[] = {1, 2};
int* p = &arr[0];
// good: p + 1 is an rvalue, but *(p + 1) is an lvalue
*(p + 1) = 10;   

5. Lvalue references

& can produce “lvalue references”. It takes a lvalue and return a lvalue reference. Non-const lvalue references cannot be assigned rvalues, since that would require an invalid rvalue-to-lvalue conversion.

std::string& sref1 = "Hello world!"; // ERROR: invalid initialization of non-const reference of type 'std::string&' from an rvalue of type 'std::string'
const std::string& sref2 = "Hello world!"; // good 
std::string s("Hello world!");
std::string& sref3 = s; // good 

Reference

Eli Bendersky’s website

Leave a Reply

Your email address will not be published. Required fields are marked *