diff --git a/docs/advanced/classes.rst b/docs/advanced/classes.rst index 01a490b72..abde2c64f 100644 --- a/docs/advanced/classes.rst +++ b/docs/advanced/classes.rst @@ -903,6 +903,64 @@ are listed. .. _module_local: +For more complex multiple-inheritance class architectures with virtual methods, it might be necessary to combine +two different Trampoline class hierarchies. A templating trick can be used in this case to interleaf another +trampoline class (-hierarchy): + +.. code-block:: cpp + + class Animal { + public: + virtual ~Animal() { } + virtual std::string go(int n_times) = 0; + }; + template + class PyAnimal : public AnimalBase { + public: + using AnimalBase::AnimalBase; // Inherit constructors + std::string go(int n_times) override { PYBIND11_OVERRIDE_PURE(std::string, AnimalBase, go, n_times); } + }; + + class Dog : public Animal { + public: + std::string go(int n_times) override; + }; + template + class PyDog : public PyAnimal { + public: + using PyAnimal::PyAnimal; // Inherit constructors + std::string go(int n_times) override { PYBIND11_OVERRIDE(std::string, DogBase, go, n_times); } + }; + + class Mutant { + public: + virtual ~Mutant() { } + virtual void transform(); + }; + template + class PyMutant : public PyMutantBase { + public: + using PyMutantBase::PyMutantBase; // Inherit constructors + void transform() override { PYBIND11_OVERRIDE_PURE(void, MutantBase, transform, ); } + }; + + class Chimera : public Dog, public Mutant { + public: + virtual ~Chimera() { } + }; + template + class PyChimera : public PyMutant> { + public: + using PyMutant>::PyMutant; // Inherit constructors + }; + +The class ``Chimera`` inherits from both ``Dog`` and ``Mutant`` both of which feature virtual methods and +trampoline classes for binding. However, the ``Mutant`` trampoline class uses a second template parameter +``PyMutantBase`` so it can be injected into the single inheritance structure required by a trampoline class. +In effect, this mechanism enforces that the actual class the trampolines are using is only inherited from once. +Since the trampolines only need to add their respective trampoline function registrations, the order of the +inheritance of the various trampoline classes does not matter. + Module-local class bindings =========================== diff --git a/tests/test_multiple_inheritance.cpp b/tests/test_multiple_inheritance.cpp index 5916ae901..c70bb529e 100644 --- a/tests/test_multiple_inheritance.cpp +++ b/tests/test_multiple_inheritance.cpp @@ -80,6 +80,59 @@ struct I801D : I801C {}; // Indirect MI } // namespace +namespace TrampolineNesting { +class ChainBaseA { +public: + ChainBaseA() = default; + ChainBaseA(const ChainBaseA &) = default; + ChainBaseA(ChainBaseA &&) = default; + virtual ~ChainBaseA() = default; + virtual int resultA() { return 1; } +}; +class ChainChildA : public ChainBaseA { +public: + using ChainBaseA::ChainBaseA; + int resultA() override { return 2; } +}; +class ChainBaseB { +public: + ChainBaseB() = default; + ChainBaseB(const ChainBaseB &) = default; + ChainBaseB(ChainBaseB &&) = default; + virtual ~ChainBaseB() = default; + virtual std::string resultB() { return "A"; } +}; +class ChainChildB : public ChainBaseB { +public: + using ChainBaseB::ChainBaseB; + std::string resultB() override { return "B"; } +}; +class Joined : public ChainChildA, public ChainChildB { +public: + Joined() = default; + Joined(const Joined &) = default; + Joined(Joined &&) = default; +}; + +template +class TrampolineA : public Base { +public: + using Base::Base; + int resultA() override { PYBIND11_OVERLOAD(int, Base, resultA, ); } +}; +template +class TrampolineB : public PyBase { +public: + using PyBase::PyBase; + std::string resultB() override { PYBIND11_OVERLOAD(std::string, Base, resultB, ); } +}; +template +class TrampolineJoined : public TrampolineB> { +public: + using TrampolineB>::TrampolineB; +}; +} // namespace TrampolineNesting + TEST_SUBMODULE(multiple_inheritance, m) { // Please do not interleave `struct` and `class` definitions with bindings code, // but implement `struct`s and `class`es in the anonymous namespace above. @@ -338,4 +391,19 @@ TEST_SUBMODULE(multiple_inheritance, m) { .def("get_f_e", &MVF::get_f_e) .def("get_f_f", &MVF::get_f_f) .def_readwrite("f", &MVF::f); + + namespace TN = TrampolineNesting; + + py::class_>(m, "ChainBaseA") + .def(py::init<>()) + .def("resultA", &TN::ChainBaseA::resultA); + py::class_>(m, "ChainChildA") + .def(py::init<>()); + py::class_>(m, "ChainBaseB") + .def(py::init<>()) + .def("resultB", &TN::ChainBaseB::resultB); + py::class_>(m, "ChainChildB") + .def(py::init<>()); + py::class_>(m, "Joined") + .def(py::init<>()); } diff --git a/tests/test_multiple_inheritance.py b/tests/test_multiple_inheritance.py index 3a1d88d71..e6acc2939 100644 --- a/tests/test_multiple_inheritance.py +++ b/tests/test_multiple_inheritance.py @@ -491,3 +491,12 @@ def test_python_inherit_from_mi(): assert o.g == 7 assert o.get_g_g() == 7 + + +def test_trampoline_nesting(): + assert m.ChainBaseA().resultA() == 1 + assert m.ChainChildA().resultA() == 2 + assert m.ChainBaseB().resultB() == "A" + assert m.ChainChildB().resultB() == "B" + assert m.Joined().resultA() == 2 + assert m.Joined().resultB() == "B"