dynamic_cast and poor man's multiple dispatch in C++

15 Aug 2020 - John Z. Li

C++ only supports single dispatch. In case one really needs multiple dispatch, he can emulate it using dynamic_cast. Suppose he needs the following multiple dispatched function:

    pets_fight(Pet*, Pet*)

The function is required to be dispatched to a cat-fight-cat, cat-fight-dog, or dog-fight-dog version according to underlying types of the two parameters. Also notice that a pet should not be able to fight itself. This is can be done using dynamic_cast as in the following code:

    #include <iostream>
    class Pet {
    public:
      virtual void fight(Pet &other) = 0;
      virtual ~Pet(){};
    };

    class Cat : public Pet {
    public:
      virtual void fight(Pet &other) override;
      virtual ~Cat() override;
    };

    class Dog : public Pet {
    public:
      virtual void fight(Pet &other) override {
        if (dynamic_cast<Dog *>(&other) == this)
          return;
        if (dynamic_cast<Dog *>(&other)) {
          std::cout << "A dog fights another dog." << std::endl;
        } else if (dynamic_cast<Cat *>(&other)) {
          std::cout << "A dog fights a cat" << std::endl;
        } else {
          std::cout << "undefined pets type" << std::endl;
        }
      }

    public:
      virtual ~Dog() override{};
    };

    void Cat::fight(Pet &other) {
      if (dynamic_cast<Cat *>(&other) == this)
        return;
      if (dynamic_cast<Dog *>(&other)) {
        std::cout << "A cat fights a dog." << std::endl;
      } else if (dynamic_cast<Cat *>(&other)) {
        std::cout << "A cat fights another cat. " << std::endl;
      } else {
        std::cout << "undefined pets type." << std::endl;
      }
    }
    Cat::~Cat(){};

    void pets_fight(Pet *pet1_ptr, Pet *pet2_ptr) { pet1_ptr->fight(*pet2_ptr); }

	// test with the following
    int main() {
      Pet *pet1_ptr = new Cat;
      Pet *pet2_ptr = new Dog;
      Pet *pet3_ptr = new Cat;
      Pet *pet4_ptr = new Dog;
      pets_fight(pet1_ptr, pet1_ptr); // fight self is a no-op;
      pets_fight(pet1_ptr, pet2_ptr); // cat fight dog;
      pets_fight(pet3_ptr, pet1_ptr); // cat fight cat;
      pets_fight(pet4_ptr, pet3_ptr); // dog fight dog
      delete pet1_ptr;
      delete pet2_ptr;
      delete pet3_ptr;
      delete pet4_ptr;
      return 0;
    }

The code works because when using dynamic_cast to cast a pointer to a base class to a pointer to a derived class, if the pointed object is actually of the type of the derived class, it will return a valid pointer to an object of that type; otherwise, it will return a nullptr.

Notice that the same does not apply to references. If a reference of a base class is cast to a reference of a derived class, an exception will be thrown if the conversion can not be performed.