Const as a supertype

May 9, 2017, 11:31 p.m.

My professor once told us that, following the common OOP polymorphism rules, one can consider that the const or volatile versions of a type (e.g. in C or C++) to be the supertype of the non-qualified type.

It didn't make a lot of sense to me back then.

Usually when people explain super/subtyping, they think about substitutability. If you can say with confidence that A is a B, then A is a subtype of B. As a common OOP example, a Student is a Person. You cannot say, always, that a Person is a Student. This suggests a subtype relationship in one direction.

So putting my professor's words into that context, he is asserting that an int is always a const int, while a const int is not always an int.

This still made no sense - a red balloon is obviously a balloon, but a balloon is not always a red balloon. Intuitively, the qualifier should result in a subtype.

However, there is a more precise way (short of drawing arcane greek symbols) to think about substitutability than the handwavy "is a" thing. If A is a subtype of B, then it should be the case that B can be "safely used" wherever A is expected. Let's think about it in this context then.

If I have an int, can I safely pretend that it's a const int instead? Actually, yes. That's why you can easily cast an int to a const int, but not vice versa. You can do all the same read-only operations as before. The read-only operations are what a const int possesses; an int simply "inherited" those operations. Any write operations are only "defined" for the subtype int. An int can do everything a const int can, plus more.

What about volatile? That's a bit more subtle because the allowable operations are seemingly the same, but it still makes sense if you consider what it means for "an int to safely pretend to be a volatile int". When you add volatile to a type, the compiler no longer saves the value into a register. Each read or write will always go to memory (or more likely, the L[1-3] caches). It makes sense then that always reading from memory is "safer" than relying on the register value, which may be wrong if another thread or piece of hardware changed the canonical value in the memory address. You can thus say an int can safely pretend to be volatile, but not the other way around.