Here, we cover one of the basic concepts of the C++ programming language, i.e., lvalue and rvalue, which is often confusing. Before deep diving into the concept and it’s usage, let’s check below code and think about the output:

template<typename T>
void doSomething(T& obj)
{
  std::cout<<obj<<std::endl;
}
int main()
{
    doSomething(10);
    return 0;
}

Both lvalue and rvalue form an expression. lvalue is an object reference that stores the value, whereas rvalue is just value. Do you know what else stores?

int main()
{
    int x = 100; // x is lvalue and 100 is value(rvalue)
    int y = x;   // y is lvalue and x is rvalue
    int &rz = y; // rz is lvalue and y is rvalue
    int *px = &x;// px is lvalue and &x?(Why1) 
    int w = *px; // w is lvalue  and *px?(Why2)
    return 0;
}

In the line (int y = x;), y is lvalue and x becomes rvalue after conversion from lvalue to rvalue. Why1: The pointer px stores the address of variable x, and the address of variable x (&x, which is a location) stores the variable value, i.e., 100. So px is lvalue, x is lvalue, and (&x) is rvalue.

Why2: *px is dereference of pointer, which means value at dereference of pointer is 100. And 100 is rvalue.So px is lvalue and (*px) is rvalue.

Let’s make a small change to the above snippet and walk through:

int main()
{
    int x = 100;    // OK
    int &lrZ1 = x;  // OK
    int &lrZ2 = 10; // NOK (Why3)
    const int &clrZ1 = x;  // OK
    const int &clrZ2 = 10; // OK (Why4)
    clrZ1 = 11;            // NOK
    x = 111;               // OK
    return 0;
}

Why3: A non-const (modifiable) lvalue reference can be bound ONLY to modifiable lvalue (line above), not to rvalue. Why 4: A temporary int object is created and clrZ2 is bound to this temporary object.

int main()
{
    const int cx = 100;    // OK
    int &lrZ1 = cx;        // NOK
    int &lrZ2 = 10;        // NOK 
    const int &clrZ1 = cx; // OK (Why5)
    clrZ1 = 10;            // NOK
    cx = 200;              // NOK 
    return 0;
}

A const (non-modifiable) lvalue reference can be bound to modifiable lvalue, non-modifiable lvalue (Why 5) and also rvalue (Why 4). So we prefer to use const lvalue reference over lvalue reference unless we need to modify the object.

Any guess, Why is it NOK in the below snippet, and what should we do to make it work?

void funWithInputRValue(int x)
{
    //..print the value of x
}
void funWithInputLValue(int& x)
{
    //..print the value of x
}
int getRValue()
{
    int localObj = 10;
    return localObj;
}
int globalVar = 100;
int& getLValue()
{
    return globalVar;
}
int main()
{
    int x = 100; // x is lvalue and 100 is value(rvalue)
    funWithInputRValue(100); //OK
    funWithInputRValue(x);   //OK
    funWithInputLValue(100); //NOK
    funWithInputLValue(x);   //OK
    int &rv = getRValue();   //NOK
    int &rv1 = getLValue();  //OK
    getLValue() = 501;       //OK (Note1)
    return 0;
}

Note1: This operation can be restricted if the function (i.e., getLValue()) returns a non-modifiable lvalue reference instead of a modifiable lvalue reference.

The lvalue and rvalue play an important role in many C++ concepts like operator overloading, exception. Let’s check some of them below.

With Operator Overloading:

Do you know why double preincrement works while postincrement complains?

int main()
{
    int lvalue = 100;
    ++++lvalue; //OK
    std::cout<<lvalue<<std::endl;
    lvalue++++; //NOK (Why6)
    std::cout<<lvalue<<std::endl;
    //int x = ++++5; //NOK (Why7)
    return 0;
}

Why 6: The preincrement operator takes modifiable lvalue and returns modifiable lvalue, while the postincrement operator takes modifiable lvalue and returns rvalue. And we can’t increment on rvalue, i.e., Why7. Checkout link here for details of the overlaoding increment operators.

Do you know why some overloaded binary operators should return by reference?

class Sample
{
    int val;
    public:
    Sample(int x):val(x){}
    Sample(const Sample &obj){}
    Sample& operator=(const Sample &obj)
    {
     //.. 
     return *this;
    }
    int getVal()
    {
      return val;
    }
    friend std::ostream& operator<< (std::ostream& out, const Sample& obj);
};
std::ostream& operator<< (std::ostream& out, const Sample& obj)
{
    out<< "("<< obj.val<< ")";
    return out;
}
int main()
{
    Sample obj(56);
    Sample obj1(5);
    Sample obj2(6);
    std::cout<<obj<<obj1<<obj2<<std::endl; //Note2
    obj = obj1 = obj2; //Note3
}

