class GameObject {
public:
virtual ~GameObject() {}
};
class Player: public GameObject {};
struct IGameController {
virtual GameObject* GetPlayer() = 0;
};
class CameraOnlyController : public IGameController {
public:
GameObject* GetPlayer() override { return nullptr; }
};
class PlayerController : public IGameController {
public:
PlayerController() { player = new Player(); }
~PlayerController() { delete player; }
Player* GetPlayer() override { return player; }
private:
Player* player;
};
int main() {
IGameController* playerController = new PlayerController();
Player* player = playerController->GetPlayer(); // error: a value of type "GameObject *" cannot be used to initialize an entity of type "Player *"
delete playerController;
}
It does compile if I change the controller interface to PlayerController specifically
PlayerController* playerController = new PlayerController();
I understand playerController could point to a CameraOnlyController later on like so
playerController = new CameraOnlyController();
but since it does not when Player* player is initialized, why is this prevented? Is it the compiler trying to enforce type safety, and I am assuming it knows that playerController was assigned to new PlayerController() at the time, but it's wrong to assume this?
IGameController* playerController = new PlayerController();
playerController
is of type IGameController*
.
The C++ compiler type checking does not remember anything else about playerController
. It forgets the fact it was constructed from a pointer to PlayerController
.
Player* player = playerController->GetPlayer();
so here it takes the information it is permitted to know, that playerController
is a IGameController*
, and states there is a type mismatch.
If you want the compiler to know more about the type of playerController
you have to change the type of playerController
yourself. The C++ compiler will not automatically extend the type of playerController
to be everything it could know when determining the meaning of the line of code.
At the same time, the C++ compiler is free to follow the as-if rule, and devirtualize the type of playerController
. But they may only do this as-if they didn't (for example, making your code faster).
Programming languages exist that permit more extensive type deduction of a given variable. C++ is not one of them.
You can do this:
auto* playerController = new PlayerController();
auto* player = playerController->GetPlayer();
delete playerController;
in which case, the exact types of the various variables will be used, or
auto* playerController = new PlayerController();
Player* player = playerController->GetPlayer();
delete playerController;
which validates that player
is of the type you want.