string literal is an lvalue; other literals are rvalues.

S

Steven T. Hatton

This is from the draft of the previous version of the Standard:

http://www.kuzbass.ru:8086/docs/isocpp/expr.html

2- A literal is a primary expression. Its type depends on its form
(lex.literal). A string literal is an *lvalue*; all other literals are
*rvalues*.

-4- The operator :: followed by an identifier, a qualified-id, or an
operator-function-id is a primary-expression. Its type is specified by the
declaration of the identifier, qualified-id, or operator-function-id. The
result is the entity denoted by the identifier, qualified-id, or
operator-function-id. The result is an *lvalue* if the entity is a function
or variable. The identifier, qualified-id, or operator-function-id shall
have global namespace scope or be visible in global scope because of a
using-directive (namespace.udir). [Note: the use of :: allows a type, an
object, a function, an enumerator, or a namespace declared in the global
namespace to be referred to even if its identifier has been hidden
(basic.lookup.qual). ]

As I understand the distinction between rvalue and lvalue, an lvalue *can*
appear on the left side of an expression, whereas an rvalue can *only*
appear on the right side. What I don't fully understand is

1) who cares? What I mean to ask is, why is it so important to specify?

I assume it's a way of defining the syntax rules by specifying that lvalues
can go here, but not rvalues, etc. But this seems like the chicken and the
egg almost. There is talk of lvalue to rvalue conversion in 4.1. Is that
just a conceptual artifice, or something the compiler actually does?

2) I'm not sure why a function or a string literal would be considered an
lvalue.

In the case of a function is it because it can be a standalone statement? Or
is it because it can appear in a pure virtual declaration on the lefthand
side of the '='?
 
A

Alf P. Steinbach

* "Steven T. Hatton said:
As I understand the distinction between rvalue and lvalue, an lvalue *can*
appear on the left side of an expression, whereas an rvalue can *only*
appear on the right side.

If they're not part of expressions.

Another more useful difference is that you can take the address of an lvalue,
but not of an rvalue.

An lvalue denotes a storage location, an rvalue denotes a value sans location,
such as the int value 5.

Thus, an rvalue cannot by itself appear on the left hand side in an
assignment, and that's the basis of the terminology.

But the terminology is a bit misleading: a const lvalue can't appear on the
left hand side of an assignment, either (although what's there must be an
lvalue).


What I don't fully understand is

1) who cares? What I mean to ask is, why is it so important to specify?

A program that modified the value of 5, say, would be difficult to debug.

I assume it's a way of defining the syntax rules by specifying that lvalues
can go here, but not rvalues, etc. But this seems like the chicken and the
egg almost. There is talk of lvalue to rvalue conversion in 4.1. Is that
just a conceptual artifice, or something the compiler actually does?

I think it's your job to quote the relevant passage in 4.1.

Converting an lvalue to rvalue is often necessary. E.g. you can use an int
variable wherever you can use an int value such as 5. The opposite conversion
is of course not allowed; e.g. you cannot assign to the value 5.


2) I'm not sure why a function or a string literal would be considered an
lvalue.

Function: because you must be able to take its address in order to use
function pointers.

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.

In the case of a function is it because it can be a standalone statement? Or
is it because it can appear in a pure virtual declaration on the lefthand
side of the '='?

No, see above.
 
S

Steven T. Hatton

Alf said:
If they're not part of expressions.

Another more useful difference is that you can take the address of an
lvalue, but not of an rvalue.

An lvalue denotes a storage location, an rvalue denotes a value sans
location, such as the int value 5.

If I have a literal 5 in my source code it begins life as an ascii
character. That is translated and used by the compiler. It is either
consumed to produce a result that somehow makes its way into the runtime
instance of the program, or it is directly inserted into the runtime. E.g.,

const int x = 5 * 125; // 5 is consumed and forgotten

const int y = 5; // 5 lives a long and significant life

int f(const int& n){
return 5 * n; // 5 has to stick around in the shadows somewhere
}
Thus, an rvalue cannot by itself appear on the left hand side in an
assignment, and that's the basis of the terminology.

