Following the discussion on my answer to this question, apparently:
the following code is allowed
struct Foo {
int x;
};
Foo f;
Foo & f_ref = f;
(&f) -> ~Foo ();
new (&f) Foo ();
int x = f_ref .x;
but the following code is not allowed
struct Foo {
const int & x; // difference is const reference
Foo (int & i) : x(i) {}
};
int i;
Foo f (i);
Foo & f_ref = f;
(&f) -> ~Foo ();
new (&f) Foo (i);
int x = f_ref .x;
Because of $3.8/7
If, after the lifetime of an object has ended and before the storage which the object occupied is reused or released, a new object is created at the storage location which the original object occupied, a pointer that pointed to the original object, a reference that referred to the original object, or the name of the original object will automatically refer to the new object and, once the lifetime of the new object has started, can be used to manipulate the new object, if:
- the type of the original object is not const-qualified, and, if a class type, does not contain any non-static data member whose type is const-qualified or a reference type …
I can understand how a reference to f.x could be invalidated when f ceases to exist, but I don’t see why f_ref should be invalidated purely because one of its members is const and/or reference and not otherwise: it was a reference to a Foo before and is a reference to a Foo afterwards.
Can someone please explain the rationale behind this condition?
Edit
Thanks for the answers. I don’t buy the “guarantee it doesn’t change” argument because we don’t currently allow optimisers to cache referands, for example:
struct Foo {
const int & x;
Foo (const int & i) : x(i) {}
void do_it ();
};
int i;
Foo f (i);
const int & ii = f.x;
f .do_it (); // may modify i
std :: cout << ii; // May NOT use cached i
I don’t see how do_it is allowed to invalidate referenced values but operator new isn’t — Sequence points invalidate cached values: why should delete/placement-new be exempt?
I believe the motivation is to permit the compiler to cache the values of
constobjects (note that’s const objects, not merely referands of pointers-to-const and reference-to-const), and the addresses of referands of references, across calls to unknown code.In your second example, the compiler can “see” firstly that the object has been created and destroyed, and secondly that it was re-created using the same value. But the authors of the standard wanted compilers to be allowed to turn this code:
Into this:
because the reference member of
fcannot be reseated, and hence must still refer toi. The destruct-and-construct operation violates the non-reaseatable-ness of the reference memberx.This optimization should not be particularly controversial: consider the following example, using a
constobject rather than an object with aconstor reference member:Here
iis a compile-time constant,some_function_in_another_TUcannot validly destroy it and create anotherintin its place with a different value. So the compiler should be allowed to emit code forstd::cout << 1;The idea is that the same should be true by analogy for const objects of other types, and for references.If a call to unknown code could reseat a reference member, or alter the value of a
constdata member, then a useful invariant of the language (references are never reseated and const objects never change their values) would be broken.