Search code examples
c++interfaceabstract-classcovariance

Why is covariance not allowed with an abstract class?


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?


Solution

  • 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.