Extend trampoline class pattern with example for nesting trampoline class hierarchies

This commit is contained in:
Arnim Balzer 2023-03-04 07:25:24 +00:00
parent 3cc7e4258c
commit b3c9615d2d
No known key found for this signature in database
GPG Key ID: 785EF903F0917AB7
3 changed files with 135 additions and 0 deletions

View File

@ -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 AnimalBase = Animal>
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 DogBase = Dog>
class PyDog : public PyAnimal<DogBase> {
public:
using PyAnimal<DogBase>::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 MutantBase = Mutant, class PyMutantBase = MutantBase>
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 ChimeraBase = Chimera>
class PyChimera : public PyMutant<ChimeraBase, PyDog<ChimeraBase>> {
public:
using PyMutant<ChimeraBase, PyDog<ChimeraBase>>::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
===========================

View File

@ -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 Base = ChainBaseA>
class TrampolineA : public Base {
public:
using Base::Base;
int resultA() override { PYBIND11_OVERLOAD(int, Base, resultA, ); }
};
template <class Base = ChainBaseB, class PyBase = Base>
class TrampolineB : public PyBase {
public:
using PyBase::PyBase;
std::string resultB() override { PYBIND11_OVERLOAD(std::string, Base, resultB, ); }
};
template <class Base = Joined>
class TrampolineJoined : public TrampolineB<Base, TrampolineA<Base>> {
public:
using TrampolineB<Base, TrampolineA<Base>>::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_<TN::ChainBaseA, TN::TrampolineA<>>(m, "ChainBaseA")
.def(py::init<>())
.def("resultA", &TN::ChainBaseA::resultA);
py::class_<TN::ChainChildA, TN::ChainBaseA, TN::TrampolineA<TN::ChainChildA>>(m, "ChainChildA")
.def(py::init<>());
py::class_<TN::ChainBaseB, TN::TrampolineB<>>(m, "ChainBaseB")
.def(py::init<>())
.def("resultB", &TN::ChainBaseB::resultB);
py::class_<TN::ChainChildB, TN::ChainBaseB, TN::TrampolineB<TN::ChainChildB>>(m, "ChainChildB")
.def(py::init<>());
py::class_<TN::Joined, TN::ChainBaseA, TN::ChainBaseB, TN::TrampolineJoined<>>(m, "Joined")
.def(py::init<>());
}

View File

@ -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"