Temporary objects, l-values.

J

jason.cipriani

First I have a question about the "official" definition of a
"temporary object".

So in this code:

void function1 (SomeThing a) { }

void function2 (void) {
SomeThing b;
function1(b);
}

A copy of b is made to pass to function1(). That's a temporary object,
of course. I was always under the impression that a "temporary object"
was an object created by the compiler implicitly, and it's entirely
"behind the scenes" (as in the above example). But, is the object
created in the following code also called a "temporary object":

void function1 (SomeThing &a) { }

void function2 (void) {
function1(SomeThing());
}

I mean, you explicitly construct it. It's not secretly created by the
compiler -- but it is anonymous (outside of function1), and it does
get destroyed after the statement is evaluated. It's just not
completely "behind the scenes". Would that also be called, officially,
a "temporary object"?

My second question is about l-values. In the following code:

struct A {
A (void) { }
};

void function (void) {
A a;
A() = a;
}

That compiles on all the compilers I've tested it on (Borland's BCC32,
GCC, and Comeau); and I expected it to. That would mean that "A()" is
not a temporary, by definition, since it is also an l-value. It seems
weird that I can say "A() = something;". I mean I know exactly what it
is doing, but it doesn't make sense to do it (just like trying to set
"2 = 3" doesn't really make much sense) (unless the = operator has
some other side effects that you want). Are the compilers I've tried
it on broken? Or is A() really an l-value there? Or is it compiler-
specific whether or not that is accepted or not? The reason I am
confused is because of code like this (untested, I'm just typing it in
this email hopefully I didn't screw up something important):

struct X {
};

struct Y {
Y (const X &) { }
};

void function (Y &) { }

void function2 (void) {
X x;
function(Y(x));
}

That code compiles with recent versions of GCC, Borland's compiler,
and the MS compiler. However, Comeau does not compile it, complaining
that non-const reference parameters to functions must be l-values. Yet
Comeau does compile the following (add a default constructor to Y in
the above example for this):

void function2 (void) {
Y y;
(Y(x)) = y;
}

Comeau's inconsistency about what it thinks an l-value is bugs me (I
asked them about this one and am waiting for a response).

So I'm a little confused. Mostly I'm asking to clear up some points in
a discussion I'm having with somebody on another newsgroup.

Thanks,
Jason
 
K

Kai-Uwe Bux

First I have a question about the "official" definition of a
"temporary object".

So in this code:

void function1 (SomeThing a) { }

void function2 (void) {
SomeThing b;
function1(b);
}

A copy of b is made to pass to function1(). That's a temporary object,
of course. I was always under the impression that a "temporary object"
was an object created by the compiler implicitly, and it's entirely
"behind the scenes" (as in the above example). But, is the object
created in the following code also called a "temporary object":

void function1 (SomeThing &a) { }

void function2 (void) {
function1(SomeThing());
}

I mean, you explicitly construct it. It's not secretly created by the
compiler -- but it is anonymous (outside of function1), and it does
get destroyed after the statement is evaluated. It's just not
completely "behind the scenes". Would that also be called, officially,
a "temporary object"?

a) Yes.

b) The code should not compile. The temporary does not bind to non-const
references. See [8.5.3/5].

My second question is about l-values. In the following code:

struct A {
A (void) { }
};

void function (void) {
A a;
A() = a;
}

That compiles on all the compilers I've tested it on (Borland's BCC32,
GCC, and Comeau); and I expected it to. That would mean that "A()" is
not a temporary, by definition, since it is also an l-value.

Nope. You seem to think that something needs to be an l-value to allow for
assignment. That is not true for class types. For class types, the
assignment operator is a member function. You can call non-const member
functions on r-values.

It seems
weird that I can say "A() = something;". I mean I know exactly what it
is doing, but it doesn't make sense to do it (just like trying to set
"2 = 3" doesn't really make much sense) (unless the = operator has
some other side effects that you want).

