diff --git a/README.md b/README.md index efabbfff6..1bf80d055 100644 --- a/README.md +++ b/README.md @@ -45,10 +45,12 @@ pybind11 can map the following core C++ features to Python - Arbitrary exception types - Enumerations - Callbacks +- Iterators and ranges - Custom operators +- Single and multiple inheritance - STL data structures - Iterators and ranges -- Smart pointers with reference counting like `std::shared_ptr` +- Smart pointers with reference counting like ``std::shared_ptr`` - Internal references with correct reference counting - C++ classes with virtual (and pure virtual) methods can be extended in Python diff --git a/docs/advanced.rst b/docs/advanced.rst index 07901e852..d905b30d0 100644 --- a/docs/advanced.rst +++ b/docs/advanced.rst @@ -217,6 +217,8 @@ The following interactive session shows how to call them from Python. that demonstrates how to work with callbacks and anonymous functions in more detail. +.. _overriding_virtuals: + Overriding virtual functions in Python ====================================== @@ -2151,3 +2153,45 @@ type is explicitly allowed. }; } }; + +Multiple Inheritance +==================== + +pybind11 can create bindings for types that derive from multiple base types +(aka. *multiple inheritance*). To do so, specify all bases in the template +arguments of the ``class_`` declaration: + +.. code-block:: cpp + + py::class_(m, "MyType") + ... + +The base types can be specified in arbitrary order, and they can even be +interspersed with alias types and holder types (discussed earlier in this +document)---pybind11 will automatically find out which is which. The only +requirement is that the first template argument is the type to be declared. + +There are two caveats regarding the implementation of this feature: + +1. When only one base type is specified for a C++ type that actually has + multiple bases, pybind11 will assume that it does not participate in + multiple inheritance, which can lead to undefined behavior. In such cases, + add the tag ``multiple_inheritance``: + + .. code-block:: cpp + + py::class_(m, "MyType", py::multiple_inheritance()); + + The tag is redundant and does not need to be specified when multiple base + types are listed. + +2. As was previously discussed in the section on :ref:`overriding_virtuals`, it + is easy to create Python types that derive from C++ classes. It is even + possible to make use of multiple inheritance to declare a Python class which + has e.g. a C++ and a Python class as bases. However, any attempt to create a + type that has *two or more* C++ classes in its hierarchy of base types will + fail with a fatal error message: ``TypeError: multiple bases have instance + lay-out conflict``. Core Python types that are implemented in C (e.g. + ``dict``, ``list``, ``Exception``, etc.) also fall under this combination + and cannot be combined with C++ types bound using pybind11 via multiple + inheritance. diff --git a/docs/intro.rst b/docs/intro.rst index 40dcf3593..25597965f 100644 --- a/docs/intro.rst +++ b/docs/intro.rst @@ -35,12 +35,14 @@ The following core C++ features can be mapped to Python - Instance methods and static methods - Overloaded functions - Instance attributes and static attributes -- Exceptions +- Arbitrary exception types - Enumerations -- Iterators and ranges - Callbacks +- Iterators and ranges - Custom operators +- Single and multiple inheritance - STL data structures +- Iterators and ranges - Smart pointers with reference counting like ``std::shared_ptr`` - Internal references with correct reference counting - C++ classes with virtual (and pure virtual) methods can be extended in Python diff --git a/docs/limitations.rst b/docs/limitations.rst index c6100d167..a1a4f1aff 100644 --- a/docs/limitations.rst +++ b/docs/limitations.rst @@ -9,15 +9,12 @@ certain limitations: values. This means that some additional care is needed to avoid bugs that would be caught by the type checker in a traditional C++ program. -- Multiple inheritance relationships on the C++ side cannot be mapped to - Python. - - The NumPy interface ``pybind11::array`` greatly simplifies accessing numerical data from C++ (and vice versa), but it's not a full-blown array class like ``Eigen::Array`` or ``boost.multi_array``. -All of these features could be implemented but would lead to a significant -increase in complexity. I've decided to draw the line here to keep this project -simple and compact. Users who absolutely require these features are encouraged -to fork pybind11. +These features could be implemented but would lead to a significant increase in +complexity. I've decided to draw the line here to keep this project simple and +compact. Users who absolutely require these features are encouraged to fork +pybind11. diff --git a/include/pybind11/attr.h b/include/pybind11/attr.h index 070d9d9d8..4b4cfc0fd 100644 --- a/include/pybind11/attr.h +++ b/include/pybind11/attr.h @@ -41,6 +41,9 @@ template struct base { /// Keep patient alive while nurse lives template struct keep_alive { }; +/// Annotation indicating that a class is involved in a multiple inheritance relationship +struct multiple_inheritance { }; + NAMESPACE_BEGIN(detail) /* Forward declarations */ enum op_id : int; @@ -127,6 +130,8 @@ struct function_record { /// Special data structure which (temporarily) holds metadata about a bound class struct type_record { + PYBIND11_NOINLINE type_record() { } + /// Handle to the parent scope handle scope; @@ -148,21 +153,36 @@ struct type_record { /// Function pointer to class_<..>::dealloc void (*dealloc)(PyObject *) = nullptr; - // Pointer to RTTI type_info data structure of base class - const std::type_info *base_type = nullptr; - - /// OR: Python handle to base class - handle base_handle; + /// List of base classes of the newly created type + list bases; /// Optional docstring const char *doc = nullptr; + + /// Multiple inheritance marker + bool multiple_inheritance = false; + + PYBIND11_NOINLINE void add_base(const std::type_info *base, void *(*caster)(void *)) { + auto base_info = detail::get_type_info(*base, false); + if (!base_info) { + std::string tname(base->name()); + detail::clean_type_id(tname); + pybind11_fail("generic_type: type \"" + std::string(name) + + "\" referenced unknown base type \"" + tname + "\""); + } + + bases.append((PyObject *) base_info->type); + + if (caster) + base_info->implicit_casts.push_back(std::make_pair(type, caster)); + } }; /** * Partial template specializations to process custom attributes provided to * cpp_function_ and class_. These are either used to initialize the respective - * fields in the type_record and function_record data structures or executed - * at runtime to deal with custom call policies (e.g. keep_alive). + * fields in the type_record and function_record data structures or executed at + * runtime to deal with custom call policies (e.g. keep_alive). */ template struct process_attribute; @@ -257,13 +277,19 @@ template <> struct process_attribute : process_attribute_default { /// Process a parent class attribute template struct process_attribute::value>::type> : process_attribute_default { - static void init(const handle &h, type_record *r) { r->base_handle = h; } + static void init(const handle &h, type_record *r) { r->bases.append(h); } }; -/// Process a parent class attribute +/// Process a parent class attribute (deprecated, does not support multiple inheritance) template struct process_attribute> : process_attribute_default> { - static void init(const base &, type_record *r) { r->base_type = &typeid(T); } + static void init(const base &, type_record *r) { r->add_base(&typeid(T), nullptr); } +}; + +/// Process a multiple inheritance attribute +template <> +struct process_attribute : process_attribute_default { + static void init(const multiple_inheritance &, type_record *r) { r->multiple_inheritance = true; } }; /*** diff --git a/include/pybind11/cast.h b/include/pybind11/cast.h index b885298a7..00d19ac3d 100644 --- a/include/pybind11/cast.h +++ b/include/pybind11/cast.h @@ -15,7 +15,6 @@ #include "descr.h" #include #include -#include NAMESPACE_BEGIN(pybind11) NAMESPACE_BEGIN(detail) @@ -25,9 +24,13 @@ struct type_info { PyTypeObject *type; size_t type_size; void (*init_holder)(PyObject *, const void *); - std::vector implicit_conversions; + std::vector implicit_conversions; + std::vector> implicit_casts; buffer_info *(*get_buffer)(PyObject *, void *) = nullptr; void *get_buffer_data = nullptr; + /** A simple type never occurs as a (direct or indirect) parent + * of a class that makes use of multiple inheritance */ + bool simple_type = true; }; PYBIND11_NOINLINE inline internals &get_internals() { @@ -72,32 +75,34 @@ PYBIND11_NOINLINE inline internals &get_internals() { return *internals_ptr; } -PYBIND11_NOINLINE inline detail::type_info* get_type_info(PyTypeObject *type, bool throw_if_missing = true) { +PYBIND11_NOINLINE inline detail::type_info* get_type_info(PyTypeObject *type) { auto const &type_dict = get_internals().registered_types_py; do { auto it = type_dict.find(type); if (it != type_dict.end()) return (detail::type_info *) it->second; type = type->tp_base; - if (!type) { - if (throw_if_missing) - pybind11_fail("pybind11::detail::get_type_info: unable to find type object!"); + if (!type) return nullptr; - } } while (true); } -PYBIND11_NOINLINE inline detail::type_info *get_type_info(const std::type_info &tp) { +PYBIND11_NOINLINE inline detail::type_info *get_type_info(const std::type_info &tp, bool throw_if_missing) { auto &types = get_internals().registered_types_cpp; auto it = types.find(std::type_index(tp)); if (it != types.end()) return (detail::type_info *) it->second; + if (throw_if_missing) { + std::string tname = tp.name(); + detail::clean_type_id(tname); + pybind11_fail("pybind11::detail::get_type_info: unable to find type info for \"" + tname + "\""); + } return nullptr; } -PYBIND11_NOINLINE inline handle get_type_handle(const std::type_info &tp) { - detail::type_info *type_info = get_type_info(tp); +PYBIND11_NOINLINE inline handle get_type_handle(const std::type_info &tp, bool throw_if_missing) { + detail::type_info *type_info = get_type_info(tp, throw_if_missing); return handle(type_info ? ((PyObject *) type_info->type) : nullptr); } @@ -124,7 +129,7 @@ PYBIND11_NOINLINE inline handle get_object_handle(const void *ptr, const detail: auto &instances = get_internals().registered_instances; auto range = instances.equal_range(ptr); for (auto it = range.first; it != range.second; ++it) { - auto instance_type = detail::get_type_info(Py_TYPE(it->second), false); + auto instance_type = detail::get_type_info(Py_TYPE(it->second)); if (instance_type && instance_type == type) return handle((PyObject *) it->second); } @@ -149,18 +154,56 @@ inline void keep_alive_impl(handle nurse, handle patient); class type_caster_generic { public: PYBIND11_NOINLINE type_caster_generic(const std::type_info &type_info) - : typeinfo(get_type_info(type_info)) { } + : typeinfo(get_type_info(type_info, false)) { } PYBIND11_NOINLINE bool load(handle src, bool convert) { + return load(src, convert, Py_TYPE(src.ptr())); + } + + bool load(handle src, bool convert, PyTypeObject *tobj) { if (!src || !typeinfo) return false; if (src.is_none()) { value = nullptr; return true; - } else if (PyType_IsSubtype(Py_TYPE(src.ptr()), typeinfo->type)) { - value = ((instance *) src.ptr())->value; - return true; } + + if (typeinfo->simple_type) { /* Case 1: no multiple inheritance etc. involved */ + /* Check if we can safely perform a reinterpret-style cast */ + if (PyType_IsSubtype(tobj, typeinfo->type)) { + value = reinterpret_cast *>(src.ptr())->value; + return true; + } + } else { /* Case 2: multiple inheritance */ + /* Check if we can safely perform a reinterpret-style cast */ + if (tobj == typeinfo->type) { + value = reinterpret_cast *>(src.ptr())->value; + return true; + } + + /* If this is a python class, also check the parents recursively */ + auto const &type_dict = get_internals().registered_types_py; + bool new_style_class = PyType_Check(tobj); + if (type_dict.find(tobj) == type_dict.end() && new_style_class && tobj->tp_bases) { + tuple parents(tobj->tp_bases, true); + for (handle parent : parents) { + bool result = load(src, convert, (PyTypeObject *) parent.ptr()); + if (result) + return true; + } + } + + /* Try implicit casts */ + for (auto &cast : typeinfo->implicit_casts) { + type_caster_generic sub_caster(*cast.first); + if (sub_caster.load(src, convert)) { + value = cast.second(sub_caster.value); + return true; + } + } + } + + /* Perform an implicit conversion */ if (convert) { for (auto &converter : typeinfo->implicit_conversions) { temp = object(converter(src.ptr(), typeinfo->type), false); @@ -201,7 +244,7 @@ public: auto it_instances = internals.registered_instances.equal_range(src); for (auto it_i = it_instances.first; it_i != it_instances.second; ++it_i) { - auto instance_type = detail::get_type_info(Py_TYPE(it_i->second), false); + auto instance_type = detail::get_type_info(Py_TYPE(it_i->second)); if (instance_type && instance_type == tinfo) return handle((PyObject *) it_i->second).inc_ref(); } @@ -262,7 +305,8 @@ template class type_caster_base : public type_caster_generic { public: static PYBIND11_DESCR name() { return type_descr(_()); } - type_caster_base() : type_caster_generic(typeid(type)) { } + type_caster_base() : type_caster_base(typeid(type)) { } + type_caster_base(const std::type_info &info) : type_caster_generic(info) { } static handle cast(const itype &src, return_value_policy policy, handle parent) { if (policy == return_value_policy::automatic || policy == return_value_policy::automatic_reference) @@ -433,7 +477,7 @@ public: } /* Check if this is a C++ type */ - if (get_type_info((PyTypeObject *) h.get_type().ptr(), false)) { + if (get_type_info((PyTypeObject *) h.get_type().ptr())) { value = ((instance *) h.ptr())->value; return true; } @@ -749,22 +793,56 @@ protected: /// Type caster for holder types like std::shared_ptr, etc. template class type_caster_holder : public type_caster_base { public: - using type_caster_base::cast; - using type_caster_base::typeinfo; - using type_caster_base::value; - using type_caster_base::temp; + using base = type_caster_base; + using base::base; + using base::cast; + using base::typeinfo; + using base::value; + using base::temp; - bool load(handle src, bool convert) { - if (!src || !typeinfo) { + PYBIND11_NOINLINE bool load(handle src, bool convert) { + return load(src, convert, Py_TYPE(src.ptr())); + } + + bool load(handle src, bool convert, PyTypeObject *tobj) { + if (!src || !typeinfo) return false; - } else if (src.is_none()) { + if (src.is_none()) { value = nullptr; return true; - } else if (PyType_IsSubtype(Py_TYPE(src.ptr()), typeinfo->type)) { - auto inst = (instance *) src.ptr(); - value = (void *) inst->value; - holder = inst->holder; - return true; + } + + if (typeinfo->simple_type) { /* Case 1: no multiple inheritance etc. involved */ + /* Check if we can safely perform a reinterpret-style cast */ + if (PyType_IsSubtype(tobj, typeinfo->type)) { + auto inst = (instance *) src.ptr(); + value = (void *) inst->value; + holder = inst->holder; + return true; + } + } else { /* Case 2: multiple inheritance */ + /* Check if we can safely perform a reinterpret-style cast */ + if (tobj == typeinfo->type) { + auto inst = (instance *) src.ptr(); + value = (void *) inst->value; + holder = inst->holder; + return true; + } + + /* If this is a python class, also check the parents recursively */ + auto const &type_dict = get_internals().registered_types_py; + bool new_style_class = PyType_Check(tobj); + if (type_dict.find(tobj) == type_dict.end() && new_style_class && tobj->tp_bases) { + tuple parents(tobj->tp_bases, true); + for (handle parent : parents) { + bool result = load(src, convert, (PyTypeObject *) parent.ptr()); + if (result) + return true; + } + } + + if (try_implicit_casts(src, convert)) + return true; } if (convert) { @@ -774,6 +852,23 @@ public: return true; } } + + return false; + } + + template ::value, int> = 0> + bool try_implicit_casts(handle, bool) { return false; } + + template ::value, int> = 0> + bool try_implicit_casts(handle src, bool convert) { + for (auto &cast : typeinfo->implicit_casts) { + type_caster_holder sub_caster(*cast.first); + if (sub_caster.load(src, convert)) { + value = cast.second(sub_caster.value); + holder = holder_type(sub_caster.holder, (type *) value); + return true; + } + } return false; } @@ -968,7 +1063,6 @@ template <> inline void cast_safe(object &&) {} NAMESPACE_END(detail) - template tuple make_tuple(Args&&... args_) { const size_t size = sizeof...(Args); @@ -1023,7 +1117,7 @@ struct arg_v : arg { template arg_v arg::operator=(T &&value) const { return {name, std::forward(value)}; } -/// Alias for backward compatibility -- to be remove in version 2.0 +/// Alias for backward compatibility -- to be removed in version 2.0 template using arg_t = arg_v; inline namespace literals { @@ -1199,7 +1293,7 @@ unpacking_collector collect_arguments(Args &&...args) { "Invalid function call: positional args must precede keywords and ** unpacking; " "* unpacking must precede ** unpacking" ); - return {std::forward(args)...}; + return { std::forward(args)... }; } NAMESPACE_END(detail) diff --git a/include/pybind11/common.h b/include/pybind11/common.h index 164e381b8..0c2faf8e9 100644 --- a/include/pybind11/common.h +++ b/include/pybind11/common.h @@ -89,6 +89,7 @@ #include #include #include +#include #if PY_MAJOR_VERSION >= 3 /// Compatibility macros for various Python versions #define PYBIND11_INSTANCE_METHOD_NEW(ptr, class_) PyInstanceMethod_New(ptr) diff --git a/include/pybind11/operators.h b/include/pybind11/operators.h index 22d1859d0..2e78c01a3 100644 --- a/include/pybind11/operators.h +++ b/include/pybind11/operators.h @@ -10,7 +10,6 @@ #pragma once #include "pybind11.h" -#include #if defined(__clang__) && !defined(__INTEL_COMPILER) # pragma clang diagnostic ignored "-Wunsequenced" // multiple unsequenced modifications to 'self' (when using def(py::self OP Type())) diff --git a/include/pybind11/pybind11.h b/include/pybind11/pybind11.h index b602439b7..35c5f916d 100644 --- a/include/pybind11/pybind11.h +++ b/include/pybind11/pybind11.h @@ -17,6 +17,7 @@ # pragma warning(disable: 4512) // warning C4512: Assignment operator was implicitly defined as deleted # pragma warning(disable: 4800) // warning C4800: 'int': forcing value to bool 'true' or 'false' (performance warning) # pragma warning(disable: 4996) // warning C4996: The POSIX name for this item is deprecated. Instead, use the ISO C and C++ conformant name +# pragma warning(disable: 4702) // warning C4702: unreachable code #elif defined(__INTEL_COMPILER) # pragma warning(push) # pragma warning(disable: 186) // pointless comparison of unsigned integer with zero @@ -576,18 +577,6 @@ public: PYBIND11_OBJECT_DEFAULT(generic_type, object, PyType_Check) protected: void initialize(type_record *rec) { - if (rec->base_type) { - if (rec->base_handle) - pybind11_fail("generic_type: specified base type multiple times!"); - rec->base_handle = detail::get_type_handle(*(rec->base_type)); - if (!rec->base_handle) { - std::string tname(rec->base_type->name()); - detail::clean_type_id(tname); - pybind11_fail("generic_type: type \"" + std::string(rec->name) + - "\" referenced unknown base type \"" + tname + "\""); - } - } - auto &internals = get_internals(); auto tindex = std::type_index(*(rec->type)); @@ -617,6 +606,12 @@ protected: ht_qualname = name; } #endif + + size_t num_bases = rec->bases.size(); + tuple bases(num_bases); + for (size_t i = 0; i < num_bases; ++i) + bases[i] = rec->bases[i]; + std::string full_name = (scope_module ? ((std::string) scope_module.str() + "." + rec->name) : std::string(rec->name)); @@ -629,6 +624,11 @@ protected: memcpy((void *) tp_doc, rec->doc, size); } + /* Danger zone: from now (and until PyType_Ready), make sure to + issue no Python C API calls which could potentially invoke the + garbage collector (the GC will call type_traverse(), which will in + turn find the newly constructed type in an invalid state) */ + object type_holder(PyType_Type.tp_alloc(&PyType_Type, 0), false); auto type = (PyHeapTypeObject*) type_holder.ptr(); @@ -646,8 +646,12 @@ protected: /* Basic type attributes */ type->ht_type.tp_name = strdup(full_name.c_str()); type->ht_type.tp_basicsize = (ssize_t) rec->instance_size; - type->ht_type.tp_base = (PyTypeObject *) rec->base_handle.ptr(); - rec->base_handle.inc_ref(); + + if (num_bases > 0) { + type->ht_type.tp_base = (PyTypeObject *) ((object) bases[0]).inc_ref().ptr(); + type->ht_type.tp_bases = bases.release().ptr(); + rec->multiple_inheritance |= num_bases > 1; + } type->ht_name = name.release().ptr(); @@ -689,9 +693,23 @@ protected: if (rec->scope) rec->scope.attr(handle(type->ht_name)) = *this; + if (rec->multiple_inheritance) + mark_parents_nonsimple(&type->ht_type); + type_holder.release(); } + /// Helper function which tags all parents of a type using mult. inheritance + void mark_parents_nonsimple(PyTypeObject *value) { + tuple t(value->tp_bases, true); + for (handle h : t) { + auto tinfo2 = get_type_info((PyTypeObject *) h.ptr()); + if (tinfo2) + tinfo2->simple_type = false; + mark_parents_nonsimple((PyTypeObject *) h.ptr()); + } + } + /// Allocate a metaclass on demand (for static properties) handle metaclass() { auto &ht_type = ((PyHeapTypeObject *) m_ptr)->ht_type; @@ -811,31 +829,18 @@ protected: static void releasebuffer(PyObject *, Py_buffer *view) { delete (buffer_info *) view->internal; } }; -template class Predicate, typename... BaseTypes> struct class_selector; -template class Predicate, typename Base, typename... Bases> -struct class_selector { - static inline void set_bases(detail::type_record &record) { - if (Predicate::value) record.base_type = &typeid(Base); - else class_selector::set_bases(record); - } -}; -template class Predicate> -struct class_selector { - static inline void set_bases(detail::type_record &) {} -}; - NAMESPACE_END(detail) template class class_ : public detail::generic_type { template using is_holder = detail::is_holder_type; template using is_subtype = detail::bool_constant::value && !std::is_same::value>; - template using is_base_class = detail::bool_constant::value && !std::is_same::value>; + template using is_base = detail::bool_constant::value && !std::is_same::value>; template using is_valid_class_option = detail::bool_constant< is_holder::value || is_subtype::value || - is_base_class::value + is_base::value >; public: @@ -848,9 +853,6 @@ public: static_assert(detail::all_of_t::value, "Unknown/invalid class_ template parameters provided"); - static_assert(detail::count_t::value <= 1, - "Invalid class_ base types: multiple inheritance is not supported"); - PYBIND11_OBJECT(class_, detail::generic_type, PyType_Check) template @@ -864,7 +866,9 @@ public: record.init_holder = init_holder; record.dealloc = dealloc; - detail::class_selector::set_bases(record); + /* Register base classes specified via template arguments to class_, if any */ + bool unused[] = { (add_base(record), false)... }; + (void) unused; /* Process optional arguments, if any */ detail::process_attributes::init(extra..., &record); @@ -877,6 +881,16 @@ public: } } + template ::value, int> = 0> + static void add_base(detail::type_record &rec) { + rec.add_base(&typeid(Base), [](void *src) -> void * { + return static_cast(reinterpret_cast(src)); + }); + } + + template ::value, int> = 0> + static void add_base(detail::type_record &) { } + template class_ &def(const char *name_, Func&& f, const Extra&... extra) { cpp_function cf(std::forward(f), name(name_), @@ -1198,7 +1212,7 @@ template state; - if (!detail::get_type_info(typeid(state))) { + if (!detail::get_type_info(typeid(state), false)) { class_(handle(), "iterator") .def("__iter__", [](state &s) -> state& { return s; }) .def("__next__", [](state &s) -> ValueType { @@ -1223,7 +1237,7 @@ template state; - if (!detail::get_type_info(typeid(state))) { + if (!detail::get_type_info(typeid(state), false)) { class_(handle(), "iterator") .def("__iter__", [](state &s) -> state& { return s; }) .def("__next__", [](state &s) -> KeyType { diff --git a/include/pybind11/pytypes.h b/include/pybind11/pytypes.h index 56ebb7f31..6313a7ef1 100644 --- a/include/pybind11/pytypes.h +++ b/include/pybind11/pytypes.h @@ -612,7 +612,7 @@ public: } size_t size() const { return (size_t) PyList_Size(m_ptr); } detail::list_accessor operator[](size_t index) const { return detail::list_accessor(*this, index); } - void append(const object &object) const { PyList_Append(m_ptr, object.ptr()); } + void append(handle h) const { PyList_Append(m_ptr, h.ptr()); } }; class args : public tuple { PYBIND11_OBJECT_DEFAULT(args, tuple, PyTuple_Check) }; diff --git a/include/pybind11/stl_bind.h b/include/pybind11/stl_bind.h index efdb3f994..92285cec3 100644 --- a/include/pybind11/stl_bind.h +++ b/include/pybind11/stl_bind.h @@ -12,8 +12,6 @@ #include "common.h" #include "operators.h" -#include -#include #include #include diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 3be8cf898..bfc477660 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -27,9 +27,9 @@ set(PYBIND11_TEST_FILES test_pickling.cpp test_python_types.cpp test_sequences_and_iterators.cpp - test_smart_ptr.cpp test_stl_binders.cpp test_virtual_functions.cpp + test_multiple_inheritance.cpp ) string(REPLACE ".cpp" ".py" PYBIND11_PYTEST_FILES "${PYBIND11_TEST_FILES}") diff --git a/tests/test_inheritance.cpp b/tests/test_inheritance.cpp index f75e9cff7..f43edc261 100644 --- a/tests/test_inheritance.cpp +++ b/tests/test_inheritance.cpp @@ -61,7 +61,7 @@ test_initializer inheritance([](py::module &m) { .def(py::init()); /* Another way of declaring a subclass relationship: reference parent's C++ type */ - py::class_(m, "Rabbit", py::base()) + py::class_(m, "Rabbit") .def(py::init()); /* And another: list parent in class template arguments */ diff --git a/tests/test_multiple_inheritance.cpp b/tests/test_multiple_inheritance.cpp new file mode 100644 index 000000000..7a11d44d1 --- /dev/null +++ b/tests/test_multiple_inheritance.cpp @@ -0,0 +1,85 @@ +/* + tests/test_multiple_inheritance.cpp -- multiple inheritance, + implicit MI casts + + Copyright (c) 2016 Wenzel Jakob + + 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" + +PYBIND11_DECLARE_HOLDER_TYPE(T, std::shared_ptr); + +struct Base1 { + Base1(int i) : i(i) { } + int foo() { return i; } + int i; +}; + +struct Base2 { + Base2(int i) : i(i) { } + int bar() { return i; } + int i; +}; + +struct Base12 : Base1, Base2 { + Base12(int i, int j) : Base1(i), Base2(j) { } +}; + +struct MIType : Base12 { + MIType(int i, int j) : Base12(i, j) { } +}; + +test_initializer multiple_inheritance([](py::module &m) { + py::class_(m, "Base1") + .def(py::init()) + .def("foo", &Base1::foo); + + py::class_(m, "Base2") + .def(py::init()) + .def("bar", &Base2::bar); + + py::class_(m, "Base12"); + + py::class_(m, "MIType") + .def(py::init()); +}); + +/* Test the case where not all base classes are specified, + and where pybind11 requires the py::multiple_inheritance + flag to perform proper casting between types */ + +struct Base1a { + Base1a(int i) : i(i) { } + int foo() { return i; } + int i; +}; + +struct Base2a { + Base2a(int i) : i(i) { } + int bar() { return i; } + int i; +}; + +struct Base12a : Base1a, Base2a { + Base12a(int i, int j) : Base1a(i), Base2a(j) { } +}; + +test_initializer multiple_inheritance_nonexplicit([](py::module &m) { + py::class_>(m, "Base1a") + .def(py::init()) + .def("foo", &Base1a::foo); + + py::class_>(m, "Base2a") + .def(py::init()) + .def("bar", &Base2a::bar); + + py::class_>(m, "Base12a", py::multiple_inheritance()) + .def(py::init()); + + m.def("bar_base2a", [](Base2a *b) { return b->bar(); }); + m.def("bar_base2a_sharedptr", [](std::shared_ptr b) { return b->bar(); }); +}); diff --git a/tests/test_multiple_inheritance.py b/tests/test_multiple_inheritance.py new file mode 100644 index 000000000..7e1e08633 --- /dev/null +++ b/tests/test_multiple_inheritance.py @@ -0,0 +1,65 @@ +import pytest + + +def test_multiple_inheritance_cpp(msg): + from pybind11_tests import MIType + + mt = MIType(3, 4) + + assert mt.foo() == 3 + assert mt.bar() == 4 + + +def test_multiple_inheritance_mix1(msg): + from pybind11_tests import Base2 + + class Base1: + def __init__(self, i): + self.i = i + + def foo(self): + return self.i + + class MITypePy(Base1, Base2): + def __init__(self, i, j): + Base1.__init__(self, i) + Base2.__init__(self, j) + + mt = MITypePy(3, 4) + + assert mt.foo() == 3 + assert mt.bar() == 4 + + +def test_multiple_inheritance_mix2(msg): + from pybind11_tests import Base1 + + class Base2: + def __init__(self, i): + self.i = i + + def bar(self): + return self.i + + class MITypePy(Base1, Base2): + def __init__(self, i, j): + Base1.__init__(self, i) + Base2.__init__(self, j) + + mt = MITypePy(3, 4) + + assert mt.foo() == 3 + assert mt.bar() == 4 + + +def test_multiple_inheritance_virtbase(msg): + from pybind11_tests import Base12a, bar_base2a, bar_base2a_sharedptr + + class MITypePy(Base12a): + def __init__(self, i, j): + Base12a.__init__(self, i, j) + + mt = MITypePy(3, 4) + assert mt.bar() == 4 + assert bar_base2a(mt) == 4 + assert bar_base2a_sharedptr(mt) == 4