But the terminology is a bit misleading: a const lvalue can't appear on
the left hand side of an assignment, either (although what's there must be
an lvalue).

I guess some of the difficulty is that we are talking about two drastically
different models of the same abstract problem space. One is the text-based
source code, the other is the runtime representation of the program in
storage. When I read your statement that const lvalue can't appear on the
lefthand side of an assignment I thought I understood and agreed with it.
Then I wrote the two examples above. Does this mean that x and y only
become const lvalues after they are initialized (defined)?

I will grant that I know of no way to specify the location of 5 in the
functon f above, but it has to have some kind of representaton in runtime
storage. I'm not sure if it would be categorize as an rvalue. I'm just
using it to try to get an undstanding of what parts of the source code find
their way into the runtime image, and how they are represented there.

I believe I undstood this stuff about ten years ago.
A program that modified the value of 5, say, would be difficult to debug.

Agreed. But I knew that before I knew it was called an rvalue.

I think it's your job to quote the relevant passage in 4.1.

This is a better link than the one I posted previously in this thread:
http://www.itga.com.au/~gnb/wp/cd2/

4.1 Lvalue-to-rvalue conversion [conv.lval]

1 An lvalue (_basic.lval_) of a non-function, non-array type T can be
converted to an rvalue. If T is an incomplete type, a program that
necessitates this conversion is ill-formed. If the object to which
the lvalue refers is not an object of type T and is not an object of a
type derived from T, or if the object is uninitialized, a program that
necessitates this conversion has undefined behavior. If T is a non-
class type, the type of the rvalue is the cv-unqualified version of T.
Otherwise, the type of the rvalue is T. 1)

2 The value contained in the object indicated by the lvalue is the
rvalue result. When an lvalue-to-rvalue conversion occurs within the
operand of sizeof (_expr.sizeof_) the value contained in the refer-
enced object is not accessed, since that operator does not evaluate
its operand.
Converting an lvalue to rvalue is often necessary. E.g. you can use an
int
variable wherever you can use an int value such as 5. The opposite
conversion is of course not allowed; e.g. you cannot assign to the value
5.

But to say an lvalue is converted to an rvalue doesn't mean much to me.
Does something actually happen in the CPU to change the representation of
the object? Also, if the rvalue /is/ an object, it has a storage location
doesn't it?
Function: because you must be able to take its address in order to use
function pointers.

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.

There's something I'm missing here. What is the difference in how the
program represents and process the same object as an rvalue or an lvalue?
 
A

Alf P. Steinbach

Oops, that was not general enough. The function


std::string foo(){ return "Hey"; }


returns an rvalue because a temporary object is an rvalue. You cannot take
its address because conceptually it has no storage location. But in terms of
machine code it really has, in this particular case, and if you then define


void bar( std::string const& s ){ std::cout << &s << std::endl; }


and call it like


bar( foo() );


that location may be used directly, and if so then 'bar' will display it
(also the object may itself have a function that gives its address).

No, one does not need to be stupid in order not to think of that.

The exact rules for this are _very_ complicated & hazy, e.g. causing a problem
in Andrei Alexandrescu's original Mojo design (which was reviewed by hundreds
of people before one really smart fellow spotted the problem).

On the other hand in ordinary programming the conceptual model is very simple
and suffices.

Think of an rvalue as the integer value 5, and that's all you need.

const int x = 5 * 125; // 5 is consumed and forgotten

const int y = 5; // 5 lives a long and significant life

int f(const int& n){
return 5 * n; // 5 has to stick around in the shadows somewhere
}

Does this mean that x and y only
become const lvalues after they are initialized (defined)?

Yes. They do not exist before their initializations. And initialization is
not assignment, in spite of having nearly the same syntax.

The reason for initialization not being assignment (in C++) is that
initialization may have to construct an object from raw memory, whereas
assignment must change an existing object, e.g. deallocate stuff.

But to say an lvalue is converted to an rvalue doesn't mean much to me.
Does something actually happen in the CPU to change the representation of
the object?

