C++/lValue required
Expert: Ralph McArdell - 6/1/2008
QuestionWhat is lValue? Some of my C++ programs flag an error:
"lValue required".
Any help will be greatly appreciated.
Binoy
AnswerThe 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.