Huh? The reason that 2 = 3 does not make sense is that 2 is const. A() is
not const. Why should it not make sense to set its value?
Are the compilers I've tried
it on broken? Or is A() really an l-value there? Or is it compiler-
specific whether or not that is accepted or not? The reason I am
confused is because of code like this (untested, I'm just typing it in
this email hopefully I didn't screw up something important):

struct X {
};

struct Y {
Y (const X &) { }
};

void function (Y &) { }

void function2 (void) {
X x;
function(Y(x));
}

That code compiles with recent versions of GCC, Borland's compiler,
and the MS compiler. However, Comeau does not compile it, complaining
that non-const reference parameters to functions must be l-values.

a) gcc also does not compile the code.
b) Comeau is correct.
Yet
Comeau does compile the following (add a default constructor to Y in
the above example for this):

void function2 (void) {
Y y;
(Y(x)) = y;
}

Comeau's inconsistency about what it thinks an l-value is bugs me (I
asked them about this one and am waiting for a response).

So I'm a little confused. Mostly I'm asking to clear up some points in
a discussion I'm having with somebody on another newsgroup.

Contrary to popular belief, temporaries are not const (unless created
const). However, temporaries do not bind to non-const references. Hence:

typeded std::vector< int > int_vector;
int_vector x;

x.swap( int_vector() ); // illegal
swap( x, int_vector() ); // illegal
int_vector().swap( x ); // legal

In the same way, you can call the assignment operator on a temporary.
(Usually, there is no point in doing so since all effects of the assignment
will be lost at the end of the full expression.)



What is the underlying problem that you are trying to solve?


Best

Kai-Uwe Bux
 
J

jason.cipriani

Thanks for the quick reply!

All right, so... now I have lots of nitpicky questions...

Kai-Uwe Bux said:

Ok. Now if you have this:

{
SomeThing x;
function1(x);
}

There, x itself is not a "temporary object", right, because you
explicitly
declare it, then use it in another statement -- even though it has a
very
short life. But if you "shorten" that to this:

{
function1(SomeThing());
}

Then that SomeThing you pass to function1 *is* a "temporary object".
The
line is when you ... define the object explicitly in a separate
statement,
so it's lifetime is longer than just the expression it was defined in?
Do I
have that right?
b) The code should not compile. The temporary does not bind to non-const
references. See [8.5.3/5].

Thanks for the section reference. I'll read through it tomorrow, it
should
clear up my confusion.
Nope. You seem to think that something needs to be an l-value to allow for
assignment. That is not true for class types. For class types, the
assignment operator is a member function. You can call non-const member
functions on r-values.

I'm sorry, I don't understand. I'm having a hard time thinking about
this.
When I see people describe what an l-value is, it is usually described
as
"something that can appear on the left side of an = sign". I take that
to
mean "something that can be set equal to something else".

So what you are saying is: Because the assignment operator is a
member
function for class types, a class type does not necessarily have to be
an
"l-value" to appear on the left side of an = sign, because it's just
like
calling any other member function. Since I can do
"SomeThing().SomeMemberFunction();", I can do "SomeThing().operator =
(...);" as well, even though it's not an l-value. Right?

The reason that I thought SomeThing() was an l-value is because it
could
appear on the "left side of an = sign". But, I guess the definition
is
deeper than that. So... what does it take for a class type value to be
an
l-value then? If the fact that the = operator is just another member
function like any other means that "SomeThing() = ...;" does not imply
that
SomeThing() is necessarily an l-value, then it seems like the "=" sign
and
the "l-value-ness" of a class type are completely unrelated concepts?
In
other words, if "SomeThing()" can appear on the left side of an =
sign, but
it is not an l-value, then what *is* an l-value (since being able to
be to
the left of an = is not the only requirement)?

Is it more accurate to say an l-value is "something that can be set
equal to
something else", and not worry about the "=" at all... so it's more
about
the meaning of the statement? What I mean is, if you have a type and
you
implement an = operator for it that does something else that isn't
assignment, then in that case "=" has nothing to do with l-values.
Similarily, if you have a member function, say, "Assign()" that does
do
assignment, then an l-value of that type is a value where it's
meaningful to
call "Assign()" on, even though the assignment operator itself is not
involved?

I am not disagreeing at all -- I'm just trying to wrap my head around
what
you typed, because I'm pretty easily confused.
Huh? The reason that 2 = 3 does not make sense is that 2 is const. A() is
not const. Why should it not make sense to set its value?

