Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 17 additions & 0 deletions include/nanobind/nb_class.h
Original file line number Diff line number Diff line change
Expand Up @@ -340,6 +340,16 @@ class class_ : public object {
sizeof...(Ts) == !std::is_same_v<Base, T> + !std::is_same_v<Alias, T>,
"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<T*, Base*> ||
detail::is_accessible_static_base_of<Base, T>::value,
"nanobind does not support virtual base classes");

template <typename... Extra>
NB_INLINE class_(handle scope, const char *name, const Extra &... extra) {
detail::type_init_data d;
Expand All @@ -354,6 +364,13 @@ class class_ : public object {
if constexpr (!std::is_same_v<Base, T>) {
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<Alias, T>)
Expand Down
8 changes: 8 additions & 0 deletions include/nanobind/nb_traits.h
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,14 @@ struct detector<std::void_t<Op<Arg>>, Op, Arg>
avoid redundancy when combined with nb::arg(...).none(). */
template <typename T> struct remove_opt_mono { using type = T; };

template <typename Base, typename Derived>
struct is_accessible_static_base_of {
template <typename B = Base, typename D = Derived>
static decltype(static_cast<D*>(std::declval<B*>()), std::true_type()) check(B*);
static std::false_type check(...);
static constexpr bool value = decltype(check((Base*)nullptr))::value;
};

NAMESPACE_END(detail)

template <typename... Args>
Expand Down
19 changes: 19 additions & 0 deletions tests/test_classes.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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_<DerivedBecomesPolymorphic, Struct>(
nb::handle(), "DerivedBecomesPolymorphic");
});
m.def("bind_subclass_with_non_primary_base", []() {
nb::class_<DerivedNonPrimary, Struct>(nb::handle(), "DerivedNonPrimary");
});
static_assert(!nb::detail::is_accessible_static_base_of<Struct, DerivedVirt>::value);
// this fails at compile time:
//nb::class_<DerivedVirt, Struct>(nb::handle(), "DerivedVirt");
}
7 changes: 7 additions & 0 deletions tests/test_classes.py
Original file line number Diff line number Diff line change
Expand Up @@ -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()