Re: GotW #88: Is it safe to const_cast a reference to a temporary?
* James Kanze:
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, ?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.
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. ;-)
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).
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: Because it messes up the order in which people normally read text.
Q: Why is it such a bad thing?
A: Top-posting.
Q: What is the most annoying thing on usenet and in e-mail?