It might. In the case of 'a = b + 1;' the lvalue 'b' may be converted to
rvalue by loading the contents of &b into a processor register. For example.

Also, if the rvalue /is/ an object, it has a storage location doesn't it?

See above. It may. But you cannot directly take the address of an rvalue,
and you cannot for sure obtain that address unless it's an object that has
a function that returns the address, and especially in the case of built-in
types it may not really have a storage location except in the sense of perhaps
being embedded in a machine code instruction, and for a typical optimizing
compiler even that may not exist; the rvalue might exist only conceptually,
which is much of the point.

There's something I'm missing here. What is the difference in how the
program represents and process the same object as an rvalue or an lvalue?

The difference is in what the program text is allowed to do. E.g. you cannot
change the value 5. And you cannot call non-const functions on rvalues.
 
H

Howard

Steven T. Hatton said:
If I have a literal 5 in my source code it begins life as an ascii
character. That is translated and used by the compiler. It is either
consumed to produce a result that somehow makes its way into the runtime
instance of the program, or it is directly inserted into the runtime. E.g.,

const int x = 5 * 125; // 5 is consumed and forgotten

const int y = 5; // 5 lives a long and significant life

int f(const int& n){
return 5 * n; // 5 has to stick around in the shadows somewhere
}


I guess some of the difficulty is that we are talking about two drastically
different models of the same abstract problem space. One is the text-based
source code, the other is the runtime representation of the program in
storage. When I read your statement that const lvalue can't appear on the
lefthand side of an assignment I thought I understood and agreed with it.
Then I wrote the two examples above. Does this mean that x and y only
become const lvalues after they are initialized (defined)?

I will grant that I know of no way to specify the location of 5 in the
functon f above, but it has to have some kind of representaton in runtime
storage. I'm not sure if it would be categorize as an rvalue. I'm just
using it to try to get an undstanding of what parts of the source code find
their way into the runtime image, and how they are represented there.

I believe I undstood this stuff about ten years ago.

You're misunderstanding basic data storage concepts here. Having the
constant value 5 in your source code may lead to also having that value in
the compiled machine code, but it's in the *code*, not in a data location.
It becomes just a part of the arguments to a machine code instruction.
Variables, however, are given specific storage space, and the address of
that space is then used as an argument to machine code instructions. (Of
course, sometimes the storage is temporary, like a register, and in those
cases the register name is used instead of an address.) Look at the
assemble language generated by defining, initializing, and assigning
variables in your program, and you'll get the picture. (But turn off
optimizations, because a very small program may have too much of what you
want to see optimized into register data.)

-Howard
 
D

David Harmon

On Mon, 12 Apr 2004 10:58:47 -0400 in comp.lang.c++, "Steven T. Hatton"
If I have a literal 5 in my source code it begins life as an ascii
character.

That may be, but there is no requirement in C++ that the source
character set in which your program is written is ASCII. The C++
standard turns backflips to ensure that it does not specify more than is
necessary for a proper implementation of C++, and thereby impose
unnecessary restrictions on portability and efficiency. If you wish to
think about standard portable C++, you must likewise abandon many
assumptions about implementation.

A good one to start with is guesswork about what things do or do not
occupy what memory. Especially consts and temporaries.
E.g.,

const int x = 5 * 125; // 5 is consumed and forgotten

5 is neither consumed nor forgotten. 5 is eternal and unchanging.
The 5 you know is the same one Euclid knew.

But, C++ requires that the above has the same effect as
const int x = 625;
const int y = 5; // 5 lives a long and significant life

No, you are thinking of the lifetime of y. y is "alive" in whatever
part of your program that declaration is visible. But y might never
occupy run-time memory, if the generated code has no need to make it do
so, ie. if you never take the address of y.
int f(const int& n){
return 5 * n; // 5 has to stick around in the shadows somewhere
}

This might possibly for instance be implemented by something resembling
return (n << 2) + n;
I will grant that I know of no way to specify the location of 5 in the
functon f above, but it has to have some kind of representaton in runtime
storage.

