You are here:

C++/lValue required

Advertisement


Question
What is lValue? Some of my C++ programs flag an error:
"lValue required".
Any help will be greatly appreciated.

Binoy

Answer
The short answer is that an lvalue is something that can appear on the left hand side of an assignment. That is the 'l' in lvalue stands for left. From this you can probably guess that there are also rvalues - things that can appear on the right hand side of an assignment. From these descriptions we can deduce that an lvalue can of course be converted to an rvalue:

   int a(1234);
   int b(4321);
   
   // a and b are lvalues
   
   a = b;   // lvalue b converted to rvalue

However the converse is _not_ true: an rvalue cannot be converted to an lvalue. It is this that the compiler is complaining about.

Of course this is C++ so the reality is not as simple as laid out above. The ISO C++ standard has a whole section on lvalues and rvalues - section 3.10 should you be interested. It lists 15 points - and not that many of them make the situation that much clearer for us users of C++ (rather than those that implement C++ - e.g. by writing compilers).

Rvalues tend cases where you get unnamed temporary objects or when using literal values (e.g. the result of an expression such as x+3 is an rvalue). Lvalues are named, or references to, objects or functions. So if your code seems to be giving the compiler an rvalue when it wants an lvalue try breaking the expression / statement into parts and give a name to the rvalue part the compiler is complaining about. You generally require an lvalue to modify an object.

For example the standard includes the following example of a function that returns a reference to an int:

   int & f();

Calling f yields an lvalue - and indeed such an expression can be used on the left side of an assignment:

   f() = 1234;

The above assigns 1234 to whatever int the returned int reference from f refers to.

Likewise expressions that yield a de-referenced pointer are also lvalues:

   int * f2();
   
   *(f2()+3) = 1234;   // assigns 1234 to the 3rd int-sized location after the address returned by f2
   
Or even just:

   int * p = ...;      // some initialisation expression that initialises p to a reasonable value
   
   *p = 1234;          // assign 1234 to whatever int p point to.

On the other hand if a function returns a value:

   int f3();

Then the result of calling such a function is an rvalue - the returned int value is unnamed and temporary, that is it only exists until the end of the statement in which it appears (or until any references initialised to refer to it go out of scope). Of course we can _copy_ it to a (named) lvalue:

   int f3Value = f3();

In the above statement the returned rvalue from the call to f3 will be destroyed at the end of the statement. It is f3Value, the named lvalue copy, that lives on beyond the statement. (In fact in such simple cases as shown above the temporary returned by functions such as f3 can be elided - left out - if the compiler can work out how to place returned value directly into an lvalue such as f3Value - this is the return value optimisation or RVO. Without it assignment of values from functions such as f3 usually involve making 2 copies of the returned value: one from the usually local object or expression value returned from within the function to the rvalue returned by the function then another from the rvalue to the lvalue on the left side of an assignment).

So while:

   f3() = 1234;

is illegal: writing to the temporary int returned from f3 is illegal as it is an rvalue and would make no sense in any case - how would you use the result of the assignment?

   int f3Value = f3();
   f3Value = 1234;     // overwrite the copy of the returned value from f3 with 1234

is OK.

Another case where we get rvalues as opposed to lvalues is type conversions including casting and  explicitly creation of an object using functional notation (e.g. long('a')). That is type conversions can produce unnamed temporary values. (An lvalues is produced if the target type is a reference type).

Note that you can get rvalues from expressions used to pass values to functions. If the function takes a non-const reference then the compiler assumes the referred to value is likely to be modified within the function and requires an lvalue to store the result of any such modifications so the modifications can be seen beyond the statement the function call is part of. To pass an rvalue by reference you require a const reference to something - indicating that the value (probably) will not be modified. For example:

   #include <iostream>
   #include <stdexcept>
   
   void PassByConstRef( int const & in )
   {
       if ( in < 100 || in > 1000 )
       {
         throw std::out_of_range( "Expected parameter value in range [100, 1000]" );
       }
   }

   int main()
   {
       try
       {
         int v(101);
         PassByConstRef( v );
         PassByConstRef( v*4 );
         PassByConstRef( 1234 );
       }
       catch ( std::exception & e )
       {
         std::cerr << "Error: " << e.what();
       }
   }

In the above example I call a function that takes one parameter passed by const reference, called in as it is in effect an input parameter. The function just checks the passed in value is in some required range and throws an exception if it is not.

The main function calls the PassByConstRef using various argument forms for the in parameter - a named lvalue, the result of an expression and a literal value. All are either rvalues or converted to rvalues. The example should compile and execute (and not cause an exception for the shown value of v, but should for the literal 1234 argument value).

