l-values and r-values

T

Taras_96

Hi everyone,

I think I mostly understand l-values and r-values after reading a few
archived posts on this board:

* l-values usually appear on the LHS of an assignment, r-values
usually appear on the RHS of an assignment

An r-value might appear on the LHS of an assignment because of the
following:

* C++ allows a member function to be invoked on a temporary object
* A function returning by value is an r-value
* The assignment operator can be a member function of a UDT

The combination of these factors means that an r-value can appear on
the LHS of an assignment operator:

Foo GetTemp() {return Foo;}

GetTemp() = 5; // which is equivalent to
GetTemp.operator=(5); //

(this of course assumes that the appropriate conversion constructors
and assignment operators are provided for class Foo)

However, I still don't understand how a string literal can be
considered an l-value. The following excerpts hint at an answer:

"String literal: because early C did not have 'const', and so old C
functions that take 'char*' as argument could not be called with
literal strings as actual arguments if string literals were considered
rvalues. However, that may still change. It's just an old
compatibility feature on its way out" - http://tiny.cc/yEr2A

"char *x and char x[] are often used to refer to strings (null
terminated arrays of characters), and they differ in their "lvalue-
ness". " - http://tiny.cc/9JJBG

Wouldn't the following:

void foo(char *);

foo("Hello");

be equivalent to:

char * blah = "Hello";
foo(blah);

? I don't see how this effects the l-valueness of a string literal

Also, is it a defining characteristic of an r-value that you can not
take the address of an r-value (obviously this is true for most cases,
I was wondering if it is, in fact, a defining characteristic, or
whether there are some exceptions)

Taras
 
J

jason.cipriani

Hi everyone,

I think I mostly understand l-values and r-values after reading a few
archived posts on this board:

 * l-values usually appear on the LHS of an assignment, r-values
usually appear on the RHS of an assignment

An r-value might appear on the LHS of an assignment because of the
following:

 * C++ allows a member function to be invoked on a temporary object
 * A function returning by value is an r-value
 * The assignment operator can be a member function of a UDT

The combination of these factors means that an r-value can appear on
the LHS of an assignment operator:

Foo GetTemp() {return Foo;}

GetTemp() = 5; // which is equivalent to
GetTemp.operator=(5); //

(this of course assumes that the appropriate conversion constructors
and assignment operators are provided for class Foo)

However,  I still don't understand how a string literal can be
considered an l-value. The following excerpts hint at an answer:

"String literal: because early C did not have 'const', and so old C
functions that take 'char*' as argument could not be called with
literal strings as actual arguments if string literals were considered
rvalues.  However, that may still change.  It's just an old
compatibility feature on its way out" -http://tiny.cc/yEr2A

"char *x and char x[] are often used to refer to strings (null
terminated  arrays of characters), and they differ in their "lvalue-
ness".  " -http://tiny.cc/9JJBG


This might be referring to differences between the actual types
"char*" and the type "char[]". For example:

char *x;
x = "Something"; // This is valid (although unsafe, see below).
char y[10];
y = "Something"; // This is not allowed. Note y is on LHS.

That's not to say you can't convert both to a char *:

char *z;
char *x = NULL;
z = x; // This is valid.
char y[10];
z = y; // This is also valid. Note y is on RHS.

And that's the same as this:

void foo (char *z);
char *x = NULL;
foo(x); // This is valid.
char y[10];
foo(y); // This conversion is also valid.

Wouldn't the following:

void foo(char *);

foo("Hello");

be equivalent to:

char * blah = "Hello";
foo(blah);


Also note that this is a special case; string literals are able to be
converted to non-const just to main compatibility with old code, but
aren't guaranteed to be in a modifiable part of memory. Therefore:

char *blah = "Hello";
blah[0] = 'J';

Is officially undefined. Whereas:

char array[] = "Hello";
char *blah = array;
blah[0] = 'J';

Is perfectly safe.

In general you should not try to write new code that takes string
literals as non-const char*. Perhaps consider this instead:

void foo (const char *);
const char *blah = "Hello";
foo(blah);

I.e. You should never write a line of code like this:

char *blah = "Hello";


Jason
 
J

James Kanze

