Edit: I edited the code here to not use pointers because there were too many unrelated comments about it
#include <iostream>
struct Foo {
Foo(const int a) : total(a) {}
int index = 0;
const int total;
};
struct Bar {
Bar(const int a) : total(a) {}
mutable int index = 0;
const int total;
};
int main() {
const Foo foo(3);
for (
;
foo.index < foo.total;
const_cast<Foo*>(&foo)->index++ // 1. Undefined behavior because foo is const
)
std::cout << "Foo " << foo.index << std::endl;
const Bar bar(3);
for (
;
bar.index < bar.total;
bar.index++ // 2. Not undefined behavior because of mutable?
)
std::cout << "Bar " << bar.index << std::endl;
return 0;
}
To the best of my knowledge the line labeled with // 1. Undefined behavior because foo is const
is undefined behavior because foo
is a const
object and const_cast
is being used to modify the object anyway
However I am uncertain if there is any possible undefined behavior related to the line labeled with // 2. Not undefined behavior because of mutable?
. It essentially is achieving the same result by using mutable
instead of const_cast
My question then is whether there are cases where a const
object with mutable
members can cause undefined behavior
A mutable member subobject of a const object is itself not a const object and none of the special rules for const objects, such as not being allowed to modify it, apply to it.
The containing complete object and other non-mutable subobjects of that subobject, recursively, are still const objects and so may not be modified.
The definition of const object can be found in [basic.type.qualifier/1.1] of the standard:
(1.1) A const object is an object of type
const T
or a non-mutable subobject of a const object.
So modifying the mutable
member subobject or any of its subobjects is always fine, even if the containing object is const
, but this applies only to the mutable member. Whether any const_cast
is involved is irrelevant.
However, this is only covers actual modification of the mutable subobject, i.e. built-in assignment to itself or one of its (scalar) subobjects. What is still not permitted is to replace the mutable subobject via placement-new with a new object. The relevant rule about replacing const objects is that no new objects may be placed in any storage that has been or will be occupied by a const-complete object of automatic, static or thread-local storage duration. In your case bar
is a const object and a complete object. It has automatic storage duration and the mutable subobject occupies part of its storage.