I have a beginner question here. If I am correct, the following was
valid for "old" c:
int foo()
{
int i;
...
return i;
}
int main()
{
int j;
j=foo()++;
j=++foo();
...
But it is not true as both ++'s require a lvalue. I can not figure it
out the justification. Why using a temporary var is unsafe here? I
assume the tmp is still valid in the expression, so should be ok. Did
I miss sth?
Thanks.
Lee
[ Send an empty e-mail to c++-...@netlab.cs.rpi.edu for info ]
[ about comp.lang.c++.moderated. First time posters: do this! ]
> hello, Mr./Ms. Gurus,
>
> I have a beginner question here. If I am correct, the following was
> valid for "old" c:
>
> int foo()
> {
> int i;
> ...
> return i;
> }
>
> int main()
> {
> int j;
> j=foo()++;
> j=++foo();
> ...
>
>
> But it is not true as both ++'s require a lvalue. I can not figure it
> out the justification. Why using a temporary var is unsafe here? I
> assume the tmp is still valid in the expression, so should be ok. Did
> I miss sth?
>
> Thanks.
>
> Lee
The pre and post increment and decrement operators are assignment
operators. They not only produce a value based on the original value,
but they also have the side effect of storing a new value back into
the object. Now an object is an lvalue, and anything that is not an
lvalue can't have anything, such as a new object stored in it.
What would you expect:
int main()
{
int j;
j = 3++;
....to do? 3 is not an lvalue, so you can't store a new value to it.
In your example, just replace:
j=foo()++; with j=foo();
....and...
j=++foo(); with j=foo()+1;
....how would that change the result of your program?
--
Jack Klein
Home: http://JK-Technology.Com
FAQs for
comp.lang.c http://www.eskimo.com/~scs/C-faq/top.html
comp.lang.c++ http://www.parashift.com/c++-faq-lite/
alt.comp.lang.learn.c-c++ ftp://snurse-l.org/pub/acllc-c++/faq
What should it be good for? Why would you like to increment the value of a temporary before throwing it away? Remember what j = foo()++; would be supposed to do:
- call function foo
- assign the result to j
- increment the result
- throw the result away
That's almost as usefull as writing j = 1++; or j = ++1;
Heinz
Thanks! Actually my question and subject title are misleading, my
main question is:
> > Why using a temporary var is unsafe here? I
> > assume the tmp is still valid in the expression, so should be ok. Did
> > I miss sth?
Could you please explain why temp should be used a non-lvalue? It is
still stored somewhere, on stack (in memory) or in a register. ARM
p25: "An object is a region of storage; an lvalue is an expression
refering to an object or function". It has the storage, why it is to
dangerous to be used as a lvalue? I don't see it from a perspective
of a beginner. I am just curious. My example is just for demo
purpose, I have never used it in practice.
Regards!
Lee
> But it is not true as both ++'s require a lvalue. I can not figure it
> out the justification. Why using a temporary var is unsafe here? I
> assume the tmp is still valid in the expression, so should be ok. Did
> I miss sth?
Your confusion arises from mistaking
++i;
for shorthand for
i + 1;
while it really means
i = i + 1;
So, when you write:
int j = ++i;
you are actually writing
int j = i = i + 1;
or, since assignment groups from right to left:
int j = (i = i + 1);
The expression (i = i + 1) requires that i be a modifiable lvalue (by
definition of the assignment operator).
If you don't mean to include an assignment operation in your
statement, you can easily write it as
int j = i + 1;
which works just fine even if i is replaced by a temporary.
> Thanks! Actually my question and subject title are misleading, my
> main question is:
>
> > > Why using a temporary var is unsafe here? I
> > > assume the tmp is still valid in the expression, so should be ok. Did
> > > I miss sth?
>
> Could you please explain why temp should be used a non-lvalue? It is
> still stored somewhere, on stack (in memory) or in a register. ARM
> p25: "An object is a region of storage; an lvalue is an expression
> refering to an object or function". It has the storage, why it is to
> dangerous to be used as a lvalue? I don't see it from a perspective
> of a beginner.
To really understand this, you need to disentangle the concepts of
rvalue and temporary object.
Going back to your original example, you had a function like this:
int foo();
According to the standard (3.10/5), "The result of calling a function
which does not return a reference is an rvalue."
Does that rvalue refer to an object? 3.10/2 says "An lvalue refers to
an object or function. Some rvalue expressions -- those of class or
cv-qualified class type -- also refer to objects." In other words, an
rvalue of non-class type (such as the result of calling a function
which returns int) need *not* refer to an object.
Peeking under the hood, it is likely that the rvalue is held in a
register someplace...and a register is *not* a region of storage.
(Notice, e.g. that you can't take the address of an rvalue.)
But what about the temporary object? Well...there's no guarantee that
calling foo() introduces a temporary object, and, in fact, it is
likely not to.
OK...you also seem to be under the impression that it is not possible
for an lvalue expression to refer to a temporary object. This is
false...it's easy to construct an lvalue that refers to a temporary:
const int& i = foo();
Binding the reference to the rvalue result of calling foo() *will*
cause the implementation to introduce a temporary object (if it hasn't
already), and the reference will bind to that object.
Don't be confused by the fact that i is a reference to a const
int...it's still an lvalue.
Finally, we come to the question, "Why is it unsafe to bind a
reference to non-const to a temporary object?"
This has to do with a category of easy-to-make, hard-to-find errors
that crop up because of the (annoying, unsafe) implicit conversion
rules for built in types that C++ inherits from C. Here's an
illustration:
void Increment(int& i) { ++i; }
short j = 1;
Increment(j); // <-- error!
It's easy to read this code and get the impression that j is
incremented by the Increment() function. But that's not what happens.
Instead, a temporary of type int is created and initialized with the
value of j. Increment() increments the temporary, which soon ceases
to exist. j is unmodified.
This is not theoretical...this is a simplified version of actual
errors committed by real programmers. The basic problem is that,
given the implicit conversion rules, it's not always obvious when and
where the compiler will introduce a temporary. Requiring that a
reference initialized with an rvalue be a reference to const makes
this particular mistake impossible.
Hope this clarifies!
In C - and in C++ - 'modifing' an object really means that a new value
has been written into the object. If you went to the trouble to
calculate, and then store (in the object), a new value, do you
want to throw it away? Probably not, but if the new value is
written into a temporary, it *will* be thrown away.
That's my interpretation of the reasoning behind why binding a
temporary to a reference to non-const is disallowed by the
standard.
However - not everyone likes that rule. Sometimes the modification is
just a side-effect of the desired operation, such as writing to a
stream. IIRC, the ARM's rule for this was the opposite of the
standard.
> It is
> still stored somewhere, on stack (in memory) or in a register. ARM
> p25: "An object is a region of storage; an lvalue is an expression
> refering to an object or function". It has the storage, why it is to
> dangerous to be used as a lvalue? I don't see it from a perspective
> of a beginner. I am just curious. My example is just for demo
> purpose, I have never used it in practice.
[snip]
It's not only through implicit conversions that temporaries are created.
Trying to modify temporaries created through implicit conversions is
certainly futile, but it CAN be useful to modify temporaries that are
not from implicit conversions. For example:
bool extract(std::istream& is, int& i) { return is >> i; }
std::string s("123");
int i;
extract(std::istringstream(s), i); // currently illegal
std::istringstream(s1) is a temporary, but if it were allowed to be
bound to a reference to non-const, we could have some nice effect
(getting the int value) even though the modified temporary istringstream
object would be lost after destruction.
(Note that, since the implicit conversion here is from istringstream&
to istream&, there is no istream temporary object created.)
Under current rules, we should write:
std::string s("123");
int i;
std::istringstream iss(s);
extract(iss, i);
// We don't need iss any more.
For this reason, I think that binding a temporary to a reference to
non-const should be disallowed only when the temporary is created
through an implicit conversion. What do you think?
--
KIM Seungbeom <musi...@bawi.org>
> For this reason, I think that binding a temporary to a reference to
> non-const should be disallowed only when the temporary is created
> through an implicit conversion. What do you think?
I think it's one of two alternatives I think might be worth
considering. The downside is that it requires the compiler to keep
track of *why* a temporary is introduced, and to chain the "reasons"
together. For instance, when binding a reference to an rvalue, the
implementation is allowed to introduce *another* temporary and bind
the reference to that. The impelementation would need to notice that
it's copying a copy, and figure out why the first copy was made in the
first place.
Another possibility would be to say that you can bind a non-const
reference to an rvalue of user-defined type, but not to an rvalue of
built-in type. This would be easy for compilers to enforce, and would
allow for most of the idioms that programmers would like to use. On
the downside, it makes classes with implicit conversions to and from
user-defined types even more error-prone than they already are.
I don't know if the standards committee is considering any extensions
along these lines though. Neither approach is cost/risk free. And
most of the instances when we'd use these options have one or two line
work arounds. So...I'm not holding my breath. :-)
As one of our guidelines is to make C++ more teachable I would be pretty
certain that we will not touch this area. That temporaries of all kinds
can only bind to const refs is teachable, that sometimes they do and
sometimes they do not is NOT IMO.
--
ACCU Spring Conference 2003 April 2-5
The Conference you should not have missed
ACCU Spring Conference 2004 Late April
Francis Glassborow ACCU
You will be interested in the paper, "A Proposal to Add Move Semantics
Support to the C++ Language", by Hinnant, Dimov, and Abrahams, eg at:
http://anubis.dkuug.dk/jtc1/sc22/wg21/docs/papers/2002/n1377.htm
It is a rather different approach to the underlying problem. Last I heard,
it represented the most mature thinking on this subject. Corrections
invited :-)
Dave Harris, Nottingham, UK | "Weave a circle round him thrice,
bran...@cix.co.uk | And close your eyes with holy dread,
| For he on honey dew hath fed
http://www.bhresearch.co.uk/ | And drunk the milk of Paradise."
I can certainly see that.
A third approach that actually does seem to be on the table is the
move-semantics proposal (Hinnant, Dimov & Abrahams) which would
introduce a new reference type (an "rvalue-reference") which could
bind to a temporary. But this looks like a major addition to the
language and, thus, also in the "not holding ones breath" category.
To make C++ more teachable, we should make it more consistent.
Isn't it consistent in that
// invoking a non-const member function on an rvalue
foo().non_const_member_function()
is allowed while
// binding an rvalue to a reference to non-const
function_taking_reference_to_non_const(foo())
is not?
To get around this inconsistency, we rely on a member function that
returns a reference to *this.
struct X { X& self() { return *this; } };
void f(X&);
f(X()); // illegal
f(X().self()); // legal
Take the well-known "convert-to-string" idiom, for a real-world example:
using std::string;
using std::ostringstream;
string s = static_cast<ostringstream&>(
ostringstream().flush() << "3 + 1 = " << 3 + 1 << "."
).str();
Why do we have to say "ostringstream().flush()" first, even though
there is nothing in the ostringstream to be flushed at that time?
It's just to convert a temporary to an lvalue that can be bound
to a reference to non-const object. Wouldn't it be more intuitive
and more teachable if we could write like the following?
string s = static_cast<ostringstream&>(
ostringstream() << "3 + 1 = " << 3 + 1 << "."
).str();
To reiterate, I'm aware of the danger of allowing something like this:
void increment(int& i) { i++; }
double d = 4;
increment(d);
but the danger is in that an invisible temporary is created due to
an implicit conversion and that it is bound to the reference instead,
not in that it is an rvalue. If the temporary is explicitly created,
there's no danger. Moreover, we've seen enough the usefulness of
allowing explicitly created rvalues (temporaries) to be bound to
a reference to non-const.
It is more important to allow a useful feature than to prevent every misuse,
isn't it? ;-)
--
KIM Seungbeom <musi...@bawi.org>