Is it legal to cast const away of a non-static const field with guaranteed non const allocation

Viewed 89

I have the following code which seems to work always (msvc, gcc and clang).

But I'm not sure if it is really legal. In my framework my classes may have "two constructors" - one normal C++ constructor which does simple member initialization and an additional member function "Ctor" which executes additional initialization code. It is used to allow for example calls to virtual functions. These calls are handled by a generic allocation/construction function - something like "make_shared".

The code:

#include <iostream>

class Foo
{
    public:
      constexpr Foo() : someConstField(){}
    public:
        inline void Ctor(int i)
        {
            //use Ctor as real constructor to allow for example calls to virtual functions
            const_cast<int&>(this->someConstField) = i;
        }
    public:
      const int someConstField;
};

int main()
{
    //done by a generic allocation function
    Foo f;
    f.Ctor(12); //after this call someConstField is really const!

    //
    std::cout << f.someConstField;
}
5 Answers

Modifying const memory is undefined behaviour. Here that int has already been allocated in const memory by the default constructor.

Honestly I am not sure why you want to do this in the first place. If you want to be able to initalise Foo with an int just create an overloaded constructor:

...
    constexpr Foo(int i) : someConstField{i} {}

This is completely legal, you are initalising the const memory when it is created and all is good.

If for some reason you want to have your object initalised in two stages (which without a factory function is not a good idea) then you cannot, and should not, use a const member variable. After all, if it could change after the object was created then it would no longer be const.

As a general rule of thumb you shouldn't have const member variables since it causes lots of problems with, for example, moving an object.

When I say "const memory" here, what I mean is const qualified memory by the rules of the language. So while the memory itself may or may not be writable at the machine level, it really doesn't matter since the compiler will do whatever it likes (generally it just ignores any writes to that memory but this is UB so it could do literally anything).

No.

It is undefined behaviour to modify a const value. The const_cast itself is fine, it's the modification that's the problem.

According to 7.1.6.1 in C++17 standard

Except that any class member declared mutable (7.1.1) can be modified, any attempt to modify a const object during its lifetime (3.8) results in undefined behavior.

And there is an example (similar to yours, except not for class member):

const int* ciq = new const int (3); // initialized as required
int* iq = const_cast<int*>(ciq); // cast required
*iq = 4; // undefined: modifies a const object

I suggest, that you use the constructor to avoid the const cast. You commented, that after your call of Ctor the value of someConstField will remain const. Just set it in the constructor and you will have no problems and your code becomes more readable.

#include <iostream>

class Foo
{
    public:
      constexpr Foo(int i) : someConstField(Ctor(i)){}

      int Ctor(); // to be defined in the implementation

      const int someConstField;
};

int main()
{
    Foo f(12);
    std::cout << f.someConstField;
}

If your allocation function allocates raw memory, you can use placement new to construct an object at that memory location. With this you must remember to call the destructor of the object before freeing the allocation.

Small example using malloc:

class Foo
{
    public:
      constexpr Foo(int i) : someConstField(i){}
    public:
      const int someConstField;
};

int main()
{
    void *raw_memory = std::malloc(sizeof(Foo));
    Foo *foo = new (raw_memory) Foo{3}; // foo->someConstField == 3
    // ...
    foo->~Foo();
    std::free(foo);
}
Related