I'm sorry, "2 = 3" was a really bad example that I used to try to
describe
something that I didn't know the word for. What I meant was something
like
"in most situations, it's a silly thing to do" (for lack of a better
phrase). Sort of like... assuming the = operator for a type has no
side
effects, then doing something like "A() = whatever;" doesn't have any
effect
on your program. If you removed it, everything would still be the
same.
That's kind of more what I meant. It was a very bad example.
a) gcc also does not compile the code.
b) Comeau is correct.

It does not compile with GCC... I must have confused myself with
different
test cases. Argh. Thanks for double-checking that.
Contrary to popular belief, temporaries are not const (unless created
const). However, temporaries do not bind to non-const references.

I did not think temporaries were const. I think I went the extreme in
the
other direction, trying to assign temporaries to other things. My
logic was
all screwed up. This is what I had thought:

1) L-values are things that appear on the left side of = operators.
2) "(Y(x))" can appear on the left side of = operator.
3) Temporaries are not l-values. Temporaries can not be assigned to
other
things.
4) Therefore, Y(x) is not a "temporary".
5) Therefore, Y(x) can bind to a non-const reference.

But the flaw in that "proof" (of sorts) is that pretty much every
point is
wrong :( . Because like you said above, the = operator is just
another
non-const member function for class types, nothing special about it.
And
temporaries are not necessarily const.
Hence:

typeded std::vector< int > int_vector;
int_vector x;

x.swap( int_vector() ); // illegal
swap( x, int_vector() ); // illegal
int_vector().swap( x ); // legal

In the same way, you can call the assignment operator on a temporary.

If you can call the assignment operator on a temporary, why are you
not
allowed to bind temporaries to non-const reference parameters? I mean,
code
like this *seems* entirely reasonable to me:

void function (A &a) {
// do some stuff to a here
}

void function2 (void) {
function(A());
}

Why is that illegal? You create a new A(), pass a reference to it to
function (so it's not copied when you call function(), it's created
before
the actual call is made), it's valid inside function(), you can do
stuff to
it, function() returns, statement ends, A() is destroyed. It doesn't
seem
dangerous, unpredictable, indeterminant, or anything. Do you know why
you
aren't allowed to do that, then (mostly for my own curiosity, I don't
actually have a program that I "need" to do something like that in)?

And since temporaries are not necessarily const, why can they (even
the
non-const ones) only be bound to const references?
(Usually, there is no point in doing so since all effects of the
assignment
will be lost at the end of the full expression.)

*That* is what I was trying to say with the stupid "2=3" example. "No
point". Duh.
What is the underlying problem that you are trying to solve?

It's an off-topic discussion on a Borland newsgroup about exactly
this
topic. Whether or not you can bind A() to a non-const reference
parameter.
The source of the confusion is two things:

1) I'm not familiar enough with the standard to know what the real,
correct
behavior is, and
2) The Borland compiler *does*, in fact, accept the above code (with
"function(A());").

There was some confusion about which was correct so I asked here to
get a
real answer. The Borland compiler is wrong, of course. It's strange
because
with the [broken] Borland compiler, the following is accepted, like I
said:

struct X { };

struct Y {
Y (const X &) { }
};

void function (Y &) { }

void function2 (void) {
X x;
function(Y(x));
function((Y)x);
}

But this is not (complaining about binding l-values to non-const
references):

struct Y {
};

struct X {
operator Y () { return Y(); }
};

void function (Y &) { }

void function2 (void) {
X x;
function(Y(x));
function((Y)x);
}

The difference being that in the first example the conversion uses
Y's
constructor, but in the second the conversion uses X's cast operator.
It's
totally weird.

Thanks again for your time!
Jason
 
J

jason.cipriani

Sorry about the screwed up line breaks in that last post... don't
blame me, blame it on the fact that computers have been around for a
half a century and nobody's figured out a consistent way to let me
copy and paste text from one text box to another without problems yet.
 
K

Kai-Uwe Bux

Thanks for the quick reply!

All right, so... now I have lots of nitpicky questions...