I think I mostly understand l-values and r-values after
reading a few archived posts on this board:
* l-values usually appear on the LHS of an assignment, r-values
usually appear on the RHS of an assignment

Historically, that's where the distinction (and the names) come
from. Today, I'd say that the concept is far more arbitrary;
some operators return rvalues, others lvalues and some require
rvalues, others lvalues. And the only real definition of the
terms is the list of these requirements.
An r-value might appear on the LHS of an assignment because of
the following:
* C++ allows a member function to be invoked on a temporary object
* A function returning by value is an r-value
* The assignment operator can be a member function of a UDT
The combination of these factors means that an r-value can
appear on the LHS of an assignment operator:

Yes, because a user defined assignment operator isn't an
assignment operator. Once overload resolution has chosen it,
it is a function call, and it obeys the rules of function calls,
not the rules of assignment operators.
Foo GetTemp() {return Foo;}
GetTemp() = 5; // which is equivalent to
GetTemp.operator=(5); //

You mean
GetTemp().operator=( 5 ) ;
for the second line, of course.
(this of course assumes that the appropriate conversion
constructors and assignment operators are provided for class
Foo)
However, I still don't understand how a string literal can be
considered an l-value.

It's an array, and all arrays are l-values.
The following excerpts hint at an answer:
"String literal: because early C did not have 'const', and so
old C functions that take 'char*' as argument could not be
called with literal strings as actual arguments if string
literals were considered rvalues. However, that may still
change. It's just an old compatibility feature on its way
out" -http://tiny.cc/yEr2A

This has nothing to do with lvalue-ness. In C, the type of a
string literal is char[]. In C++, it is char const[], but there
is a special, limited (and I think deprecated) conversion to
char*, to avoid breaking existing code.
"char *x and char x[] are often used to refer to strings (null
terminated arrays of characters), and they differ in their
"lvalue- ness". " -http://tiny.cc/9JJBG

This is just wrong. In both cases, x is an lvalue. They
differ in type, not in lvalue-ness.
Wouldn't the following:
void foo(char *);

be equivalent to:
char * blah = "Hello";
foo(blah);
? I don't see how this effects the l-valueness of a string
literal

Yes, and it doesn't.
Also, is it a defining characteristic of an r-value that you
can not take the address of an r-value (obviously this is true
for most cases, I was wondering if it is, in fact, a defining
characteristic, or whether there are some exceptions)

Well, it's certainly related to the original sense: a lvalue
always designated an object, an rvalue (originally) was just a
value, without an object. In C++, if the rvalue has class type,
then there is an object (and you can obtain its address). Also,
you can initialize a reference to const with an rvalue, and then
take its address. The result is that the distinction
lvalue/rvalue is purely arbitrary.
 
T

Taras_96

Do you understand the actual definition of an lvalue?  An lvalue
refers to an object or function.  Do you understand the definition of
an object?  An object is a region of storage.  So a reference to
anything that occupies storage is a valid lvalue.

Stroustrup states that: "there is a need for a name for "something in
memory." This is the simplest and most fundamental notion of an
object. That is, an /object/ is a contiguous region of storage; an /
lvalue/ is an expression that refers to an object"

But in a sense, the '5' in the expression:

int i = 5;

would be stored somewhere in memory (ultimately, unless it is
optimised out). I suppose that '5' can not be an l-value, because it
isn't an expression that refers to an object (it actually is the
object itself).

Additionally, K&R states that: "An object is a manipulatable region of
storage; an lvalue is an expression referring to an object"

I suppose with that in mind you can't really modify the value of 5, so
with this definition '5' isn't an object.

I'm thinking of an r-value more like a 'concept' rather than a an
object, if that makes any sense. You can not change the 'concept' of
the number 5, 5 will always be 5. This is re-inforced in C++ by only
allowing to take a constant reference to temporaries:

const int & rr = 3;

Otherwise, you could do something like this:

rr = 4;

which would then change 3 to be equal to 4, which doesn't make sense!
 
T

Taras_96

Historically, that's where the distinction (and the names) come
from.  Today, I'd say that the concept is far more arbitrary;
some operators return rvalues, others lvalues and some require
rvalues, others lvalues.  And the only real definition of the
terms is the list of these requirements.

That's why I stated 'usually' :)
 
T

Taras_96