I suppose the representation of 5 in runtime storage in ((n << 2) + n)
is implicit in the sequence of some machine instructions in the
generated code. It certainly does not _have to_ correspond to a
location in memory with the value 5 in it, it is a rvalue.
Is that what you are trying to say?
 
S

Steven T. Hatton

David said:
On Mon, 12 Apr 2004 10:58:47 -0400 in comp.lang.c++, "Steven T. Hatton"


That may be, but there is no requirement in C++ that the source
character set in which your program is written is ASCII. The C++
standard turns backflips to ensure that it does not specify more than is
necessary for a proper implementation of C++, and thereby impose
unnecessary restrictions on portability and efficiency.

Just to be pedantic, the Standard /does/ specify that the basic source
character set is a subset of the ASCII character set:

15) The glyphs for the members of the basic source character set are
intended to identify characters from the subset of ISO/IEC 10646 which
corresponds to the ASCII character set.
If you wish to
think about standard portable C++, you must likewise abandon many
assumptions about implementation.

A good one to start with is guesswork about what things do or do not
occupy what memory. Especially consts and temporaries.

I don't believe it is really possible to comprehend the intent of the
standard at that level of abstraction.
5 is neither consumed nor forgotten. 5 is eternal and unchanging.
The 5 you know is the same one Euclid knew.

Euclid knew about integer literals?
But, C++ requires that the above has the same effect as
const int x = 625;

"5) This provision is sometimes called the as-if rule, because an
implementation is free to disregard any requirement of this International
Standard as long as the result is as if the requirement had been obeyed, as
far as can be determined from the observable behavior of the program. For
instance, an actual implementation need not evaluate part of an expression
if it can deduce that its value is not used and that no side effects
affecting the observable behavior of the program are produced."

So, technically, I cannot speak in terms of what the implementation actually
does and express the full intention of the Standard.
No, you are thinking of the lifetime of y. y is "alive" in whatever
part of your program that declaration is visible. But y might never
occupy run-time memory, if the generated code has no need to make it do
so, ie. if you never take the address of y.

But, in some sense 5 is recoverable, whereas in the first example, it is
not. I had implicitly assumed y was used.
This might possibly for instance be implemented by something resembling
return (n << 2) + n;

Yes, I had considered that possibility.
I suppose the representation of 5 in runtime storage in ((n << 2) + n)
is implicit in the sequence of some machine instructions in the
generated code. It certainly does not _have to_ correspond to a
location in memory with the value 5 in it, it is a rvalue.
Is that what you are trying to say?

No. " I'm not sure if it would be categorize as an rvalue. I'm just
using it to try to get an understanding of what parts of the source code
find their way into the runtime image, and how they are represented there."

Please read the portions of the Standard I quoted above so you are aware of
what I already know. That should help you in determining what parts of my
posts are informally making assumptions such as, 'virtual functions are
implemented using vtbls', or, 'excessive inclusion of unnecessary headers
leads to slower compile times'.

If it's not clear what assumptions I have made, please ask for
clarification.
 
S

Steven T. Hatton

Howard wrote:

You're misunderstanding basic data storage concepts here. Having the
constant value 5 in your source code may lead to also having that value in
the compiled machine code, but it's in the *code*, not in a data location.

One man's data is another man's instruction. ;-)
It becomes just a part of the arguments to a machine code instruction.

Actually, I thought I was making the point, not missing it. I will admit I
was not sure how exactly to express it.
Variables, however, are given specific storage space, and the address of
that space is then used as an argument to machine code instructions. (Of
course, sometimes the storage is temporary, like a register, and in those
cases the register name is used instead of an address.) Look at the
assemble language generated by defining, initializing, and assigning
variables in your program, and you'll get the picture. (But turn off
optimizations, because a very small program may have too much of what you
want to see optimized into register data.)

That is very good advice. Thank you.
 
S

Steven T. Hatton

Alf said:
returns an rvalue because a temporary object is an rvalue. You cannot
take
its address because conceptually it has no storage location. But in terms
of machine code it really has, in this particular case, and if you then
define


I looked this up in Sebesta's _Concepts of Programming Languages_, and found
that he rather bluntly says: "The address of a variable is sometimes called
its l-value, and the value is sometimes called its r-value."

