I’ll speak to C++, where this difference is most relevant.
As you correctly note, immutable means that an object cannot change at all after its creation. This creation can of course occur at runtime, i.e., a const
object is not necessarily a compile-time constant. In C++, an object is immutable if (1) and either (2) or (3) are met:
It has no members declared mutable
that are mutated by const
member functions
It is declared const
const
member functions do not use const_cast
to remove const
qualification in order to mutate any members
However, you could also consider access modifiers: if an operation internally mutates an instance, but has no effect on the state of the instance observable through its public interface, then the object is “logically immutable”.
So C++ provides the tools necessary to create immutable objects, but like most everything in C++, the tools are only minimally sufficient, and require diligence to actually use. The state of an instance is not necessarily confined to the instance member variables—because C++ does not provide a way to enforce referential transparency, it can include global or class state as well.
const
also has another function in C++: to qualify references and pointers. A const
reference may refer to a non-const
object. It is legal (though not generally necessary or advisable) to use const_cast
to mutate an object through a const
reference, if and only if that object is declared non-const
:
int i = 4; // Non-const object.
const int* p = &i; // Pointer to a constant integer.
*const_cast<int*>(p) = 5; // Legal.
And of course it’s undefined behaviour to mutate a const
object:
const int i = 4; // const object.
const int* p = &i; // Pointer to a const integer.
*const_cast<int*>(p) = 5; // Illegal.