GotW #88: Is it safe to const_cast a reference to a temporary?

  • Thread starter Niels Dekker - no return address
  • Start date
N

Niels Dekker - no return address

Herb Sutter wrote a new Guru-of-the-Week column last month, GotW #88,
about binding a temporary object to a reference-to-const. Now if this
temporary isn't const, is it safe to const_cast the reference?

#include <string>
#include <cassert>
using std::string;

string f() { return "abc"; }

void h() {
const string& s1 = f();
const string& s2 = s1;
const_cast<string&>(s1) = "new value!"; // Safe?
assert(s1 == "new value!"); // Okay?
assert(s2 == "new value!"); // Okay?
}

See also:
GotW #88 - A Candidate For the "Most Important const."
http://herbsutter.spaces.live.com/blog/cns!2D4327CC297151BB!378.entry

Kind regards,
 
A

Alf P. Steinbach

* Niels Dekker - no return address:
Herb Sutter wrote a new Guru-of-the-Week column last month, GotW #88,
about binding a temporary object to a reference-to-const. Now if this
temporary isn't const, is it safe to const_cast the reference?

#include <string>
#include <cassert>
using std::string;

string f() { return "abc"; }

void h() {
const string& s1 = f();
const string& s2 = s1;
const_cast<string&>(s1) = "new value!"; // Safe?

Not in standard C++, although it might be safe with a particular compiler.

The short of it is that you're misinforming the compiler, and the almost
as short of it is that the compiler is free to optimize away the call to
f and e.g. substitute the value string("abc") wherever s1 and s2 are used.

The bit-longer of it is that the temporary was const to begin with (the
temporary bound to the reference, not the temporary returned by the
function, although they might end up being the same), and you can't cast
away /original/ const'ness with portable well-defined result.

assert(s1 == "new value!"); // Okay?
assert(s2 == "new value!"); // Okay?
}

See also:
GotW #88 - A Candidate For the "Most Important const."
http://herbsutter.spaces.live.com/blog/cns!2D4327CC297151BB!378.entry

Would IMHO be better if Herb posted his GotW questions here, as he did
for the first few, and summarized the responses + his extra insights
(AFAIK the PIMPL idiom GOTW with erronous auto_ptr still not corrected).


Cheers, & hth.,

- Alf
 
A

Alf P. Steinbach

* Alf P. Steinbach:
* Niels Dekker - no return address:

Not in standard C++, although it might be safe with a particular compiler.

The short of it is that you're misinforming the compiler, and the almost
as short of it is that the compiler is free to optimize away the call to
f and e.g. substitute the value string("abc") wherever s1 and s2 are used.

Except for taking address, which includes passing by reference, which
applies to just about anything it could be used for.

I didn't think of that.

And I'm not sure there is any qualification of "used" that would make
the last statement above true.

Sorry about hasty trigger finger -- correcting this before Someone
Else does.

However, rest applies:

The bit-longer of it is that the temporary was const to begin with (the
temporary bound to the reference, not the temporary returned by the
function, although they might end up being the same), and you can't cast
away /original/ const'ness with portable well-defined result.



Would IMHO be better if Herb posted his GotW questions here, as he did
for the first few, and summarized the responses + his extra insights
(AFAIK the PIMPL idiom GOTW with erronous auto_ptr still not corrected).

Cheers, & hth.,

