When exposing C++ classes with virtual methods to Python, you must use a trampoline class to allow Python to override those methods.
Key Requirements:
- Override every virtual method: For every method you want Python to be able to override, the trampoline class must provide an override using
PYBIND11_OVERRIDE or PYBIND11_OVERRIDE_PURE. - Inheritance chain: If you have a hierarchy (e.g.,
Animal -> Dog -> Husky), every level in the hierarchy that is registered with pybind11 requires its own trampoline class, even if that specific level doesn't introduce new virtual methods. This is because the trampoline is needed to bridge the virtual dispatch for the entire chain. - Trailing commas: When using
PYBIND11_OVERRIDE for functions with no arguments, you must include a trailing comma (e.g., PYBIND11_OVERRIDE(type, Class, name, )) to ensure portable implementation.
Optimization: Template Trampolines
To avoid duplicating override logic across multiple trampoline classes in a deep hierarchy, you can use template trampoline classes. This allows you to define the override logic once in a base template and reuse it for derived classes.
// Example of a template trampoline approach
template <class AnimalBase = Animal>
class PyAnimal : public AnimalBase, public py::trampoline_self_life_support {
public:
using AnimalBase::AnimalBase;
std::string go(int n_times) override { PYBIND11_OVERRIDE_PURE(std::string, AnimalBase, go, n_times); }
std::string name() override { PYBIND11_OVERRIDE(std::string, AnimalBase, name, ); }
};
template <class DogBase = Dog>
class PyDog : public PyAnimal<DogBase>, public py::trampoline_self_life_support {
public:
using PyAnimal<DogBase>::PyAnimal;
std::string go(int n_times) override { PYBIND11_OVERRIDE(std::string, DogBase, go, n_times); }
std::string bark() override { PYBIND11_OVERRIDE(std::string, DogBase, bark, ); }
};
// Registration
py::class_<Animal, PyAnimal<>, py::smart_holder> animal(m, "Animal");
py::class_<Dog, Animal, PyDog<>, py::smart_holder> dog(m, "Dog");