Your posts made me dig deeper into TCPL, and there it was in 5.2.2:

"The type of a string literal is ‘‘array of the appropriate number of
c o n s t characters,’’ so " B o h r " is
of type c o n s t c h a r []"

So thus a string literal is an l-value.

Your responses say that a string literal is static and in a non-
modifiable area of memory. Also, Bjarne writes "A string literal is
statically allocated so that it is safe to return one from a function"

Is this in any way similar to the Java implementation of Strings,
where a string is immutable and contained within a string pool (I
realise that this is a c++ group, so apologies), in that in C++ the
same string may refer to the same memory location (this is
implementation defined) - assigning two literal strings that are the
same to different pointers is conceptually similar to assigning the
integer 5 to two different variables? Was there any reason apart from
efficiency than choosing this meaning rather than 'each string literal
creates a separate object'?

Taras
 
A

Andrey Tarasevich

Taras_96 said:
...
I think I mostly understand l-values and r-values after reading a few
archived posts on this board:

* l-values usually appear on the LHS of an assignment, r-values
usually appear on the RHS of an assignment

Firstly, the above applies specifically to _built-in_ assignment
operator, not to every assignment in the program. Once we start talking
about user-defined assignment operators, all bets are off. There's no
relationship between lvalues/rvalues and user-defined assignment operators.

Secondly, once we restrict the consideration to built-in assignment
operator, the word "usually" becomes important. It is true that the
built-in assignment requires a [modifiable] lvalue on its LHS, this is
in no way a defining property of lvalue.

As for the RHS, it doesn't make much sense to say that an rvalue usually
appears there. Normally, just about anything can appear on the RHS. It
is correct to say that the RHS of an assignment is _used_ _as_ an
rvalue, but not correct o say that it _is_ an rvalue.
An r-value might appear on the LHS of an assignment because of the
following:

* C++ allows a member function to be invoked on a temporary object
* A function returning by value is an r-value
* The assignment operator can be a member function of a UDT

You seem to be talking about the overladed assignment here. Once again,
the overloaded assignment has virtually no connection to the distinction
between lvalues and rvalues. It doesn't make much sense to get it
involved at all.
However, I still don't understand how a string literal can be
considered an l-value. The following excerpts hint at an answer:

"String literal: because early C did not have 'const', and so old C
functions that take 'char*' as argument could not be called with
literal strings as actual arguments if string literals were considered
rvalues. However, that may still change. It's just an old
compatibility feature on its way out" - http://tiny.cc/yEr2A

"char *x and char x[] are often used to refer to strings (null
terminated arrays of characters), and they differ in their "lvalue-
ness". " - http://tiny.cc/9JJBG

You seem to be using a bad source for your information. In both C and
C++ the term 'lvalue' is not tied to the "LHS of an assignment" in any
way. It is true that this is how this term was born, but in the end its
meaning was modified to something completely different. In a simplified
form, an lvalue is something that has a location in storage (address in
memory). In C the term 'lvalue' is synonymous with the term 'object'. In
C++ it is defined to also include functions and has some other
intricacies, but the general idea of "location in memory" remains the same.

If you want to express it through operators, an lvalue in C++ is
something you can apply the built-in unary operator '&' to (note: not
the LHS of the assignment operator, but the operand of address-of
operator). This is in no way an exact criterion, but it conveys the
general idea rather well.
Wouldn't the following:

void foo(char *);

foo("Hello");

be equivalent to:

char * blah = "Hello";
foo(blah);

? I don't see how this effects the l-valueness of a string literal

It doesn't. Pointers always point to lvalues in C and C++. The source
you are using doesn't make any sense.
Also, is it a defining characteristic of an r-value that you can not
take the address of an r-value (obviously this is true for most cases,
I was wondering if it is, in fact, a defining characteristic, or
whether there are some exceptions)

It works one way only: you can't take the address (i.e. use the built-in
unary '&') on an rvalue. However, just because you can't use the
built-in '&' on something doesn't mean it is an rvalue.
 
S

Stefan Ram

Taras_96 said:
But in a sense, the '5' in the expression:
int i = 5;

This line is not an expression, but a declaration.
would be stored somewhere in memory

