It's certainly true that you can't override a method without virtual functions, as one of the comments mentioned. To achieve any kind of runtime polymorphism, you will need some kind of lookup table either implemented yourself or by the compiler. A relatively recent alternative to virtual functions is to use variants instead. Another great blog post compares virtual functions to std::variant. To quote:
virtual functions – dynamic dispatch via vtable vs std::variant – dynamic dispatch via internal union tag (discriminant) and compile-time generated function pointer table
Example code
#include <memory>
#include <string>
#include <variant>
namespace Virtual
{
struct Animal
{
virtual ~Animal() = default;
virtual std::string speak() const = 0;
};
struct Cat : Animal
{
std::string speak() const override { return "meow"; }
};
struct Dog : Animal
{
std::string speak() const override { return "woof"; }
};
} // namespace Virtual
namespace Variant
{
struct Cat
{
std::string speak() const { return "meow"; }
};
struct Dog
{
std::string speak() const { return "woof"; }
};
using Animal = std::variant<Cat, Dog>;
} // namespace Variant
{
using namespace Virtual;
const std::unique_ptr<const Animal> pCat = std::make_unique<Cat>();
const std::unique_ptr<const Animal> pDog = std::make_unique<Dog>();
pCat->speak(); // expect "meow";
pDog->speak(); // expect "woof";
}
{
using namespace Variant;
const auto cat = Animal{Cat{}};
const auto dog = Animal{Dog{}};
std::visit([](const auto& animal) { return animal.speak(); }, cat); // "meow";
std::visit([](const auto& animal) { return animal.speak(); }, dog); // "woof"
}
Also illustrates another quote from the blog post
virtual functions - clients must operate using pointer or reference vs std::variant – clients use value type [i.e., wrapping the underlying object in a variant]
overrideis a specific keyword used only in conjunction withvirtual. But for dynamic(runtime) polymorphism withoutvirtualfunctions check boost.any's implementation. The real thing we need to avoid is not 'virtual' keyword. We try to keep the data local inside CPU cache; when doing so, the set of used types becomes bounded and known, so we sometimes discardvirtualfunctions to save a few more bytes of memory.