-
I use multiple inheritance on the C++ side to cut down on code duplication. Maybe there is a better way, but that is not related to my question. I know from the documentation that Nanobind does not support multiple inheritance. Therefore, I tried to set up the class inheritance in a way that hides the fact that I have multiple inheritance. However, this leads to uninitialized memory errors. This is best explained with an example (the full code can be found here). C++ Class Structure:class A {
public:
A() = default;
virtual ~A() = default;
virtual int foo() const = 0;
};
class B : virtual public A {
public:
B(int _a, int _b) : a(_a), b(_b) { }
int foo() const override { return a * b; }
int a, b;
};
class C : virtual public A {
public:
C() = default;
virtual int bar() const { return x * 42; }
double x = 1.0;
};
class D : public B, public C {
public:
D(int _a, int _b) : B(_a, _b) { }
}; A couple of things to note:
BindingsNB_MODULE(testbed, m)
{
m.doc() = "Minimal working example for Nanobind";
nb::class_<A>(m, "A").def("foo", &A::foo);
nb::class_<B, A>(m, "B")
.def(nb::init<int, int>())
.def_rw("a", &B::a)
.def_rw("b", &B::b);
nb::class_<C, A>(m, "C").def("bar", &C::bar).def_rw("x", &C::x);
// NOTE: Nanobind does not support multiple inheritance, so this Pybind11
// syntax does not work anymore.
// nb::class_<D, B, C>(m, "D").def(nb::init<int, int>());
// Define D as if it were only a child of C and bind the inherited methods of
// B directly.
nb::class_<D, C>(m, "D")
.def(nb::init<int, int>())
// Inherited from B
.def("foo", &D::foo)
.def_rw("a", &D::a)
.def_rw("b", &D::b);
} IssueThe issue is that when I construct an object of class from testbed import B, D
b = B(1, 2)
assert b.a == 1
assert b.b == 2
assert b.foo() == 2
d = D(3, 4)
assert d.a == 3
assert d.b == 4
assert d.foo() == 12
assert d.x == 1 # Fails with d.x = 8.4879831653e-314
assert d.bar() == 42 # Produces a segfault WorkaroundI have found one workaround to this issue, which is to explicitly bind the inherited members of nb::class_<D, C>(m, "D")
.def(nb::init<int, int>())
// Inherited from B
.def("foo", &D::foo)
.def_rw("a", &D::a)
.def_rw("b", &D::b)
// Inherited from C
// NOTE: Normally, you would not need to redefine `bar` and `x` here,
// but since `D` is not a direct child of `B`, we need to explicitly
// rebind them to ensure they are accessible.
.def("bar", &D::bar)
.def_rw("x", &D::x); This results in the correct behavior— Can you help me understand why this problem might arise without the explicit binding of inherited members? Is this already documented somewhere and I missed it? |
Beta Was this translation helpful? Give feedback.
Replies: 1 comment 3 replies
-
Quick follow-up: I tried it without the diamond inheritance (commented out everything related to class |
Beta Was this translation helpful? Give feedback.
The problem when you use these kinds of patterns is that the base pointer of the object must be adjusted when casting. Nanobind does not do this, so this will never work.