Lvalues and Rvalues

Lvalues and Rvalues

By Mikael Kilpeläinen

Overload, 12(61):, June 2004


The two concepts, lvalue and rvalue , can be somewhat confusing in C++. Nevertheless, the difference is important to understand. The basic consequences related to these concepts are interesting and good to know in many cases.

In C++ every expression yields either an lvalue or an rvalue and accordingly every expression is called either an lvalue or an rvalue expression. An example of an lvalue is an identifier. As a further example, a reference to an object is an lvalue. Every expression that is not an lvalue is an rvalue. A good example of this is an expression that produces an arithmetic value. An intuitive approach would be to think of expressions as functions and then an lvalue can be thought as the result of a function returning a reference.

Examples:

  1. The subscript operator is a function of the form T& operator[](T*, ptrdiff_t) and therefore A[0] is an lvalue where A is of array type.

  2. The dereference operator is a function of the form T& operator*(T*) and hence *p is an lvalue where p is of pointer type.

  3. The negate operator is of the form T operator-(T) and therefore -x is an rvalue.

The terms lvalue and rvalue were inherited from C. The original meaning comes from the assignment: an lvalue being the left side of the assignment and rvalue the right side. However, in the modern C++, lvalue can be considered more as a locator value . An lvalue refers to a defined region of storage. Although, this is not true with the function lvalues since functions are not objects. Similarly, an rvalue can be considered as a value of an expression. This separation of two concepts helps to define and talk about things, though some would say that it has caused more confusion. Nevertheless, exact definitions are needed to clarify a language.

An rvalue should not be confused with the constness of an object. An rvalue does not mean the object would be immutable. There is some confusion about this, since non-class rvalues are non-modifiable. This is not the case with user types. A class rvalue can be used to modify an object through its member functions. Albeit in practice, it can be said that objects are modified only through modifiable lvalues . A modifiable lvalue is an lvalue that can be used to modify the object. Other lvalues are non-modifiable lvalues, const reference is a good example of this.

As mentioned already, non-class rvalues do not have the same qualities as the user type rvalues. One might wonder about this. After all, C++ was designed so that user types would behave like built-ins, at least as uniformly as possible. Still this inconsistency exists and the reasons for it shall be explored later. Non-class rvalues are not modifiable, nor can have cv-qualified types (the cv-qualifications are ignored). On the contrary, the class rvalues are modifiable and can be used to modify an object via its member functions. They can also have cv-qualified types. In case of built-ins, some operators require an lvalue as does every assignment expression as the left side. The built-in address-of operator requires an lvalue which reflects the character of lvalues rather well.

Examples:

int var = 0;
var = 1 + 2; // ok, var is an lvalue here
var + 1 = 2 + 3; // error, var + 1 is an rvalue
int* p1 = &var; // ok, var is an lvalue
int* p2 = &(var + 1); // error, var + 1 is an rvalue
UserType().member_function(); // ok, calling a
                              // member function of the class rvalue

The only real reason I can think of for needing the class rvalues to be modifiable, is to allow the calling of the nonconst members of the proxies [ 1 ] and similar. That is, a proxy represents a type and it ought to behave accordingly. Keeping this in mind when considering coherent behaviour along with the const-correctness, one would make the mutating members non-const. For this to work the modifiable rvalues are needed. Although this may seem quite irrelevant, it is actually quite an important reason. It can be used to emulate lvalue behaviour and hence many things are made possible. Thinking about the modifiable class rvalues more closely, they enable many usable concepts and there are only a few rare cases when that introduces problems. After all, the big difference between the built-in types and the user types is that the user types can have members. This difference effectively makes the non-class rvalues non-modifiable.

An rvalue cannot be used to initialise non-const reference. That is, an rvalue cannot be converted to an lvalue, but when an lvalue is used in a context where an rvalue is expected, the lvalue is implicitly converted to an rvalue. This binding restriction and the modifiable class rvalue lead to interesting consequences. It allows us to call all member functions for a user type but not mutating free functions. This can be confusing as one needs to know whether an operator is a member or not, and after all it is not consistent. The same problem motivates us to implement operators as non-members where possible, for consistency with built-in types. Also, since the member functions can be called, the called function can return a non-const reference to the object itself. This means that a modifiable lvalue referring to a temporary object can be created, making it possible to call a function that takes a non-const reference. Time has shown this to be very dangerous as it allows mistakes that can be hard to find, mostly because of the implicit conversions. That ought not to be the case here, since it is not easy to make such a mistake by accident.

Examples:

struct A {
A& operator=(const A&) { return *this; }
};
void func(A&);
..
func(A() = A()); // fine, operator= yields an lvalue
ofstream("some") << some_variable; // fine as long
// as the operator<< is a member

Forbidding the binding of rvalues to non-const references does not come without problems, however. It makes it difficult to provide uniform behaviour with unusual copy semantics, like those of auto_ptr , which is why they are a bad idea. This is why a special reference has been proposed for the next C++ standard. It would only bind to an rvalue. The proposal actually makes a distinction between the rvalue and the lvalue references by introducing a new syntax. This would allow detection of an rvalue which is crucial for the move semantics [ 2 ] . The proposed resolution would effectively allow the uniform behaviour but still not compromise on the safety issues.

The biggest problem with non-consistency seems to be the confusion among the people. Of course it would be desirable to be consistent in the eyes of purists but you cannot always get it all. After taking a little trouble to understand, an lvalue and an rvalue are quite easy concepts.

Acknowledgement

I would like to thank Rani Sharoni for providing helpful comments.

References and Related Links

[ISO14882] ISO/IEC 14882-1998 Standard for the C++ language

[ISO9899] ISO/IEC 9899-1999 Programming language C

[Koenig-] Andrew Koenig and Barbara E. Moo, Accelerated C++, Practical Programming by Example , 2000, Addison Wesley

[Stroustrup] Bjarne Stroustrup, The Design and Evolution of C++ , 1994, Addison Wesley

[Gamma-] Erich Gamma, Richard Helm, Ralph Johnson, John Vlissides, Design Patterns: Elements of Reusable Object-Oriented Software , 1995, Addison Wesley

[Hinnant-] Howard E. Hinnant, Peter Dimov and Dave Abrahams, A Proposal to Add Move Semantics Support to the C++ Language , 2002, http://std.dkuug.dk/jtc1/sc22/wg21/docs/papers/2002/n1377.htm

[auto_ptr] auto_ptr update, Scott Meyers, http://www.awprofessional.com/content/images/020163371X/autoptrupdate%5Cauto_ptr_update.html

[Alexandrescu] Generic<Programming>: Move Constructors, Andrei Alexandrescu, http://www.cuj.com/documents/s=8246/cujcexp2102alexandr/alexandr.htm [Editor's Note: This link is broken; C/C++ User's Journal is out of business]



[ 1 ] Proxy: provide a surrogate or placeholder for another object to control access to it.

[ 2 ] A proposal to add support for move semantics to the C++ has been made and the rvalue-reference proposal is part of it.






Your Privacy

By clicking "Accept Non-Essential Cookies" you agree ACCU can store non-essential cookies on your device and disclose information in accordance with our Privacy Policy and Cookie Policy.

Current Setting: Non-Essential Cookies REJECTED


By clicking "Include Third Party Content" you agree ACCU can forward your IP address to third-party sites (such as YouTube) to enhance the information presented on this site, and that third-party sites may store cookies on your device.

Current Setting: Third Party Content EXCLUDED



Settings can be changed at any time from the Cookie Policy page.