Re: GotW #88: Is it safe to const_cast a reference to a temporary?
On Feb 3, 3:55 pm, "Alf P. Steinbach" <al...@start.no> wrote:
* James Kanze:
On Feb 2, 10:37 pm, "Alf P. Steinbach" <al...@start.no> wrote:
* 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.
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, =A77.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), =A78.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
=A78.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).
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.
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, =A78.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 =A73.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.
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.
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.
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?
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.
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.
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.)
--
James Kanze (GABI Software) email:james.kanze@gmail.com
Conseils en informatique orient=E9e objet/
Beratung in objektorientierter Datenverarbeitung
9 place S=E9mard, 78210 St.-Cyr-l'=C9cole, France, +33 (0)1 30 23 00 34