Note2 and Note3: When overloaded binary operators need to be chainable, like assignment operator overloading and operator<< overloading, the overloaded method should return by reference. This avoids unnecessary object copy because of return by reference.

The pass-by const reference is preferred over the pass-by value to avoid copying. Same is preferred for return values, but there is always an exception for better reason. Let’s check the below snippet:

Sample operator+(const Sample& c1, const Sample& c2)  
{  
   return Sample{ c1.getVal() + c2.getVal() }; //Note4
} 

Note4: It is not recommended to return a pointer or reference to a local stack object (Case-1) or a reference to a heap object (Case-2) if there is a chance that more than one such object will be needed. Because local stack objects (i.e., Case-1) are destroyed when the functions exist and load to the dangling pointer. While later (i.e., Case-2) one leads to resource leak. This pattern is the same for most of the arithmetic operations.

With Exception:

class DerivedException : public std::exception {
public:
    virtual const char* what() const throw() {
       return "DerivedException";
    }
};
void a() {
    try {
        throw DerivedException(); 
    } catch (std::exception e) { //Note5
        std::cout << "Exception is: " << e.what() << std::endl;
    }
}
int main()
{
    a();
    return 0;
}

Pass by value or catch by value (Note5) makes copy, which leads to slicing problems in interitance. So we lost exception information specific to derived class and got the general-wide exception. To get exception information specific to the dervied class, use Catch by reference.

Template non-type parameters

template<int num>
void mul(int input)
{
  std::cout<< input * num << std::endl;
}
int main()
{
  int x = 10;
  std::vector<int> iCollection{1,2,3,4,5};
  for_each(iCollection.begin(), iCollection.end(), mul<x>); //NOK
  for_each(iCollection.begin(), iCollection.end(), mul<10>);//OK
  return 0;
}

Template should be resolved at compile time, so here constant specifier, i.e. rvalue 10 works while usage of lvalue complains.

Rvalue Reference:

The C++11 introduces rvalue references to implement move semantics. And rvalue reference can be bound only to rvalue (Why 8 and Why 9) like lvalue reference can be bound only to lvalue. std::move() takes lvalue and converts to rvalue reference.

void funWithInputLValue(int& x)
{
    //..print the value of x
}
void funWithInputRValue(int&& x)
{
    //..print the value of x
}
int main()
{
    int x = 100;     // OK
    int &&rrY = 10;  // OK 
    int &&rrY1 = x;  // NOK (Why 8) 
    funWithInputLValue(x);   //OK
    funWithInputRValue(rrY1);//OK
    funWithInputRValue(x);   //NOK (Why 9)
    funWithInputRValue(std::move(x));   //OK
    return 0;
}

Summary:

  • The modifiable lvalue reference can be bound ONLY to modifiable lvalue.
  • The non-modifiable lvalue reference can be bound to modifiable lvalue, non-modificable lvalue, and rvalue.
  • The rvalue reference can be bound ONLY to rvalue.
  • The pass-by value or catch-by value makes copy, which leads to slicing problems in the respective scenario.
  • Use return by reference for the chainable action.
  • Reference: Note1 from Item#3 Effective C++.
  • Reference: Note4 from Item#21 Effective C++.
  • Reference: Note5 from Item#20 Effective C++.

Do you know, what is the fastest pit-stop timing in F1 race and how this time reduced significantly? What is similar example in tech industry? Check this link for details.

Do you know, how small mice defeated mighty tanks in WWII?Then click on this link: 10 real-life example from the psychology of money.

Do you know, what is Grand Gesture? Or how did an incomplete task on your last working day ruin your weekend? Then checkout Deep Work Summary.

2 responses to “L & R Value (reference)”

  1. […] Do you know, what is lvalue and rvalue reference? Check this link for details. […]

    Like

  2. […] Do you know what lvalue and rvalue references are? Check this link for details. […]

    Like

Leave a reply to Design around OOP Inheritance Principle – InvestOnSelf Cancel reply

Recent posts

Quote of the week

“God decided where the oil reserves are, we get to decide where the fabs are.”

~ Pat Getsinger