My edition is older than this one:
http://www.aw-bc.com/catalog/academic/product/0,4096,0321193628,00.html


Then we have K&R: "An object is a manipulatable region of storage; an lvalue
is an expression referring to an object....The name 'lvalue' comes from the
assignment expression E1 = E2 in which the left operand E1 must be an
lvalue expression."

http://www.embedded.com/story/OEG20010518S0071
It might. In the case of 'a = b + 1;' the lvalue 'b' may be converted to
rvalue by loading the contents of &b into a processor register. For
example.

That seems worth considering. But, if we have an lvalue and its contents
are loaded into a register, but its existing storage is not released, then
it didn't /become/ an rvalue, it spawned an rvalue. I think?
 
A

Alf P. Steinbach

* "Steven T. Hatton said:
That seems worth considering. But, if we have an lvalue and its contents
are loaded into a register, but its existing storage is not released, then
it didn't /become/ an rvalue, it spawned an rvalue. I think?

Right.

Perhaps you've been bitten by a misunderstanding I once had about
"conversion".

Saying "A is converted to B" is often just a way of saying "form B is computed
from form A", and does not necessarily imply a replacement of A with B.
 
S

Steven T. Hatton

Alf said:
Right.

Perhaps you've been bitten by a misunderstanding I once had about
"conversion".

Actually, I'm usually pretty aware of that notion. It's the whole
philosophy of information verses material posession. I can give a person
an idea, and still have it. Of course there are issues of relative
advantage of not sharing you ideas or knowledge in order to maintain some
kind of advantage. For the most part, that is simply contrary to my
nature. But I digress...
Saying "A is converted to B" is often just a way of saying "form B is
computed from form A", and does not necessarily imply a replacement of A
with B.

And when you do that a few million times, you end up with a whole bunch of
wasted storage. Actually, as I understand things, destroying A on every
calculation, is exactly what Mathematica does. The entire expression is
transformed according to the rules it expresses, and it is placed in new
storage. I really don't know the details, but that's what I understood
fromm one knowlegible person's explanation. Interestingly, Mathematica is
very much a form of self-referential metaprogramming.

I just found this:

"We can allocate and use "variables" that do not have names, and it is
possible to assign to strange-looking expressions (e.g., *p[a+10]=7).
Consequently, 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" - Bjarne Stroustrup TC++PL(SE)
 
A

Alf P. Steinbach

* (e-mail address removed) (Alf P. Steinbach) schriebt:
And you cannot call non-const functions on rvalues.

Boink! That was the sound of hand against empty skull. Reading another
thread it finally filtered up from my unconsciousness that what I wrote
many days ago had to be (perhaps subtly) W R O N G.


#include <iostream>

struct X
{
int my;
X(): my( 0 ) {};
X& set( int x ){ my = x; return *this; }
};

X foo(){ return X(); }

int main()
{
std::cout << foo().set( 1234 ).my << std::endl;
}


I think it's _best_ to not do such things, but that's far cry from 'cannot'.

But before leaving this, perhaps best to check with the standard that foo()
is really an rvalue. Let's see. Yep,


§3.10/5 The result of calling a function that does not return a reference
is an rvalue.


Perhaps also best to check whether the result is well-defined. But that's not
nearly as clear in the standard. It states


§3.10/10 An lvalue for an object is necessary in order to modify the
object except that an rvalue of class type can be used to modify
its referent under certain circumstances. [Example: a member
function called for an object (9.3) can modify the object]


seemingly without further specifying the "certain circumstances", and


§3.10/14 If an expression can be used to modify the object to which it
refers, the expression is called modifiable. A program that
attempts to modify an object through a nonmodifiable lvalue or
rvalue expression is ill-formed.


seemingly without defining "modifiable" -- two earlier paragraphs define
pointers to functions and pointers to incomplete types as modifiable, but that
does not include class types, and not other pointer types.

I'm glad I'm not a lawyer.

(E-mailed copy to Stephen in case he doesn't follow this thread anymore.
Sorry for the mis-information.)
 

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