Ok. Now if you have this:

{
SomeThing x;
function1(x);
}

There, x itself is not a "temporary object", right, because you
explicitly
declare it, then use it in another statement -- even though it has a
very
short life. But if you "shorten" that to this:

{
function1(SomeThing());
}

Then that SomeThing you pass to function1 *is* a "temporary object".
Yes.

The
line is when you ... define the object explicitly in a separate
statement,
so it's lifetime is longer than just the expression it was defined in?

Yes. The lifetime of a temporary ends at the full expression with a few
exceptions listed in [12.24].
Do I
have that right?

Huh? The right to do what?

[snip]
I'm sorry, I don't understand. I'm having a hard time thinking about
this.
When I see people describe what an l-value is, it is usually described
as
"something that can appear on the left side of an = sign". I take that
to
mean "something that can be set equal to something else".

One problem is that there is no short definition of l-value and r-value. The
way it formally works is that chapter 5 describes the evaluation of
expressions. For each operator, cast, function call, etc. it describes the
conditions under which it returns an l-value. This yields a recursive
definition that will allow you to deduce for each expression whether it
yields an l-value.

What you heard about l-values being the things that can go on the left
of "=" probably arises from the line

There are several assignment operators, all of which group right-to-left.
All require a modifiable lvalue as their left operand, and the type of an
assignment expression is that of its left operand. [5.17/1]

However, for class types, an expression of the for lhs = rhs does not
invoke an assignment operator but the copy assignment operator.

So what you are saying is: Because the assignment operator is a
member
function for class types, a class type does not necessarily have to be
an
"l-value" to appear on the left side of an = sign, because it's just
like
calling any other member function. Since I can do
"SomeThing().SomeMemberFunction();", I can do "SomeThing().operator =
(...);" as well, even though it's not an l-value. Right?

Yup, that's what I say. May others correct it if I happen to be wrong.

The reason that I thought SomeThing() was an l-value is because it
could
appear on the "left side of an = sign". But, I guess the definition
is
deeper than that. So... what does it take for a class type value to be
an
l-value then? If the fact that the = operator is just another member
function like any other means that "SomeThing() = ...;" does not imply
that
SomeThing() is necessarily an l-value, then it seems like the "=" sign
and
the "l-value-ness" of a class type are completely unrelated concepts?
In
other words, if "SomeThing()" can appear on the left side of an =
sign, but
it is not an l-value, then what *is* an l-value (since being able to
be to
the left of an = is not the only requirement)?

Maybe the closest nut-shell definition of l-value is "an object whose
address can be taken". However, there are, of course issues with this, too:
operator& is also overloadable.

Is it more accurate to say an l-value is "something that can be set
equal to
something else", and not worry about the "=" at all... so it's more
about
the meaning of the statement? What I mean is, if you have a type and
you
implement an = operator for it that does something else that isn't
assignment, then in that case "=" has nothing to do with l-values.
Similarily, if you have a member function, say, "Assign()" that does
do
assignment, then an l-value of that type is a value where it's
meaningful to
call "Assign()" on, even though the assignment operator itself is not
involved?

No. Those are technically unrelated to l-valueness. Note that for class
types you can assign to temporaries. That does not make them l-values.

I am not disagreeing at all -- I'm just trying to wrap my head around
what
you typed, because I'm pretty easily confused.


I'm sorry, "2 = 3" was a really bad example that I used to try to
describe
something that I didn't know the word for. What I meant was something
like
"in most situations, it's a silly thing to do" (for lack of a better
phrase). Sort of like... assuming the = operator for a type has no
side
effects, then doing something like "A() = whatever;" doesn't have any
effect
on your program. If you removed it, everything would still be the
same.
That's kind of more what I meant. It was a very bad example.

Well, that depends. Here is another weird example:

typedef std::vector< int > int_vector;
int_vector x;

swap( x, int_vector() ); // illegal
swap( x, int_vector() = int_vector() ); // legal

So, using A() = A(), you can bind a temporary to a non-const reference. Such
trickery can be useful at times (not often!). The reason is that most
classes follow the precedence of the built-in operator= and return a
reference to *this.