»5« is an integer literal. What is being stored is its value.
For example, the literal »5« and »05« have the same value.
I suppose that '5' can not be an l-value, because it isn't an
expression that refers to an object (it actually is the object
itself).

»5« is not an object at all. »5« is an integer literal.
I suppose with that in mind you can't really modify the value
of 5, so with this definition '5' isn't an object.

The value of literals is given by the C++ specification or
sometimes by the C++ implementation, it can not be modified
by a program.
I'm thinking of an r-value more like a 'concept' rather than a
an object, if that makes any sense.

Yes, it makes some sense. But thinking of an r-value as
an object in the first place is so wrong that there actually
is no need to deny it. (»I am thinking of a car more like a
vehicle than a building.«)
You can not change the 'concept' of the number 5, 5 will always be 5.

Yes.

(Beyond C++:
5 is not a physical system (like an object) that can change in
time. It is a mathematical value that is not related to time.)
 
J

James Kanze

This line is not an expression, but a declaration.
»5« is an integer literal. What is being stored is its value.
For example, the literal »5« and »05« have the same value.
»5« is not an object at all. »5« is an integer literal.

I think his point was that the value 5 must be stored somewhere
in memory. Probably as part of a load immediate machine
instruction, or some such, but it is still stored in memory.

I think the distinction he is missing is that C and C++ are
designed to support Harvard architectures, as well as von
Neumann architectures, and that conceptually, at least, "code"
(functions, etc.) isn't accessible memory. And that an rvalue
may never be in memory, accepssible or not; while the literal 5
will doubtlessly occur somewhere in code, the results of an
expression like "i + j" will typically never be in "memory", but
only in a register (which is, of course, also memory, but not
"accessible" memory, at least not from C/C++).
The value of literals is given by the C++ specification or
sometimes by the C++ implementation, it can not be modified
by a program.

