Why is constness not enforced for pointers?

It is enforced.

If you try changing the pointer, the compiler will not let you.

The thing that the pointer points to, however, is a different conversation.

Remember, T* const and T const* are not the same thing!

You can protect that by either actually making it A const*, or simply by writing your function in the manner that is appropriate.


The other answers explain the T* const vs T const * which is what is happening. But it is important to understand the implication of this beyond just the mere syntax.

When you have a T* inside a structure the pointer is inside the object (part of the layout of the objet), but the pointed object is physically outside of the structure. That is why a const object with a T* member is not allowed to modify the pointer, but it is allowed to modify the pointed object - because physically the pointed object is outside the enclosing object.

And it is up to the programmer to decide if the pointed object is logically part of the enclosing object (and as such should share constness with the enclosing) or if it is logically an external entity. Examples of former include std::vector, std::string. Examples of the latter include std::span, std::unique_ptr, std::shared_ptr. As you can see both designs are usefull.

The shortcoming of C++ is that it doesn't offer an easy way to express a logical constness as stated above (what you actually expected from your code).

This is known and for this exact purpose there is an experimental class which is not yet standard propagate_const

std::experimental::propagate_const is a const-propagating wrapper for pointers and pointer-like objects. It treats the wrapped pointer as a pointer to const when accessed through a const access path, hence the name.

struct B
{
    A a_;
    std::experimental::propagate_const<A *> pA_;

   void fun()
    {
        pA_->nonConstFun(); // OK
    }
    void fun() const
    {
        // pA_->nonConstFun(); // compilation error
    }
};