[snip]

If you can call the assignment operator on a temporary, why are you
not
allowed to bind temporaries to non-const reference parameters? I mean,
code
like this *seems* entirely reasonable to me:

void function (A &a) {
// do some stuff to a here
}

void function2 (void) {
function(A());
}

It is totally reasonable. The problems stem from "interaction of language
features (misfeatures)".

Why is that illegal? You create a new A(), pass a reference to it to
function (so it's not copied when you call function(), it's created
before
the actual call is made), it's valid inside function(), you can do
stuff to
it, function() returns, statement ends, A() is destroyed. It doesn't
seem
dangerous, unpredictable, indeterminant, or anything. Do you know why
you
aren't allowed to do that, then (mostly for my own curiosity, I don't
actually have a program that I "need" to do something like that in)?

The reason that is usually given centers around problems arising from
impicit conversions. Consider:

void inc ( int & i ) {
i += 1;
}

double x;
inc( x );

If the double converts silently to a temporary int and that binds and gets
modified, the results would be surprising.


In any case, C++0X will introduce r-value references that allow temporaries
to bind.

And since temporaries are not necessarily const, why can they (even
the
non-const ones) only be bound to const references?

To avoid the surprises from automatic conversions.


*That* is what I was trying to say with the stupid "2=3" example. "No
point". Duh.


It's an off-topic discussion on a Borland newsgroup about exactly
this
topic. Whether or not you can bind A() to a non-const reference
parameter.
The source of the confusion is two things:

1) I'm not familiar enough with the standard to know what the real,
correct
behavior is, and
2) The Borland compiler *does*, in fact, accept the above code (with
"function(A());").

There was some confusion about which was correct so I asked here to
get a
real answer.

Well, [8.5.3/5] describes the precise rules.


[snip]


Best

Kai-Uwe Bux
 
J

jason.cipriani

Kai-Uwe Bux said:
Huh? The right to do what?

LOL... I meant "am I correct".
Well, that depends. Here is another weird example:

typedef std::vector< int > int_vector;
int_vector x;

swap( x, int_vector() ); // illegal
swap( x, int_vector() = int_vector() ); // legal

You're right. That is a weird example. Look, here is a weirder one.
You can do this to accomplish the same thing without creating a second
object or any additional temporaries (aside from whatever swap does):

template <class T> T& abracadabra (const T& v) {
return const_cast<T&>(v);
}

{
A a;
swap(a, abracadabra(A()));
}

That compiles with GCC, and Comeau likes it, too. Only two A's are
created ("a", and the A() passed to swap), and the assignment operator
is called only twice total (at least with the implementation of swap()
GCC comes with).

Thanks a lot for your replies, you answered pretty much all my
questions, the rest I'll figure out once I dig into those sections. I
appreciated it!

Jason
 
K

Kai-Uwe Bux

LOL... I meant "am I correct".


You're right. That is a weird example. Look, here is a weirder one.
You can do this to accomplish the same thing without creating a second
object or any additional temporaries (aside from whatever swap does):

template <class T> T& abracadabra (const T& v) {
return const_cast<T&>(v);
}

Ah, the infamous lvalue_cast.

{
A a;
swap(a, abracadabra(A()));
}

That compiles with GCC, and Comeau likes it, too. Only two A's are
created ("a", and the A() passed to swap), and the assignment operator
is called only twice total (at least with the implementation of swap()
GCC comes with).

It compiles, but it does have undefined behavior. The reason is explained
here:

http://groups.google.com/group/comp.lang.c++/browse_frm/thread/e8c8fb5450c8182d


However, it will have defined behavior in C++0X according to n2521.


[snip]

Best
 
J

jason.cipriani

Ah, the infamous lvalue_cast.

Everybody else is always two steps ahead of me!
It compiles, but it does have undefined behavior. The reason is explained
here:

Hm, what was an interesting read. What about the following code, is
that defined? I can't imagine a compiler that would be smart enough to
recognize what is going on and create some intermediate object in
between... assuming you haven't overloaded any relevant operators:

template <class T> T& abracadabra (const T& v) {
T *pr;
const T *pv = &v;
memcpy(&pr, &pv, sizeof(T *));
return *pr;
}

