diff --git a/include/nanobind/nb_class.h b/include/nanobind/nb_class.h
index 4120f43f..ec4bc013 100644
--- a/include/nanobind/nb_class.h
+++ b/include/nanobind/nb_class.h
@@ -340,6 +340,16 @@ class class_ : public object {
sizeof...(Ts) == !std::is_same_v + !std::is_same_v,
"nanobind::class_<> was invoked with extra arguments that could not be handled");
+ // Fail on virtual bases -- they need a this-ptr adjustment, but they're
+ // not amenable to the runtime test in the class_ constructor (because
+ // a C-style cast will do reinterpret_cast if static_cast is invalid).
+ // Primary but inaccessible (ambiguous or private) bases will also
+ // fail is_accessible_static_base_of; the !is_convertible option is to
+ // avoid mis-detecting them as virtual bases.
+ static_assert(!std::is_convertible_v ||
+ detail::is_accessible_static_base_of::value,
+ "nanobind does not support virtual base classes");
+
template
NB_INLINE class_(handle scope, const char *name, const Extra &... extra) {
detail::type_init_data d;
@@ -354,6 +364,13 @@ class class_ : public object {
if constexpr (!std::is_same_v) {
d.base = &typeid(Base);
d.flags |= (uint32_t) detail::type_init_flags::has_base;
+
+ if (uintptr_t offset = (uintptr_t) (Base*) (T*) 0x1000 - 0x1000) {
+ detail::raise("nanobind::class_<>: base class %s is at offset "
+ "%td within %s! nanobind does not support "
+ "multiple inheritance", typeid(Base).name(),
+ offset, typeid(T).name());
+ }
}
if constexpr (!std::is_same_v)
diff --git a/include/nanobind/nb_traits.h b/include/nanobind/nb_traits.h
index 66ea2105..83601d3f 100644
--- a/include/nanobind/nb_traits.h
+++ b/include/nanobind/nb_traits.h
@@ -132,6 +132,14 @@ struct detector>, Op, Arg>
avoid redundancy when combined with nb::arg(...).none(). */
template struct remove_opt_mono { using type = T; };
+template
+struct is_accessible_static_base_of {
+ template
+ static decltype(static_cast(std::declval()), std::true_type()) check(B*);
+ static std::false_type check(...);
+ static constexpr bool value = decltype(check((Base*)nullptr))::value;
+};
+
NAMESPACE_END(detail)
template
diff --git a/tests/test_classes.cpp b/tests/test_classes.cpp
index e54e8261..adcd7c19 100644
--- a/tests/test_classes.cpp
+++ b/tests/test_classes.cpp
@@ -477,4 +477,23 @@ NB_MODULE(test_classes_ext, m) {
m.def("polymorphic_factory_2", []() { return (PolymorphicBase *) new AnotherPolymorphicSubclass(); });
m.def("factory", []() { return (Base *) new Subclass(); });
m.def("factory_2", []() { return (Base *) new AnotherSubclass(); });
+
+ // Test detection of unsupported base/derived relationships
+ // (nanobind requires base subobjects to be at offset zero in the
+ // derived class)
+ struct DerivedBecomesPolymorphic : Struct {
+ virtual ~DerivedBecomesPolymorphic() {}
+ };
+ struct DerivedNonPrimary : Big, Struct {};
+ struct DerivedVirt : virtual Struct {};
+ m.def("bind_newly_polymorphic_subclass", []() {
+ nb::class_(
+ nb::handle(), "DerivedBecomesPolymorphic");
+ });
+ m.def("bind_subclass_with_non_primary_base", []() {
+ nb::class_(nb::handle(), "DerivedNonPrimary");
+ });
+ static_assert(!nb::detail::is_accessible_static_base_of::value);
+ // this fails at compile time:
+ //nb::class_(nb::handle(), "DerivedVirt");
}
diff --git a/tests/test_classes.py b/tests/test_classes.py
index 6fc27ca8..d4305e14 100644
--- a/tests/test_classes.py
+++ b/tests/test_classes.py
@@ -631,3 +631,10 @@ def name(self):
assert t.go(d2) == 'Rufus says woof'
finally:
t.Dog.name = old
+
+
+def test35_non_primary():
+ with pytest.raises(RuntimeError, match="not support multiple inheritance"):
+ t.bind_newly_polymorphic_subclass()
+ with pytest.raises(RuntimeError, match="not support multiple inheritance"):
+ t.bind_subclass_with_non_primary_base()