Consider the following variation however:

   void PassByRef( int & inOut )
   {
       if ( inOut < 100 )
       {
         inOut = 100;
       }
       else if ( inOut > 1000 )
       {
         inOut = 1000;
       }
   }
   
   int main()
   {
       int v(101);
       PassByRef( v );
       PassByRef( v*4 );
       PassByRef( 1234 );
   }

In this variation the function PassByRef takes its inOut parameter by non-const reference. This means the function is free to modify the value of the referenced object - hence the value is passed both in and out. It performs a similar check to the PassByConstRef function except in this case rather than throwing an exception for out of range values it modifies them to be clamped to the minimum or maximum value.

If we compile this version we find that the compiler is happy with:

       PassByRef( v );

as v is an lvalue. But complains about:

       PassByRef( v*4 );
       PassByRef( 1234 );

As in each case a temporary rvalue would need to be created to pass the value of the expression or literal as a reference to the PassByRef function. Any changes to this temporary value would be lost at the end of the statement so the language disallows it as such errors would cause much confusion if allowed.This is especially true in the light of silent conversions - for example from int to double, viz:
   
   void PassByConstRef( double const & in )
   {
       if ( in < 100 || in > 1000 )
       {
         throw std::out_of_range( "Expected parameter value in range [100, 1000]" );
       }
   }

   void PassByRef( double & inOut )
   {
       if ( inOut < 100 )
       {
         inOut = 100;
       }
       else if ( inOut > 1000 )
       {
         inOut = 1000;
       }
   }

Here I have changed the type passed to the functions from int (const) reference to double (const) reference. If we try building the two main versions using these versions of the functions the PassByConstRef usage is still OK, However for the PassByRef version we now find that all three calls produce errors. This is because the

       PassByRef( v );

call would need to convert the int v to a temporary rvalue double before passing a reference to a double.

We can fix the PassByValue cases by using an intermediate double variable (or int variable for the original version):

   int main()
   {
       int v(101);

       double dv(v);
       PassByRef( dv );

       dv = v*4;
       PassByRef( dv );

       dv = 1234;
       PassByRef( dv );
   }

Of course in real code we would presumably be using the value returned from PassByRef - which might be called something more realistic such as ClampToRange or some such.

Note that the alternative is to ensure that parameters passed by reference are _always_ specified const unless they really need to be modified within the function. As qualifying something as being const in C++ involves something extra to do it is often forgotten - so it is a good idea to check for cases in your code where const could (should) be specified from time to time - even if the compiler has not pointed  the need for such qualification out to you already!

Hope this gives you some idea as to what lvalues and rvalues are and possible causes for compliant from your compiler and a few hints on how to get around such problems.  

C++

All Answers


Answers by Expert:


Ask Experts

Volunteer


Ralph McArdell

Expertise

I am a software developer with more than 15 years C++ experience and over 25 years experience developing a wide variety of applications for Windows NT/2000/XP, UNIX, Linux and other platforms. I can help with basic to advanced C++, C (although I do not write just-C much if at all these days so maybe ask in the C section about purely C matters), software development and many platform specific and system development problems.

Experience

My career started in the mid 1980s working as a batch process operator for the now defunct Inner London Education Authority, working on Prime mini computers. I then moved into the role of Programmer / Analyst, also on the Primes, then into technical support and finally into the micro computing section, using a variety of 16 and 8 bit machines. Following the demise of the ILEA I worked for a small company, now gone, called Hodos. I worked on a part task train simulator using C and the Intel DVI (Digital Video Interactive) - the hardware based predecessor to Indeo. Other projects included a CGI based train simulator (different goals to the first), and various other projects in C and Visual Basic (er, version 1 that is). When Hodos went into receivership I went freelance and finally managed to start working in C++. I initially had contracts working on train simulators (surprise) and multimedia - I worked on many of the Dorling Kindersley CD-ROM titles and wrote the screensaver games for the Wallace and Gromit Cracking Animator CD. My more recent contracts have been more traditionally IT based, working predominately in C++ on MS Windows NT, 2000. XP, Linux and UN*X. These projects have had wide ranging additional skill sets including system analysis and design, databases and SQL in various guises, C#, client server and remoting, cross porting applications between platforms and various client development processes. I have an interest in the development of the C++ core language and libraries and try to keep up with at least some of the papers on the ISO C++ Standard Committee site at http://www.open-std.org/jtc1/sc22/wg21/.

Education/Credentials

©2016 About.com. All rights reserved.