c++undefined-behaviormutableconst-cast

const_cast vs mutable and undefined behavior


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


Solution

  • 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.