But that has nothing to do with lvalue/rvalue. There are
lvalues that you cannot modify. (In C++, there are also rvalues
that you can modify, but that wasn't true in the original C.)
Yes, it makes some sense. But thinking of an r-value as an
object in the first place is so wrong that there actually is
no need to deny it. (»I am thinking of a car more like a
vehicle than a building.«)

An rvalue is a value, period. An lvalue is a reference to an
"object". Except that in C++, rvalues of class type are also
"objects", so the distinction becomes more or less arbitrary,
defined by the standard.
 
J

James Kanze

Your posts made me dig deeper into TCPL, and there it was in 5.2.2:
"The type of a string literal is ??array of the appropriate number of
c o n s t characters,?? so " B o h r " is
of type c o n s t c h a r []"
So thus a string literal is an l-value.
Your responses say that a string literal is static and in a
non-modifiable area of memory.

I didn't say that, and nor does the standard. Where the
compiler puts a string literal is not specified; all that the
standard says is that if you attempt to modify it, your program
has undefined behavior.

(A bit off the thread topic, but historically, the first edition
of K&R required that string literals be modifiable, and that
each instance of a string literal be a separate array. Very
much a misfeature, which was corrected by the C standard, but a
misfeature that was used in enough code that every compiler I
know has at least an option to support it, if it doesn't do so
by default.)
Also, Bjarne writes "A string literal is statically allocated
so that it is safe to return one from a function"
Is this in any way similar to the Java implementation of
Strings, where a string is immutable and contained within a
string pool (I realise that this is a c++ group, so
apologies), in that in C++ the same string may refer to the
same memory location (this is implementation defined) -
assigning two literal strings that are the same to different
pointers is conceptually similar to assigning the integer 5 to
two different variables?

Not really. First, in Java, even a string literal is a class
type (albeit a very special one), this is not the case in C++.
In Java, string literals are required to be pooled, and you can
force pooling of strings you create; any pooling in C++ is
implementation dependent.

The main motivation for string pooling, in Java, is
optimization; if you have two pooled strings, comparison for
equality can be shallow, and not deep. It's really only
interesting in very special cases. (The one case I needed it,
it wasn't for a string, but for a more complicated structure
known as a DN.)
Was there any reason apart from efficiency than choosing this
meaning rather than 'each string literal creates a separate
object'?

I suspect that history plays an even greater role than
efficiency in the C++ definitions.
 
S

Stefan Ram

James Kanze said:
I think his point was that the value 5 must be stored somewhere
in memory.

At least during compilation, but not always at run-time, as in
»if( 2 < 5 )f();«. Taras_96 was aware of this, because
he referred to it as »optimization«. But even without
»optimization« or references to specific C++-implementation,
I believe on can say that ISO/IEC 14882:2003(E) does not
specify anywhere that the value of »5« in »if( 2 < 5 )f();«
has to be »stored« anywhere.
An rvalue is a value, period.

Yes.
An lvalue is a reference to an "object".

Yes (»reference« in the general sense of the word, not in
the C++ sense of name created by a reference declaration).
 
E

Erik Wikström

An rvalue is a value, period. An lvalue is a reference to an
"object". Except that in C++, rvalues of class type are also
"objects", so the distinction becomes more or less arbitrary,
defined by the standard.

Isn't the distinction here that while an lvalue of class type is a
*reference* (in the general sense or the word) to an object, an rvalue
of class type *is* an object?

Thinking of it like this means that an lvalue can be converted to a
rvalue simply by "dereferencing" the lvalue.
 
J

James Kanze

On 2008-12-10 10:25, James Kanze wrote:
Isn't the distinction here that while an lvalue of class type
is a *reference* (in the general sense or the word) to an
object, an rvalue of class type *is* an object?

Define the general sense of "reference". Both lvalues and
rvalues of class type have addresses, and the address can be
determined (e.g. by calling a member function, then using the
this pointer). For all practical intents and purposes, lvalues
and rvalues of class type are identical; the only difference is
that the standard says that certain operators require an lvalue,
and others require an rvalue.
Thinking of it like this means that an lvalue can be converted
to a rvalue simply by "dereferencing" the lvalue.

And a class type rvalue can be "converted" to an lvalue by
calling a member function. In neither case does anything change
with regards to the object.
 
S

Stefan Ram

James Kanze said:
Define the general sense of "reference".

Given a function f, a value x from the domain of f
sometimes is being called »a /reference/ (to f(x))«.

For example, the postal address of a building is a reference
to the building. »f« here is a well-known function (look-up
scheme) mapping postal addresses to geographic locations in
the world.
 
T

Taras_96

Define the general sense of "reference".  Both lvalues and
rvalues of class type have addresses, and the address can be
determined (e.g. by calling a member function, then using the
this pointer).  For all practical intents and purposes, lvalues
and rvalues of class type are identical; the only difference is
that the standard says that certain operators require an lvalue,
and others require an rvalue.


And a class type rvalue can be "converted" to an lvalue by
calling a member function.  In neither case does anything change
with regards to the object.

Can you ever reference a class type rvalue without first using the
corresponding lvalue?

eg:

Foo foo;
Foo b = foo; // here the l-value foo is converted to an r-value, and
then used to initialise b

If this is correct, could you provide an example of a class type
rvalue being converted to an lvaue by calling a member function?

Taras
 
T

Taras_96

I didn't say that, and nor does the standard.  Where the
compiler puts a string literal is not specified; all that the
standard says is that if you attempt to modify it, your program
has undefined behavior.

Sorry, that was meant to be 'your' plural.
(A bit off the thread topic, but historically, the first edition
of K&R required that string literals be modifiable, and that
each instance of a string literal be a separate array.  Very
much a misfeature, which was corrected by the C standard,

Why is this a misfeature?
 
J

James Kanze

Can you ever reference a class type rvalue without first using
the corresponding lvalue?

That's a good question, at least if I understand it correctly.
Unless I've missed something (likely, since I haven't thought
about it that much), all possible uses of a class type rvalue do
require it to have an address, and act in all ways as if it were
an lvalue.
Foo foo;
Foo b = foo; // here the l-value foo is converted to an r-value, and
then used to initialise b

Not really. Here, the l-value foo is used to initialize the
reference parameter of the copy constructor.

A more interesting case would be:

Foo f() ;

Foo b = f() ;

Here, the rvalue is used to initialize the reference
parameter---in other words, it is behaving like an lvalue, or is
immediately converted to an lvalue (depending on how you want to
word it---but a reference is an lvalue, no matter how you look
at it).
If this is correct, could you provide an example of a class
type rvalue being converted to an lvaue by calling a member
function?

The above, except that you don't even need to call a member
function. Otherwise, of course, the classical:

std::eek:stringstream().flush() << ...

std::eek:stringstream() is, of course, not an lvalue. But flush()
returns a reference to it, and a reference is an lvalue.
 
J

James Kanze

Why is this a misfeature?

Are you saying that the fact that a literal isn't a constant,
and that when you read "abcd" in the code, it might actually be
"xyz", isn't a misfeature?
 
J

Joe Smith

Taras_96 said:
Hi everyone,

I think I mostly understand l-values and r-values after reading a few
archived posts on this board:

My attempt to sumarize conclusions made so-far:

All l-values are objects in memory, and therefore have an address.
R-values may or may not be values in memory, and may or may not have
an address.

R-values can be converted to lvalues thanks to the const T& binding.
L-values can be converted to r-values as needed.

Temporaries are normally R-values but because of the r-value to const T&
binding,
they can be converted to L-values.

Only lvalues can be on the left hand side of built in operators that modify
the right-hand side, or on wither side of operator++ and operator--.
That does not hold true for user-defined operator overloads.

L-values may or may not be modifiable.
R-values may or may not be modifiable, due to mutating member functions, or
the use of const_cast with the const T& binding.

Yeah, I'm going to say that this is a mess.
 
J

James Kanze

My attempt to sumarize conclusions made so-far:
All l-values are objects in memory, and therefore have an
address. R-values may or may not be values in memory, and may
or may not have an address.

More precisely, r-values are objects in memory if and only if
they have class type. Of course, there is no way a conforming
program can tell if a non-class type rvalue is an object
(occupies memory) or not. And if you use the rvalue to
initialize a reference, it must occupy memory. So there's
really no reason to make the distinction---the standard could
just as easily say that all values are objects.
R-values can be converted to lvalues thanks to the const T&
binding.

Except that it's not considered a conversion, according to the
standard. Similarly, if you call a member function on an
r-value, the compiler has to give it an address, in order to
initialize the this pointer.
L-values can be converted to r-values as needed.
Temporaries are normally R-values but because of the r-value
to const T& binding, they can be converted to L-values.

That's not quite the wording in the standard, but I think it
corresponds rather closely to the reality.

An important point here is that an object has a lifetime; a
temporary object normally has a lifetime until the end of the
full expression. (There's an interesting point here: does this
include the rvalue used to initialize a reference. In other
words, does the following code contain undefined behavior or
not:

#include <iostream>

void
f( int const& i )
{
pi = &i ;
}

void
g( int i )
{
std::cout << *pi << ',' << i << std::endl ;
}

int
main()
{
f( 1 ), g( 2 ) ;
return 0 ;
}

? If not, it's yet another example of a case where a non-class
rvalue behaves exactly like an object.)
Only lvalues can be on the left hand side of built in
operators that modify the right-hand side, or on wither side
of operator++ and operator--. That does not hold true for
user-defined operator overloads.

In general, only an lvalue can be "modified"; any operator which
modifies something requires an lvalue. What can happen,
especially with class types, is that the rvalue-ness is lost;
the lvalue- or rvalue-ness is not a characteristic of the
object, but of the expression.
L-values may or may not be modifiable. R-values may or may
not be modifiable, due to mutating member functions, or the
use of const_cast with the const T& binding.
Yeah, I'm going to say that this is a mess.

History. There was some discussion about dropping the
lvalue-rvalue distinction completely when the standard was being
formulated, but in the end, it was decided that built-in types
should follow the same rules as they do in C. And while I think
this does make things considerably more complicated, I can
understand that desire.

Anyway, there are a few simple rules which make it easy to
handle:

1. The distinction lvalue/rvalue only applies to expressions.
The standard specifies which expressions result in lvalues,
and it specifies which expressions require lvalues, for
which operands. And that's the only real meaning of
lvalue/rvalue---it's a more or less arbitrary distinction
based on the expression.

2. When considering expressions (and not just for
lvalue-/rvalue-ness), you have to first perform overload
resolution, and replace user defined operators with the
corresponding function call.
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

No members online now.

Forum statistics

Threads
473,770
Messages
2,569,584
Members
45,075
Latest member
MakersCBDBloodSupport

Latest Threads

Top