The problem in the authors "Animal doctor" example is that the universe of all possible animal types is practically unbounded, or "open".
What you want here are "open multimethods": something Bjarne Stroustrup has wanted in C++ for a long time but have been implemented in library form.
Of course, the thing to remember is that, as soon as you go to an open multiple dispatch arrangement, you will lose much of the static safety guarantees that the type-system affords us. There will always be runtime/dynamic type checking, and a runtime failure case.
If you don't want an open system, then some combination of templates for static polymorphism and strategic type-erasure will get the job done. e.g. use boost::variant and multivisitation , and the compiler will instantiate all O(n^2) methods for you.
 http://www.stroustrup.com/multimethods.pdf (2007)
Outcome attack( RangeBaseUnit attackingUnit, MeleeBasedUnit defendingUnit )
Outcome attack( MeleeBasedUnit attackingUnit, RangeBasedUnit defendingUnit )
The reason why C++ doesn't have them is because they are relatively harder and more costly to implement than return types (consider that multiple inheritance means that pointer-to-derived often needs to be offset to be used as pointer-to-base).
type theory <- domain theory <- category theory
Interestingly enough, std::function correctly supports contravariant parameters; it also correctly handles co/contra variance correctly via smart pointers (as long as the smart pointers support it themselves of course).