- Alf (wondering why the Send button doesn't say "Do you Really want to
Send?")
 
N

Niels Dekker - no return address

Herb Sutter wrote a new Guru-of-the-Week column last month, GotW #88,
about binding a temporary object to a reference-to-const. Now if this
temporary isn't const, is it safe to const_cast the reference?
const string& s1 = f();
const_cast<string&>(s1) = "new value!"; // Safe?

Alf P. Steinbach replied:
Thanks! It looked too much like a hack anyway...
Except for taking address, which includes passing by reference, which
applies to just about anything it could be used for.

Okay, that makes sense.

But I guess in C++0x we can bind the temporary object returned by f() to
an rvalue reference:

string && s1 = f(); // C++0x
s1 = "new value!";

Still this wouldn't work if the return type of f() would have been
declared as "const":
const string f(void);

Right?

Kind regards, Niels
 
J

James Kanze

* Niels Dekker - no return address:
Not in standard C++, although it might be safe with a
particular compiler.

This has always been my believe as well, and I'm almost sure
that I once read somewhere in the standard something to the
effect that any attempt to modify a temporary (e.g. by such
tricks) was undefined behavior. The last time I looked for it,
however, I couldn't find it. So could you tell me where in the
standard this is forbidden. It's tricky, because you are
allowed to call a non-const function on a temporary, and the
assignment operator of std::string is a non-const function.

There are really two separate questions: what about:
int const& i = 42 ;
const_cast< int& >( i ) = 0 ;
I certainly hope that it's illegal, but that was the case I was
actually looking for, and couldn't find.

It's frustrating me, because I really want the example with int
to be undefined behavior, and I'm sure that it is, but I can't
find the words in the standard to back it up.
The short of it is that you're misinforming the compiler, and
the almost as short of it is that the compiler is free to
optimize away the call to f and e.g. substitute the value
string("abc") wherever s1 and s2 are used.

This isn't generally true; a compiler cannot assume that because
a reference to const evaluated to a certain value once, it will
evaluate to that value a second time. The question is: when can
it make such assumptions?
The bit-longer of it is that the temporary was const to begin
with (the temporary bound to the reference, not the temporary
returned by the function, although they might end up being the
same), and you can't cast away /original/ const'ness with
portable well-defined result.

Except that in the example, the temporary wasn't const to begin
with. The function f() retuns an std::string, and not an
std::string const. If f() had returned an std::string const,
then the code definitely has undefined behavior.

Again, we have two different cases to consider. Temporaries of
non-class types are never const.
Would IMHO be better if Herb posted his GotW questions here,
as he did for the first few,

More than just a few:). Certainly, if he wants people to
comment on them, or even read them, he should post them
somewhere that people read regularly. Nothing against Herb's
site, but I don't visit it every week just to see if there's
anything new.
 
A

Alf P. Steinbach

* James Kanze:
This has always been my believe as well, and I'm almost sure
that I once read somewhere in the standard something to the
effect that any attempt to modify a temporary (e.g. by such
tricks) was undefined behavior. The last time I looked for it,
however, I couldn't find it. So could you tell me where in the
standard this is forbidden.

Yes. With respect to the 1998 standard there are two rules involved:
first, a rule saying that modifying a (we must presume "original" is
meant) const object incurs Undefined Behavior, §7.1.5.1/4, and second, a
rule that that the reference can be, at the implementation's discretion,
a reference to an original const object (a new const temporary that is
copy constructed from the initializer), §8.5.3/5.

There is a problem with this, and that is that the relevant part of
§8.5.3/5, at least as I interpret is, is also the culprit responsible
for requiring a copy constructor when passing an rvalue of class type to
T const& argument, and as I recall it has therefore been changed the
C++0x draft. Checking...

