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?
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?
Brake service generally comes to approximately $200 to $500 per axle at a professional shop. Calipers are typically the most difficult and expensive aspect of the braking system to service. A single caliper can cost up to $130 and several will reach prices even higher.
You can expect to pay between $30 and $75 per rotor and between $150 and $200 for the labor for each axle, for a total of between $250 and $500 per axle. Calipers are the most expensive part of the brake system to replace. A single caliper can cost around $130.
They're usually going to take around one to three hours on average. There are a variety of brake repair types to consider, with some taking longer than others.
Does the placement of a car's brake calipers affect its braking performance? The caliper placement (top, bottom, front, rear) does not affect the stopping performance of the brakes, or the cooling of the pads and rotors.
I believe the motivation is to permit the compiler to cache the values of const
objects (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:
struct Foo {
const int & x;
Foo (int & i) : x(i) {}
};
int i = 1;
Foo f(i);
some_function_in_another_TU(&f);
std::cout << f.x;
Into this:
struct Foo {
const int & x;
Foo (int & i) : x(i) {}
};
int i = 1;
Foo f(i);
some_function_in_another_TU(&f);
std::cout << i; // this line is optimized
because the reference member of f
cannot be reseated, and hence must still refer to i
. The destruct-and-construct operation violates the non-reaseatable-ness of the reference member x
.
This optimization should not be particularly controversial: consider the following example, using a const
object rather than an object with a const
or reference member:
const int i = 1;
some_function_in_another_TU(&i);
std::cout << i;
Here i
is a compile-time constant, some_function_in_another_TU
cannot validly destroy it and create another int
in its place with a different value. So the compiler should be allowed to emit code for std::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 const
data member, then a useful invariant of the language (references are never reseated and const objects never change their values) would be broken.
As far as I can tell, it's just a matter of semantic correctness, and the adherent assumptions that the optimizer may make. Consider this:
Bar important, relevant;
Foo x(important); // binds as const-reference
Zoo z(x); // also binds as const reference
do_stuff(z);
x.~Foo();
::new (&x) Foo(relevant); // Ouch?
The object z
may reasonably expect its Foo
member reference to be constant and thus refer to important
. As the standard says, the destruction plus new construction in the last two lines "automatically updates all references to refer to the (logically) new object", so now the const-reference inside z
has changed, despite the promise of being constant.
To avoid this backstabbing violation of const-correctness, the entire reconstruction-in-place is forbidden.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With