Or this... which I guess is different than the above because here you
are still casting away the const, so the compiler could figure out
what is happening (since you aren't hiding it through memcpy)?

template <class T> T& abracadabra (const T& v) {
return *(T *)&v;
}

Jason
 
J

jason.cipriani

template <class T> T& abracadabra (const T& v) {
T *pr;
const T *pv = &v;
memcpy(&pr, &pv, sizeof(T *));
return *pr;
}

Rather:

template <class T> T& abracadabra (const T& v) {
union {
T *pr;
const T* pv;
} u;
u.pv = &v;
return *u.pr;
}
 
V

Victor Bazarov

Rather:

template <class T> T& abracadabra (const T& v) {
union {
T *pr;
const T* pv;
} u;
u.pv = &v;
return *u.pr;

Accessing a member of a union that you didn't store in it has
undefined behaviour. Why don't you simply const_cast it?

V
 
E

Erik Wikström

If you can call the assignment operator on a temporary, why are you
not allowed to bind temporaries to non-const reference parameters? I
mean, code like this *seems* entirely reasonable to me:

void function (A &a) {
// do some stuff to a here
}

void function2 (void) {
function(A());
}

Why is that illegal? You create a new A(), pass a reference to it to
function (so it's not copied when you call function(), it's created
before the actual call is made), it's valid inside function(), you
can do stuff to it, function() returns, statement ends, A() is
destroyed. It doesn't seem dangerous, unpredictable, indeterminant,
or anything. Do you know why you aren't allowed to do that, then
(mostly for my own curiosity, I don't actually have a program that I
"need" to do something like that in)?

And since temporaries are not necessarily const, why can they (even
the non-const ones) only be bound to const references?

While it would work to allow to bind (non-const) references to temporary
objects in the above code it would not work in many other situations
that would also be allowed. Consider the following:

class Foo {
int i;
public:
void print() {
std::cout << i << std::endl;
}
};

void bar() {
Foo& f = Foo();
f.print();
}

In this example f is referring to a non-existing object when pring() is
called (it is a so called dangling reference). Binding temporaries to
const references is a special rule in C++, what happens is that the life
of the temporary object is extended to be as long as the life of the
reference it is bound to.
 
K

Kira Yamato

While it would work to allow to bind (non-const) references to temporary
objects in the above code it would not work in many other situations
that would also be allowed. Consider the following:

class Foo {
int i;
public:
void print() {
std::cout << i << std::endl;
}
};

void bar() {
Foo& f = Foo();
f.print();
}

In this example f is referring to a non-existing object when pring() is
called (it is a so called dangling reference). Binding temporaries to
const references is a special rule in C++, what happens is that the life
of the temporary object is extended to be as long as the life of the
reference it is bound to.

Oh. Nice to know. Thanks.
 
V

Victor Bazarov

Erik said:
[..]
While it would work to allow to bind (non-const) references to
temporary objects in the above code it would not work in many other
situations that would also be allowed. Consider the following:

class Foo {
int i;
public:
void print() {
std::cout << i << std::endl;
}
};

void bar() {
Foo& f = Foo();
f.print();
}

In this example f is referring to a non-existing object when pring()
is called (it is a so called dangling reference). Binding temporaries
to const references is a special rule in C++, what happens is that
the life of the temporary object is extended to be as long as the
life of the reference it is bound to.

What if the "special rule" would include refs to non-const? The
simple thing about it is that the temporary survives as long as
the reference to it. [The current rule allows only ref to const,
but the idea is the same, the temp survives as long as the ref]

Now, the problem is deeper than just the lifetime of the reference.
Binding is allowed with type conversion. If the conversion takes
place, there is another temporary created, to which the reference
is actually bound. As soon as you allow binding of a non-const
reference, you run into "what exactly am I changing" here (this is
the example Dr. Stroustrup uses in his D&E book):

void foo(int & i)
{
i = 42;
}

int main()
{
double d = 3.14159;
foo(d); // what's the value of 'd' after this?
}

At the time 'foo' is called, a temporary object of type 'int' is
created, and if the non-const reference is bound to it, the value
42 is assigned to that temporary object, but 'd' has nothing to
do with that. So, OOH 'foo' is supposed to change its argument's
value to 42, OTOH there is no way for it to accomplish that if
the factual argument is not of type 'int' but of type _convertible_
to 'int'.

V
 
J

jason.cipriani

... Why don't you simply const_cast it?

I did, in a previous post in this thread. The reason I didn't use
const_cast here is I am playing around trying to find other ways to do
that trick that don't involve explicitly casting the const away (which
also turned out to be undefined).

...
void bar() {
Foo& f = Foo();
f.print();
}
...
Binding temporaries to
const references is a special rule in C++, ...

Thanks for your reply. That makes sense. It looks like I also
misunderstood "binding to non-const references" in this context -- I
had always assumed that the case of function parameters was different
from the case of reference declarations and assignments. Your example
makes sense, but to me that seems different than passing a temporary
as a non-const reference parameter to a function (where no "lifetime
extensions" need to be made to keep it valid through the entire
function call). But, I guess function parameters and variable
declarations all follow the same rules?

Jason
 
K

Kai-Uwe Bux

Everybody else is always two steps ahead of me!


Hm, what was an interesting read. What about the following code, is
that defined? I can't imagine a compiler that would be smart enough to
recognize what is going on and create some intermediate object in
between... assuming you haven't overloaded any relevant operators:

template <class T> T& abracadabra (const T& v) {
T *pr;
const T *pv = &v;
memcpy(&pr, &pv, sizeof(T *));
return *pr;
}

Or this... which I guess is different than the above because here you
are still casting away the const, so the compiler could figure out
what is happening (since you aren't hiding it through memcpy)?

template <class T> T& abracadabra (const T& v) {
return *(T *)&v;
}

Neither of these has defined behavior. The problem is that the compiler does
not need to have a reason to create a const copy of the object when
initializing the reference. It is free to do that on Fridays, or every
other time, or randomly.

For some cases, the following works (by forcing a non-const copy):

template < typename T >
struct lvalue_cast_ref : public T {

lvalue_cast_ref ( T const & t )
: T ( t )
{}

operator T& ( void ) {
return ( *this );
}

};

template < typename T >
lvalue_cast_ref<T> lvalue_cast ( T const & t_ref ) {
return ( lvalue_cast_ref<T>( t_ref ) );
}

template < typename T >
T & lvalue_cast ( T & t_ref ) {
return ( t_ref );
}


#include <vector>
#include <algorithm>
#include <cassert>

typedef std::vector< int > int_vector;

int main ( void ) {
int_vector x ( 20, 20 );
swap( x, lvalue_cast( int_vector() ) );
assert( x.empty() );
}


Note however, that the above cannot work for built-in types.
Best

Kai-Uwe Bux
 
J

jason.cipriani

Thanks a lot for your replies.

Neither of these has defined behavior. The problem is that the compiler does
not need to have a reason to create a const copy of the object when
initializing the reference. It is free to do that on Fridays, or every
other time, or randomly.

I see... so, in other words, no matter what happens inside
abracadabra(), it's always going to be undefined because of the fact
that the function takes a const reference in the first place -- and
there's no rule that says that a const reference actually needs to
refer to the same instance of the object you bound to it? But if
that's the case, does that mean that the following is also undefined?

struct A {
mutable int v;
};

void function (const A &a) {
a.v = 3;
}

void function2 (void) {
// when calling a function...
A x;
function(x);
assert(x.v == 3); // <--- this may fail?
// and like this...
const A &y = x;
y.v = 4;
assert(x.v == 4); // <--- this may also fail?
}

Because in that situation, if you were typing your code with your
tongue hanging out the left side of your mouth instead of the right
and therefore the compiler decided to create a const copy of 'x' when
you called function(), then it's not necessarily x's 'v' member that
function() is modifying? Comeau does not warn about anything dangerous
there, though.
For some cases, the following works (by forcing a non-const copy):
[snip]

That was a really enlightening example, thanks. I think... it's
similar to something I tried when I was experimenting, adding this:

//...
WhateverType & self (void) { return *this; }
//...

To a WhateverType and then binding WhateverType().self() to non-const
references instead. Except that doesn't fit in with existing objects
and flow with existing syntax as nicely.

Thanks,
Jason
 
J

James Kanze

Erik said:
What if the "special rule" would include refs to non-const? The
simple thing about it is that the temporary survives as long as
the reference to it. [The current rule allows only ref to const,
but the idea is the same, the temp survives as long as the ref]

That is the rule. Any temporary which is used to initialize a
reference has its lifetime extended to that of the reference.

There is a second rule involved here: a temporary cannot be used
to initialize a reference unless that reference is to a const.
This second rule was added relatively late, however---it isn't
present in my copy of TC++PL ((c) 1986) for example (but there
is a statement that temporaries used to initialize references
have a lifetime of the scope, i.e. the same as the reference).
 
V

Victor Bazarov

James said:
Erik said:
What if the "special rule" would include refs to non-const? The
simple thing about it is that the temporary survives as long as
the reference to it. [The current rule allows only ref to const,
but the idea is the same, the temp survives as long as the ref]

That is the rule. Any temporary which is used to initialize a
reference has its lifetime extended to that of the reference.

There is a second rule involved here: a temporary cannot be used
to initialize a reference unless that reference is to a const.
This second rule was added relatively late, however---it isn't
present in my copy of TC++PL ((c) 1986) for example (but there
is a statement that temporaries used to initialize references
have a lifetime of the scope, i.e. the same as the reference).

James,

I know it's late where you are. But may I please bring the words
"what if" to your attention? Why are you reiterating what the
rule _is_ when I am talking about what the rule _might_be_?

V
 
K

Kai-Uwe Bux

Erik said:
While it would work to allow to bind (non-const) references to temporary
objects in the above code it would not work in many other situations
that would also be allowed. Consider the following:

class Foo {
int i;
public:
void print() {
std::cout << i << std::endl;
}
};

void bar() {
Foo& f = Foo();
f.print();
}

In this example f is referring to a non-existing object when pring() is
called (it is a so called dangling reference). Binding temporaries to
const references is a special rule in C++, what happens is that the life
of the temporary object is extended to be as long as the life of the
reference it is bound to.

That is not entirely correct. The standard says [12.2/4-5]:

There are two contexts in which temporaries are destroyed at a different
point than the end of the fullexpression. The first context is ...

The second context is when a reference is bound to a temporary. The
temporary to which the reference is bound or the temporary that is the
complete object to a subobject of which the temporary is bound persists
for the lifetime of the reference except as specified below.

and then a list of exceptions to the rule follows. Note that the life-time
extension does not depend on the constness or non-constness of the
reference. In particular, if [8.5.3/5] was worded to allow temporaries to
bind to non-const references, your code snippet would be perfectly fine.


Best

Kai-Uwe Bux
 
J

James Kanze

James said:
Erik Wikström wrote:
[..]
What if the "special rule" would include refs to non-const? The
simple thing about it is that the temporary survives as long as
the reference to it. [The current rule allows only ref to const,
but the idea is the same, the temp survives as long as the ref]
That is the rule. Any temporary which is used to initialize a
reference has its lifetime extended to that of the reference.
There is a second rule involved here: a temporary cannot be used
to initialize a reference unless that reference is to a const.
This second rule was added relatively late, however---it isn't
present in my copy of TC++PL ((c) 1986) for example (but there
is a statement that temporaries used to initialize references
have a lifetime of the scope, i.e. the same as the reference).
I know it's late where you are. But may I please bring the words
"what if" to your attention? Why are you reiterating what the
rule _is_ when I am talking about what the rule _might_be_?

No. You're talking about what the rule _was_.

As Kai-Uwe has pointed out, there is no special rule concerning
lifetime for "references to const"---the rule concerns
temporaries used to initialize a reference. Any reference.
(From context, I presume that this is the "special rule" you
were refering to. Given the number of special rules in C++,
however, one can never be sure:).) In other words, your "what
if" is already the case.
 

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,769
Messages
2,569,579
Members
45,053
Latest member
BrodieSola

Latest Threads

Top