Yep, in the n2315 draft (I don't have the latest) it has been changed,
and the reference bound directly, no implementation discretion -- and
I gather that that otherwise desirable change may just open the door for
code such as above, in C++0x... And hm, what about std::auto_ptr. :-(

It's tricky, because you are
allowed to call a non-const function on a temporary, and the
assignment operator of std::string is a non-const function.

I don't think that's relevant. I think you perhaps were thinking of
only direct binding of the reference, as in the C++0x draft, and that
what you point out here would indicate that the temporary returned by
the function is not really const. And it isn't, but the temporary the
reference is bound to per the C++ 1998 standard, can be original const
(if a copy is made it is required to be original const).

There are really two separate questions: what about:
int const& i = 42 ;
const_cast< int& >( i ) = 0 ;
I certainly hope that it's illegal, but that was the case I was
actually looking for, and couldn't find.

It's the same paragraph, §8.5.3/5, but a different part a bit further
down. Also here the temporary is original const.

It's frustrating me, because I really want the example with int
to be undefined behavior, and I'm sure that it is, but I can't
find the words in the standard to back it up.

See above.

This isn't generally true; a compiler cannot assume that because
a reference to const evaluated to a certain value once, it will
evaluate to that value a second time. The question is: when can
it make such assumptions?

Note: I corrected the original posting almost immediately (see that
follow-up), because taking the address of the object is one usage where
the same object must be involved.

Per the 1998 standard one case for use of rvalue is as above, where the
compiler is free to bind the reference to a newly constructed original
const copy of the initializer, because modifying that original const
object is UB.


Except that in the example, the temporary wasn't const to begin
with. The function f() retuns an std::string, and not an
std::string const. If f() had returned an std::string const,
then the code definitely has undefined behavior.

In the current standard the constness of f()'s result doesn't matter,
because it's not necessarily the object the reference is bound to.


Cheers, & hth.,

- Alf
 
A

Alf P. Steinbach

* Niels Dekker - no return address:
But I guess in C++0x we can bind the temporary object returned by f() to
an rvalue reference:

string && s1 = f(); // C++0x
s1 = "new value!";

Still this wouldn't work if the return type of f() would have been
declared as "const":
const string f(void);

Right?

The last about const, yes I think obviously, but the first, I don't
know: if rvalue references are to be useful that would be one case they
should be able to handle, but I don't know more about rvalue references
than the general impression that they can perform some black magic, and
essentially provide language support for "moving construction", allowing
you to freely implement functions that return large objects by value.

Cheers,

- Alf
 
J

James Kanze

* James Kanze:
Yes. With respect to the 1998 standard there are two rules
involved: first, a rule saying that modifying a (we must
presume "original" is meant) const object incurs Undefined
Behavior, §7.1.5.1/4, and second, a rule that that the
reference can be, at the implementation's discretion, a
reference to an original const object (a new const temporary
that is copy constructed from the initializer), §8.5.3/5.

I don't see where "orignal" is relevant. This rule concerns the
object itself, and not the type of expression used to refer to
it. In the sample code above, none of the objects are const.
That is, IMHO, the crux of the problem. (And I very definitely
remember reading somewhere that attempting to modify an
rvalue---or maybe it was only an rvalue of non-class type---was
undefined behavior, regardless of the const.)
There is a problem with this, and that is that the relevant part of
§8.5.3/5, at least as I interpret is, is also the culprit responsible
for requiring a copy constructor when passing an rvalue of class type to
T const& argument, and as I recall it has therefore been changed the
C++0x draft. Checking...
Yep, in the n2315 draft (I don't have the latest) it has been changed,
and the reference bound directly, no implementation discretion -- and
I gather that that otherwise desirable change may just open the door for
code such as above, in C++0x... And hm, what about std::auto_ptr. :-(
I don't think that's relevant. I think you perhaps were thinking of
only direct binding of the reference, as in the C++0x draft, and that
what you point out here would indicate that the temporary returned by
the function is not really const. And it isn't, but the temporary the
reference is bound to per the C++ 1998 standard, can be original const
(if a copy is made it is required to be original const).

No. I'm thinking of the object itself. The temporary, and not
the reference. The const in the reference doesn't affect the
const-ness of the temporary. For example:
int i = 43 ;
int const& ri = i ;
const_cast< int& >( ri ) = 0 ;
is perfectly defined and legal. (Which, of course, doesn't say
that it's good code.)

In the original code, the function returns a non-const object.
Something like:
f() = "whatever" ;
is perfectly legal (although not very useful). So is:
std::string s( "abc" ) ;
std::string const& rs = s ;
static_cast< std::string& >( rs ) = "whatever" ;
The original code looks very much like a combination of these
two cases, and in the absense of some special rule, is clearly
legal and well defined.
It's the same paragraph, §8.5.3/5, but a different part a bit
further down. Also here the temporary is original const.

I don't think so. In general, "temporary" and "rvalue" are
synonyms. And §3.10/9 very clearly says "Class rvalues can have
cv-qualified types; non-class rvalues always have cv-unqualified
types." A temporary of type int can never be const.
See above.

I get the feeling that there must be something obvious I'm
missing, but I just can't see it. The reference must refer to
const, but references to const can definitly be bound to
non-const objects, and when they are, casting away const and
modifying the object is well defined behavior. So we need
a special rule to forbid modifying the (non-const) temporary.
Note: I corrected the original posting almost immediately (see
that follow-up), because taking the address of the object is
one usage where the same object must be involved.

That wasn't what I was thinking of. The most obvious example:

int global = 0 ;

void
f( int const& i )
{
std::cout << i << std::endl ;
++ global ;
std::cout << i << std::endl ;
}

int
main()
{
f( global ) ;
}

This program had better output 0, then 1.

Change f() to:

void
f( int const& i )
{
std::cout << i << std::endl ;
++ const_cast< int& >( i ) ;
std::cout << i << std::endl ;
}

and it's still well defined. Declare "global" const, and the
above involves undefined behavior, however. The defined-ness
depends on the const-ness of the object refered to, and not on
the const-ness of the reference.

Which brings us back to my original problem: the temporaries
here don't have a const type---and in the case of non-class
types, cannot have a const types. So there must be some
additional rule somewhere which makes attempting to modify them
undefined behavior.
Per the 1998 standard one case for use of rvalue is as above,
where the compiler is free to bind the reference to a newly
constructed original const copy of the initializer, because
modifying that original const object is UB.
In the current standard the constness of f()'s result doesn't
matter, because it's not necessarily the object the reference
is bound to.

Which has the consequence that even if f() were declared to
return a std::string const, the code might have defined
behavior? (In fact, the standards committee has changed this,
and I think we can ignore it. The problem that I see is that
the original temporary object is not const. And so casting away
const and modifying it is well defined behavior.)
 
A

Alf P. Steinbach

* James Kanze:
I don't see where "orignal" is relevant. This rule concerns the
object itself, and not the type of expression used to refer to
it. In the sample code above, none of the objects are const.
That is, IMHO, the crux of the problem.

No, the reference's type -- its cv-qualification -- is very relevant
because the cv-qualification can be transferred to a temporary created
for the purpose, and no, it's not the case that one is guaranteed that
none of the objects are const, in the code above.

Trying to be utterly unambigiously clear:

§5.1.3/5
"A reference to type "cv1 T1" is initialized by an expression of
type "cv2 T2" as follows:

...

- Otherwise, the reference shall be to a non-volatile const type
(i.e. cv1 shall be const)
[Example: ...]

- If the initializer expression is an rvalue, with T2 a class
type, and "cv1 T1" is reference-compatible with "cv2 T2", the
reference is bound in one of the following ways (the choice is
implementation-defined):

- The reference is bound to the object represented by the rvalue
(see 3.10) or to a sub-object within that object.

- A temporary of type "cv1 T2" [sic] is created, and a constructor
is called to copy the entire rvalue into the temporary. The
reference is bound to the temporary or to sub-object within
the temporary.

The last alternative above is the one that, together with UB for
modification of const, dictates UB for the example code. And what
happens in this case is that "cv1" is "const", from the declaration of
the /reference/ s1, "T2" is "string", from the declaration of f(), and
the type of the temporary created is then "const string" -- which is
not a typo, but intentional ([sic] means essentially that "this is /not/
a typo"). This is then what the reference is bound to when this case is
chosen, namely a new temporary of type "const string".

And the pure possibility of that alternative being chosen, and that
alternative implying UB, means that formally the code is UB.

(And I very definitely
remember reading somewhere that attempting to modify an
rvalue---or maybe it was only an rvalue of non-class type---was
undefined behavior, regardless of the const.)

For rvalue of non-class type yes I think so, but for rvalue of class
type, no, quite the opposite.

But I've done enough standardeeze for today, I think. ;-)

No. I'm thinking of the object itself. The temporary, and not
the reference. The const in the reference doesn't affect the
const-ness of the temporary.

Does, in this situation -- see above.


Cheers, & hth.,

- Alf
 
A

Alf P. Steinbach

* Alf P. Steinbach:
Trying to be utterly unambigiously clear:

§5.1.3/5

Huh, who ordered /that/? Of course it should be §8.5.3/5, as I
mentioned several times already. There's a gremlin in my keyboard.


Cheers,

- Alf
 
J

James Kanze

* James Kanze:
No, the reference's type -- its cv-qualification -- is very
relevant because the cv-qualification can be transferred to a
temporary created for the purpose, and no, it's not the case
that one is guaranteed that none of the objects are const, in
the code above.
Trying to be utterly unambigiously clear:
§5.1.3/5
"A reference to type "cv1 T1" is initialized by an expression of
type "cv2 T2" as follows:

- Otherwise, the reference shall be to a non-volatile const type
(i.e. cv1 shall be const)
[Example: ...]
- If the initializer expression is an rvalue, with T2 a class
type, and "cv1 T1" is reference-compatible with "cv2 T2", the
reference is bound in one of the following ways (the choice is
implementation-defined):
- The reference is bound to the object represented by the rvalue
(see 3.10) or to a sub-object within that object.
- A temporary of type "cv1 T2" [sic] is created, and a constructor
is called to copy the entire rvalue into the temporary. The
reference is bound to the temporary or to sub-object within
the temporary.
The last alternative above

Which is a point I'd definitely overlooked. Of course, this
alternative has been dropped from the latest draft.
is the one that, together with UB for modification of const,
dictates UB for the example code. And what happens in this
case is that "cv1" is "const", from the declaration of the
/reference/ s1, "T2" is "string", from the declaration of f(),
and the type of the temporary created is then "const string"
-- which is not a typo, but intentional ([sic] means
essentially that "this is /not/ a typo"). This is then what
the reference is bound to when this case is chosen, namely a
new temporary of type "const string".

Yep. That one character (a 1 instead of a 2) makes a world of
difference here.
And the pure possibility of that alternative being chosen, and
that alternative implying UB, means that formally the code is
UB.

Agreed. (That's another thing I'm 100% sure of, but don't know
off hand where to find it in the standard. But of course,
nothing else would make sense.)

I wonder if the committee realizes that by dropping this
alternative, they're suddenly making undefined behavior defined.
For rvalue of non-class type yes I think so, but for rvalue of
class type, no, quite the opposite.
But I've done enough standardeeze for today, I think. ;-)

I'll admit that in this case, it's rather sterile. Because even
if all this were perfectly defined, I still wouldn't want to see
it in actual code.

I think, however, that I'll raise the question on the reflectors
(since comp.std.c++ seems rather dead at the moment). I
certainly wouldn't mind seeing a phrase added to the effect that
any attempt to modify an rvalue bound to a reference is
undefined behavior. (Reminds me too much of some early
Fortrans, where f(0) might actually call f() with 1 as an
argument, if a previous call to f() had modified the value.)
 
N

Niels Dekker - no return address

James said:
I think, however, that I'll raise the question on the reflectors
(since comp.std.c++ seems rather dead at the moment). I
certainly wouldn't mind seeing a phrase added to the effect that
any attempt to modify an rvalue bound to a reference is
undefined behavior.

Would that include modifying a *mutable* data member of an rvalue?

Anyway, I just added a link to this discussion on Herb Sutter's blog:
http://herbsutter.spaces.live.com/blog/cns!2D4327CC297151BB!378.entry
Thanks for your feedback!

Niels
 
A

Alf P. Steinbach

* Niels Dekker - no return address:
Would that include modifying a *mutable* data member of an rvalue?

The question is one of supporting or not optimizations based on the
compiler knowing that a value can't be modified without UB. So
presumably the rule should simply be that the object referred to by an
ordinary reference bound to an rvalue, is regarded as a const object
(originally const object) with regard to later access. Then modifying a
mutable member would be OK, and this rule would just reinstate the
relevant part of the rule that was removed in the C++0x draft.

Just my 2c,

- Alf
 
A

Alf P. Steinbach

* Niels Dekker - no return address:
Anyway, I just added a link to this discussion on Herb Sutter's blog:
http://herbsutter.spaces.live.com/blog/cns!2D4327CC297151BB!378.entry
Thanks for your feedback!

Niels, could you please add a comment for me (that darned system
requires some "Windows Live ID" whatever the heck that is):

Herb's attribution/credit for ScopeGuard is incorrect.

It was Petru Marginean's child, not Andrei Alexandrescu's.

Though Andrei helped add another abstraction layer, and co-authored the
DDJ article (I think it was DDJ, unless it was C/C++ User's Journal).


Cheers, & a bit frustrated, & TIA.,

- Alf
 
S

steady.li

by the way , I tested what you are dicussing using VC2005 but find
another problem that confused me. who can help me understand it?

int a = 1;
const int& b= 1; // legal //using the mechanism is using const
reference to temporary.
//int& c = 1; //illegal // why? but according to what you talked ,
it is not the rule though, it can
be supported by VS2005, just like
string& s1 = f();
int& d= a; //legeal
const int& e = a; //legal // why it is legal? const reference also
can point to non-const content memory?
int& f = b; //ilegal; from const to non-const error
 
A

Alf P. Steinbach

* (e-mail address removed):
by the way , I tested what you are dicussing using VC2005 but find
another problem that confused me. who can help me understand it?

int a = 1;
const int& b= 1; // legal //using the mechanism is using const
reference to temporary.
//int& c = 1; //illegal // why? but according to what you talked ,
it is not the rule though, it can
be supported by VS2005, just like

I think you mean, why is it disallowed by the standard, when there
exists at least one compiler that does support it (for class type
objects) as a language extension, showing by example that it could in
principle be allowed.

One example is

void foo( int& x ) { x = 666; }

int main()
{
foo( 42 );
}

One would want this to not compile.

Another example involves implicit type conversion, where the object
modified is not the actual argument but a temporary created from the
actual argument. One would want that also to not compile, instead of
silently doing nothing. However, the problem could be avoided by
disallowing automatic creation of a temporary for such arguments.

The short summary is that disallowing that binding is practically
useful, whereas allowing it (as it was originally) turned out to create
a host of problems.

The Annotated Reference Manual (a.k.a. the "ARM") says of the earlier
more permissive rule that it was "a major source of errors and surprises".

string& s1 = f();

This is invalid unless f() returns a reference or an object convertible
to reference.

int& d= a; //legeal
const int& e = a; //legal // why it is legal? const reference also
can point to non-const content memory?

Yes. It doesn't make that object const in itself. It just restricts
what you can do with the object via the reference.


Cheers, & hth.,

- Alf
 
N

Niels Dekker - no return address

Anyway, I just added a link to this discussion on Herb Sutter's blog:
Niels, could you please add a comment for me (that darned system
requires some "Windows Live ID" whatever the heck that is):

Herb's attribution/credit for ScopeGuard is incorrect.
It was Petru Marginean's child, not Andrei Alexandrescu's.

Though Andrei helped add another abstraction layer, and co-authored the
DDJ article (I think it was DDJ, unless it was C/C++ User's Journal).

I'm sorry for you but it's not such a big deal to me. There's a link to
the article by Andrei and Petru on Herb's blog entry. And there's a
link from his page to this newsgroup thread as well, by now. :) So a
reader of Herb's blog can easily find out that the ScopeGuard is
originally from Petru...

If you still think Herb's blog should explicitly mention Petru (which
would of course be more correct), why don't you just send him an
e-mail? His mail address is at www.gotw.ca. Otherwise just get
yourself that "Windows Live ID", whatever the heck. You can get it for
free, at least if you're willing to give some of your personal
information to Microsoft...

Hope that helps,

Thanks again for your feedback,
 

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

Forum statistics

Threads
473,769
Messages
2,569,579
Members
45,053
Latest member
BrodieSola

Latest Threads

Top