Allow specifying custom base classes

- Useful for object hierarchies that don't use C++ inheritance (such as GObject)
This commit is contained in:
Dustin Spicuzza 2022-10-17 14:22:49 -04:00
parent 964c49978f
commit d6df11602b
4 changed files with 99 additions and 0 deletions

View File

@ -65,6 +65,31 @@ struct base {
base() = default;
};
/** \rst
Annotation indicating that a class should appear to python to derive from
another given type. This is useful for wrapping type systems that don't
utilize standard C++ inheritance.
You must provide a caster function that casts from the derived type to
the base type. As an example, standard C++ inheritance would do this:
.. code-block:: c++
py::class_<Derived> cls(m, "Derived", py::custom_base<Base>([](void *o) {
return static_cast<Base*>(reinterpret_cast<Derived*>(o));
}));
.. note:: This is an advanced feature. If you use this, you likely need
to implement polymorphic_type_hook for your type hierarchy.
\endrst */
template <typename T> struct custom_base {
using caster = void *(*)(void *);
explicit custom_base(const caster &f) : fn(f) {}
caster fn;
};
/// Keep patient alive while nurse lives
template <size_t Nurse, size_t Patient>
struct keep_alive {};
@ -551,6 +576,17 @@ struct process_attribute<base<T>> : process_attribute_default<base<T>> {
static void init(const base<T> &, type_record *r) { r->add_base(typeid(T), nullptr); }
};
/// Process a custom base attribute
template <typename T>
struct process_attribute<custom_base<T>> : process_attribute_default<custom_base<T>> {
static void init(const custom_base<T> &b, type_record *r)
{
r->add_base(typeid(T), b.fn);
// TODO: rename this to 'nonsimple'?
r->multiple_inheritance = true;
}
};
/// Process a multiple inheritance attribute
template <>
struct process_attribute<multiple_inheritance> : process_attribute_default<multiple_inheritance> {

View File

@ -125,6 +125,7 @@ set(PYBIND11_TEST_FILES
test_const_name
test_constants_and_functions
test_copy_move
test_custom_base
test_custom_type_casters
test_custom_type_setup
test_docstring_options

View File

@ -0,0 +1,39 @@
/*
tests/test_custom_base.cpp -- test custom type hierarchy support
All rights reserved. Use of this source code is governed by a
BSD-style license that can be found in the LICENSE file.
*/
#include "pybind11_tests.h"
namespace {
struct Base {
int i = 5;
};
struct Derived {
int j = 6;
// just to prove the base can be anywhere
Base base;
};
} // namespace
TEST_SUBMODULE(custom_base, m) {
py::class_<Base>(m, "Base").def_readwrite("i", &Base::i);
py::class_<Derived>(m, "Derived", py::custom_base<Base>([](void *o) -> void * {
return &reinterpret_cast<Derived *>(o)->base;
})).def_readwrite("j", &Derived::j);
m.def("create_derived", []() { return new Derived; });
m.def("create_base", []() { return new Base; });
m.def("base_i", [](Base *b) { return b->i; });
m.def("derived_j", [](Derived *d) { return d->j; });
};

23
tests/test_custom_base.py Normal file
View File

@ -0,0 +1,23 @@
from pybind11_tests import custom_base as m
def test_cb_base():
b = m.create_base()
assert isinstance(b, m.Base)
assert b.i == 5
assert m.base_i(b) == 5
def test_cb_derived():
d = m.create_derived()
assert isinstance(d, m.Derived)
assert isinstance(d, m.Base)
assert d.i == 5
assert d.j == 6
assert m.base_i(d) == 5
assert m.derived_j(d) == 6