This commit is contained in:
Arnim Balzer 2024-09-20 23:43:45 -07:00 committed by GitHub
commit 2bf5f5550a
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 249 additions and 0 deletions

View File

@ -866,6 +866,8 @@ which should look as follows:
.. [#f5] https://docs.python.org/3/library/copy.html .. [#f5] https://docs.python.org/3/library/copy.html
.. _multiple_inheritance:
Multiple Inheritance Multiple Inheritance
==================== ====================
@ -902,6 +904,121 @@ are listed.
.. _module_local: .. _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.
If the base classes contain pure virtual methods, another pattern can be applied to reduce the amount of
trampoline code that needs writing. The cost is an additional ``std::same`` call for each pure-virtual
method using the macro ``PYBIND11_OVERRIDE_TEMPLATE``.
.. code-block:: cpp
class Animal {
public:
virtual ~Animal() { }
virtual std::string go(int n_times) = 0;
};
class Dog : public Animal {
public:
std::string go(int n_times) override;
};
template <class AnimalBase = Animal, class PureVirtualBase = Animal>
class PyAnimal : public AnimalBase {
public:
using AnimalBase::AnimalBase; // Inherit constructors
std::string go(int n_times) override { PYBIND11_OVERRIDE_TEMPLATE(PureVirtualBase, std::string, AnimalBase, go, n_times); }
};
using PyDog = PyAnimal<Dog>
class Mutant {
public:
virtual ~Mutant() { }
virtual void transform() = 0;
};
class XMen : public Mutant{
public:
virtual ~Mutant() { }
void transform() override;
};
template <class MutantBase = Mutant, class PyMutantBase = MutantBase, class PureVirtualBase = Mutant>
class PyMutant : public PyMutantBase {
public:
using PyMutantBase::PyMutantBase; // Inherit constructors
void transform() override { PYBIND11_OVERRIDE_TEMPLATE(PureVirtualBase, void, MutantBase, transform, ); }
};
using PyXMen = PyMutant<XMen>
class Chimera : public Dog, public Mutant {
public:
virtual ~Chimera() { }
};
template <class ChimeraBase = Chimera, class PyChimeraBase = ChimeraBase>
class PyChimera : public PyMutant<ChimeraBase, PyAnimal<ChimeraBase, PyChimeraBase>> {
public:
using PyMutant<ChimeraBase, PyAnimal<ChimeraBase, PyChimeraBase>>::PyMutant; // Inherit constructors
};
The first parameter of the :c:macro:`PYBIND11_OVERRIDE_TEMPLATE` is the base class containing
the pure virtual method. Together with the cname parameter, an ``std::same`` call is used to
invoke either :c:macro:`PYBIND11_OVERRIDE_PURE` or :c:macro:`PYBIND11_OVERRIDE`. A corresponding
:c:macro:`PYBIND11_OVERRIDE_TEMPLATE_NAME` implementation is also available. The template parameter
``PureVirtualBase`` can be used in case the pure virtual methods are not implemented in a child class.
Module-local class bindings Module-local class bindings
=========================== ===========================

View File

@ -23,6 +23,7 @@
#include <memory> #include <memory>
#include <new> #include <new>
#include <string> #include <string>
#include <type_traits>
#include <utility> #include <utility>
#include <vector> #include <vector>
@ -2963,6 +2964,51 @@ function get_override(const T *this_ptr, const char *name) {
PYBIND11_OVERRIDE_PURE_NAME( \ PYBIND11_OVERRIDE_PURE_NAME( \
PYBIND11_TYPE(ret_type), PYBIND11_TYPE(cname), #fn, fn, __VA_ARGS__) PYBIND11_TYPE(ret_type), PYBIND11_TYPE(cname), #fn, fn, __VA_ARGS__)
/** \rst
Macro to wrap :c:macro:`PYBIND11_OVERRIDE_PURE_NAME` and :c:macro:`PYBIND11_OVERRIDE_PURE`
depending on the base class and cname parameter provided.
See :ref:`_multiple_inheritance` for more information.
.. code-block:: cpp
template<class AnimalBase = Animal>
class PyAnimal : public AnimalBase {
public:
// Inherit the constructors
using AnimalBase::AnimalBase;
// Trampoline (need one for each virtual function)
std::string go(int n_times) override {
PYBIND11_OVERRIDE_TEMPLATE(
Animal, // The base class containing the purely virtual implementation
std::string, // Return type (ret_type)
Dog, // Parent class (cname)
"_go", // Name of method in Python (name)
go, // Name of function in C++ (must match Python name) (fn)
n_times // Argument(s) (...)
);
}
};
\endrst */
#define PYBIND11_OVERRIDE_TEMPLATE_NAME(base, ret_type, cname, name, fn, ...) \
if (std::is_same<base, cname>::value) { \
PYBIND11_OVERRIDE_PURE_NAME( \
PYBIND11_TYPE(ret_type), PYBIND11_TYPE(cname), name, fn, __VA_ARGS__); \
} else { \
PYBIND11_OVERRIDE_NAME( \
PYBIND11_TYPE(ret_type), PYBIND11_TYPE(cname), name, fn, __VA_ARGS__); \
}
/** \rst
Macro to wrap :c:macro:`PYBIND11_OVERRIDE_NAME` and :c:macro:`PYBIND11_OVERRIDE`
depending on the base class and cname parameter provided.
Uses :c:macro:`PYBIND11_OVERRIDE_TEMPLATE_NAME` under the hood.
See :ref:`_multiple_inheritance` for more information.
\endrst */
#define PYBIND11_OVERRIDE_TEMPLATE(base, ret_type, cname, fn, ...) \
PYBIND11_OVERRIDE_TEMPLATE_NAME( \
PYBIND11_TYPE(base), PYBIND11_TYPE(ret_type), PYBIND11_TYPE(cname), #fn, fn, __VA_ARGS__)
// Deprecated versions // Deprecated versions
PYBIND11_DEPRECATED("get_type_overload has been deprecated") PYBIND11_DEPRECATED("get_type_overload has been deprecated")

View File

@ -80,6 +80,61 @@ struct I801D : I801C {}; // Indirect MI
} // namespace } // namespace
namespace TrampolineNesting {
class ChainBaseA {
public:
ChainBaseA() = default;
ChainBaseA(const ChainBaseA &) = default;
ChainBaseA(ChainBaseA &&) = default;
virtual ~ChainBaseA() = default;
virtual int resultA() = 0;
};
class ChainChildA : public ChainBaseA {
public:
using ChainBaseA::ChainBaseA;
int resultA() override { return 1; }
};
class ChainBaseB {
public:
ChainBaseB() = default;
ChainBaseB(const ChainBaseB &) = default;
ChainBaseB(ChainBaseB &&) = default;
virtual ~ChainBaseB() = default;
virtual std::string resultB() = 0;
};
class ChainChildB : public ChainBaseB {
public:
using ChainBaseB::ChainBaseB;
std::string resultB() override { return "A"; }
};
class Joined : public ChainChildA, public ChainChildB {
public:
Joined() = default;
Joined(const Joined &) = default;
Joined(Joined &&) = default;
};
template <class Base = ChainBaseA, typename PureVirtualBase = ChainBaseA>
class TrampolineA : public Base {
public:
using Base::Base;
int resultA() override { PYBIND11_OVERRIDE_TEMPLATE(PureVirtualBase, int, Base, resultA, ) }
};
template <class Base = ChainBaseB, class PyBase = Base, typename PureVirtualBase = ChainBaseB>
class TrampolineB : public PyBase {
public:
using PyBase::PyBase;
std::string resultB() override {
PYBIND11_OVERRIDE_TEMPLATE(PureVirtualBase, std::string, Base, resultB, )
}
};
template <class Base = Joined, class PyBase = Base>
class TrampolineJoined : public TrampolineB<Base, TrampolineA<Base, PyBase>> {
public:
using TrampolineB<Base, TrampolineA<Base, PyBase>>::TrampolineB;
};
} // namespace TrampolineNesting
TEST_SUBMODULE(multiple_inheritance, m) { TEST_SUBMODULE(multiple_inheritance, m) {
// Please do not interleave `struct` and `class` definitions with bindings code, // Please do not interleave `struct` and `class` definitions with bindings code,
// but implement `struct`s and `class`es in the anonymous namespace above. // but implement `struct`s and `class`es in the anonymous namespace above.
@ -338,4 +393,24 @@ TEST_SUBMODULE(multiple_inheritance, m) {
.def("get_f_e", &MVF::get_f_e) .def("get_f_e", &MVF::get_f_e)
.def("get_f_f", &MVF::get_f_f) .def("get_f_f", &MVF::get_f_f)
.def_readwrite("f", &MVF::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::ChainChildA, TN::ChainChildB, TN::TrampolineJoined<>>(m, "Joined")
.def(py::init<>());
} }
// Needed for MSVC linker
namespace TrampolineNesting {
int ChainBaseA::resultA() { return 0; }
std::string ChainBaseB::resultB() { return ""; }
} // namespace TrampolineNesting

View File

@ -493,3 +493,14 @@ def test_python_inherit_from_mi():
assert o.g == 7 assert o.g == 7
assert o.get_g_g() == 7 assert o.get_g_g() == 7
def test_trampoline_nesting():
with pytest.raises(RuntimeError):
m.ChainBaseA().resultA()
assert m.ChainChildA().resultA() == 1
with pytest.raises(RuntimeError):
m.ChainBaseB().resultB()
assert m.ChainChildB().resultB() == "A"
assert m.Joined().resultA() == 1
assert m.Joined().resultB() == "A"