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/classes.rst b/docs/classes.rst index 80f378f68..4270b8d6d 100644 --- a/docs/classes.rst +++ b/docs/classes.rst @@ -185,10 +185,9 @@ inheritance relationship: std::string bark() const { return "woof!"; } }; -There are three different ways of indicating a hierarchical relationship to +There are two different ways of indicating a hierarchical relationship to pybind11: the first specifies the C++ base class as an extra template -parameter of the :class:`class_`; the second uses a special ``base`` attribute -passed into the constructor: +parameter of the :class:`class_`: .. code-block:: cpp @@ -201,11 +200,6 @@ passed into the constructor: .def(py::init()) .def("bark", &Dog::bark); - // Method 2: py::base attribute: - py::class_(m, "Dog", py::base() /* <- specify C++ parent type */) - .def(py::init()) - .def("bark", &Dog::bark); - Alternatively, we can also assign a name to the previously bound ``Pet`` :class:`class_` object and reference it when binding the ``Dog`` class: @@ -215,13 +209,13 @@ Alternatively, we can also assign a name to the previously bound ``Pet`` pet.def(py::init()) .def_readwrite("name", &Pet::name); - // Method 3: pass parent class_ object: + // Method 2: pass parent class_ object: py::class_(m, "Dog", pet /* <- specify Python parent type */) .def(py::init()) .def("bark", &Dog::bark); -Functionality-wise, all three approaches are completely equivalent. Afterwards, -instances will expose fields and methods of both types: +Functionality-wise, both approaches are equivalent. Afterwards, instances will +expose fields and methods of both types: .. code-block:: pycon 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 e3434b145..ba297355d 100644 --- a/include/pybind11/attr.h +++ b/include/pybind11/attr.h @@ -33,11 +33,17 @@ struct name { const char *value; name(const char *value) : value(value) { } }; struct sibling { handle value; sibling(const handle &value) : value(value.ptr()) { } }; /// Annotation indicating that a class derives from another given type -template struct base { }; +template struct base { + PYBIND11_DEPRECATED("base() was deprecated in favor of specifying 'T' as a template argument to class_") + 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; @@ -124,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; @@ -145,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; @@ -253,14 +276,20 @@ 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; } +struct process_attribute::value>> : process_attribute_default { + 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; } }; /*** @@ -269,13 +298,13 @@ struct process_attribute> : process_attribute_default> { * otherwise */ template struct process_attribute> : public process_attribute_default> { - template ::type = 0> + template = 0> static void precall(handle args) { keep_alive_impl(Nurse, Patient, args, handle()); } - template ::type = 0> + template = 0> static void postcall(handle, handle) { } - template ::type = 0> + template = 0> static void precall(handle) { } - template ::type = 0> + template = 0> static void postcall(handle args, handle ret) { keep_alive_impl(Nurse, Patient, args, ret); } }; diff --git a/include/pybind11/cast.h b/include/pybind11/cast.h index b885298a7..780a02efc 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) @@ -299,10 +343,10 @@ protected: #else /* Visual Studio 2015's SFINAE implementation doesn't yet handle the above robustly in all situations. Use a workaround that only tests for constructibility for now. */ - template ::value>::type> + template ::value>> static Constructor make_copy_constructor(const T *value) { return [](const void *arg) -> void * { return new T(*((const T *)arg)); }; } - template ::value>::type> + template ::value>> static Constructor make_move_constructor(const T *value) { return [](const void *arg) -> void * { return (void *) new T(std::move(*((T *)arg))); }; } #endif @@ -343,8 +387,8 @@ public: template struct type_caster< - T, typename std::enable_if::value || - std::is_floating_point::value>::type> { + T, enable_if_t::value || + std::is_floating_point::value>> { typedef typename std::conditional::type _py_type_0; typedef typename std::conditional::value, _py_type_0, typename std::make_unsigned<_py_type_0>::type>::type _py_type_1; typedef typename std::conditional::value, double, _py_type_1>::type py_type; @@ -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; } @@ -658,20 +702,20 @@ public: return load(src, convert, typename make_index_sequence::type()); } - template ::value && - !std::is_same::value, int>::type = 0> + !std::is_same::value, int> = 0> bool load_args(handle args, handle, bool convert) { return load(args, convert, typename make_index_sequence::type()); } - template ::value, int>::type = 0> + template ::value, int> = 0> bool load_args(handle args, handle, bool convert) { std::get<0>(value).load(args, convert); return true; } - template ::value, int>::type = 0> + template ::value, int> = 0> bool load_args(handle args, handle kwargs, bool convert) { std::get<0>(value).load(args, convert); std::get<1>(value).load(kwargs, convert); @@ -690,11 +734,11 @@ public: return type_descr(_("Tuple[") + element_names() + _("]")); } - template typename std::enable_if::value, ReturnValue>::type call(Func &&f) { + template enable_if_t::value, ReturnValue> call(Func &&f) { return call(std::forward(f), typename make_index_sequence::type()); } - template typename std::enable_if::value, void_type>::type call(Func &&f) { + template enable_if_t::value, void_type> call(Func &&f) { call(std::forward(f), typename make_index_sequence::type()); return void_type(); } @@ -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; } @@ -813,12 +908,12 @@ template <> struct handle_type_name { static PYBIND11_DESCR name() { retur template <> struct handle_type_name { static PYBIND11_DESCR name() { return _("**kwargs"); } }; template -struct type_caster::value>::type> { +struct type_caster::value>> { public: - template ::value, int>::type = 0> + template ::value, int> = 0> bool load(handle src, bool /* convert */) { value = type(src); return value.check(); } - template ::value, int>::type = 0> + template ::value, int> = 0> bool load(handle src, bool /* convert */) { value = type(src, true); return value.check(); } static handle cast(const handle &src, return_value_policy /* policy */, handle /* parent */) { @@ -837,21 +932,21 @@ public: // must have ref_count() == 1)h // If any of the above are not satisfied, we fall back to copying. template struct move_is_plain_type : std::false_type {}; -template struct move_is_plain_type struct move_is_plain_type::value && !std::is_pointer::value && !std::is_reference::value && !std::is_const::value - >::type> : std::true_type {}; + >> : std::true_type { }; template struct move_always : std::false_type {}; -template struct move_always struct move_always::value && !std::is_copy_constructible::value && std::is_move_constructible::value && std::is_same>().operator T&()), T&>::value - >::type> : std::true_type {}; + >> : std::true_type { }; template struct move_if_unreferenced : std::false_type {}; -template struct move_if_unreferenced struct move_if_unreferenced::value && !move_always::value && std::is_move_constructible::value && std::is_same>().operator T&()), T&>::value - >::type> : std::true_type {}; + >> : std::true_type { }; template using move_never = std::integral_constant::value && !move_if_unreferenced::value>; // Detect whether returning a `type` from a cast on type's type_caster is going to result in a @@ -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 fd65fe762..86af6993a 100644 --- a/include/pybind11/common.h +++ b/include/pybind11/common.h @@ -30,6 +30,16 @@ # define PYBIND11_NOINLINE __attribute__ ((noinline)) #endif +#if __cplusplus > 201103L +# define PYBIND11_DEPRECATED(reason) [[deprecated(reason)]] +#elif defined(__clang__) +# define PYBIND11_DEPRECATED(reason) __attribute__((deprecated(reason))) +#elif defined(__GNUG__) +# define PYBIND11_DEPRECATED(reason) __attribute__((deprecated)) +#elif defined(_MSC_VER) +# define PYBIND11_DEPRECATED(reason) __declspec(deprecated) +#endif + #define PYBIND11_VERSION_MAJOR 1 #define PYBIND11_VERSION_MINOR 9 #define PYBIND11_VERSION_PATCH dev0 @@ -79,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) @@ -431,14 +442,14 @@ PYBIND11_RUNTIME_EXCEPTION(reference_cast_error, PyExc_RuntimeError) /// Used in template struct format_descriptor { }; -template struct format_descriptor::value>::type> { +template struct format_descriptor::value>> { static constexpr const char value[2] = { "bBhHiIqQ"[detail::log2(sizeof(T))*2 + (std::is_unsigned::value ? 1 : 0)], '\0' }; static std::string format() { return value; } }; template constexpr const char format_descriptor< - T, typename std::enable_if::value>::type>::value[2]; + T, detail::enable_if_t::value>>::value[2]; /// RAII wrapper that temporarily clears any Python error state struct error_scope { diff --git a/include/pybind11/descr.h b/include/pybind11/descr.h index ad3c7ddd4..f8a349b1a 100644 --- a/include/pybind11/descr.h +++ b/include/pybind11/descr.h @@ -86,11 +86,11 @@ template struct int_to_str<0, Digits...> { // Ternary description (like std::conditional) template -constexpr typename std::enable_if>::type _(char const(&text1)[Size1], char const(&)[Size2]) { +constexpr enable_if_t> _(char const(&text1)[Size1], char const(&)[Size2]) { return _(text1); } template -constexpr typename std::enable_if>::type _(char const(&)[Size1], char const(&text2)[Size2]) { +constexpr enable_if_t> _(char const(&)[Size1], char const(&text2)[Size2]) { return _(text2); } @@ -164,8 +164,8 @@ PYBIND11_NOINLINE inline descr _(const char *text) { return descr(text, types); } -template PYBIND11_NOINLINE typename std::enable_if::type _(const char *text1, const char *) { return _(text1); } -template PYBIND11_NOINLINE typename std::enable_if::type _(char const *, const char *text2) { return _(text2); } +template PYBIND11_NOINLINE enable_if_t _(const char *text1, const char *) { return _(text1); } +template PYBIND11_NOINLINE enable_if_t _(char const *, const char *text2) { return _(text2); } template PYBIND11_NOINLINE descr _() { const std::type_info *types[2] = { &typeid(Type), nullptr }; diff --git a/include/pybind11/eigen.h b/include/pybind11/eigen.h index 00f05cdf2..c4384ca7a 100644 --- a/include/pybind11/eigen.h +++ b/include/pybind11/eigen.h @@ -46,9 +46,9 @@ public: // type_caster to handle argument copying/forwarding. template class is_eigen_ref { private: - template static typename std::enable_if< + template static enable_if_t< std::is_same::type, Eigen::Ref>::value, - Derived>::type test(const Eigen::Ref &); + Derived> test(const Eigen::Ref &); static void test(...); public: typedef decltype(test(std::declval())) Derived; @@ -77,7 +77,7 @@ public: }; template -struct type_caster::value && !is_eigen_ref::value>::type> { +struct type_caster::value && !is_eigen_ref::value>> { typedef typename Type::Scalar Scalar; static constexpr bool rowMajor = Type::Flags & Eigen::RowMajorBit; static constexpr bool isVector = Type::IsVectorAtCompileTime; @@ -149,18 +149,18 @@ struct type_caster::value && _("[") + rows() + _(", ") + cols() + _("]]")); protected: - template ::type = 0> + template = 0> static PYBIND11_DESCR rows() { return _("m"); } - template ::type = 0> + template = 0> static PYBIND11_DESCR rows() { return _(); } - template ::type = 0> + template = 0> static PYBIND11_DESCR cols() { return _("n"); } - template ::type = 0> + template = 0> static PYBIND11_DESCR cols() { return _(); } }; template -struct type_caster::value && is_eigen_ref::value>::type> { +struct type_caster::value && is_eigen_ref::value>> { protected: using Derived = typename std::remove_const::Derived>::type; using DerivedCaster = type_caster; @@ -181,7 +181,7 @@ public: // type_caster for special matrix types (e.g. DiagonalMatrix): load() is not supported, but we can // cast them into the python domain by first copying to a regular Eigen::Matrix, then casting that. template -struct type_caster::value && !is_eigen_ref::value>::type> { +struct type_caster::value && !is_eigen_ref::value>> { protected: using Matrix = Eigen::Matrix; using MatrixCaster = type_caster; @@ -198,7 +198,7 @@ public: }; template -struct type_caster::value>::type> { +struct type_caster::value>> { typedef typename Type::Scalar Scalar; typedef typename std::remove_reference().outerIndexPtr())>::type StorageIndex; typedef typename Type::Index Index; diff --git a/include/pybind11/numpy.h b/include/pybind11/numpy.h index 0768343fd..81dfaea13 100644 --- a/include/pybind11/numpy.h +++ b/include/pybind11/numpy.h @@ -486,7 +486,7 @@ public: }; template -struct format_descriptor::value>::type> { +struct format_descriptor::value>> { static std::string format() { return detail::npy_format_descriptor::type>::format(); } @@ -517,7 +517,7 @@ struct is_pod_struct { !std::is_same::type, std::complex>::value }; }; -template struct npy_format_descriptor::value>::type> { +template struct npy_format_descriptor::value>> { private: constexpr static const int values[8] = { npy_api::NPY_BYTE_, npy_api::NPY_UBYTE_, npy_api::NPY_SHORT_, npy_api::NPY_USHORT_, @@ -529,13 +529,13 @@ public: return object(ptr, true); pybind11_fail("Unsupported buffer format!"); } - template ::value, int>::type = 0> + template ::value, int> = 0> static PYBIND11_DESCR name() { return _("int") + _(); } - template ::value, int>::type = 0> + template ::value, int> = 0> static PYBIND11_DESCR name() { return _("uint") + _(); } }; template constexpr const int npy_format_descriptor< - T, typename std::enable_if::value>::type>::values[8]; + T, enable_if_t::value>>::values[8]; #define DECL_FMT(Type, NumPyName, Name) template<> struct npy_format_descriptor { \ enum { value = npy_api::NumPyName }; \ @@ -568,7 +568,7 @@ struct field_descriptor { }; template -struct npy_format_descriptor::value>::type> { +struct npy_format_descriptor::value>> { static PYBIND11_DESCR name() { return _("struct"); } static pybind11::dtype dtype() { @@ -634,9 +634,9 @@ private: }; template -std::string npy_format_descriptor::value>::type>::format_str; +std::string npy_format_descriptor::value>>::format_str; template -PyObject* npy_format_descriptor::value>::type>::dtype_ptr = nullptr; +PyObject* npy_format_descriptor::value>>::dtype_ptr = nullptr; // Extract name, offset and format descriptor for a struct field #define PYBIND11_FIELD_DESCRIPTOR(Type, Field) \ 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 1468467be..4a6fa8dcf 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 @@ -460,8 +461,10 @@ protected: if (overloads->is_operator) return handle(Py_NotImplemented).inc_ref().ptr(); - std::string msg = "Incompatible " + std::string(overloads->is_constructor ? "constructor" : "function") + - " arguments. The following argument types are supported:\n"; + std::string msg = std::string(overloads->name) + "(): incompatible " + + std::string(overloads->is_constructor ? "constructor" : "function") + + " arguments. The following argument types are supported:\n"; + int ctr = 0; for (detail::function_record *it2 = overloads; it2 != nullptr; it2 = it2->next) { msg += " "+ std::to_string(++ctr) + ". "; @@ -489,7 +492,7 @@ protected: msg += "\n"; } - msg += " Invoked with: "; + msg += "\nInvoked with: "; tuple args_(args, true); for (size_t ti = overloads->is_constructor ? 1 : 0; ti < args_.size(); ++ti) { msg += static_cast(static_cast(args_[ti]).str()); @@ -574,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)); @@ -615,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)); @@ -627,11 +624,16 @@ 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(); if (!type_holder || !name) - pybind11_fail("generic_type: unable to create type object!"); + pybind11_fail(std::string(rec->name) + ": Unable to create type object!"); /* Register supplemental type information in C++ dict */ detail::type_info *tinfo = new detail::type_info(); @@ -644,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(); @@ -676,7 +682,8 @@ protected: type->ht_type.tp_doc = tp_doc; if (PyType_Ready(&type->ht_type) < 0) - pybind11_fail("generic_type: PyType_Ready failed!"); + pybind11_fail(std::string(rec->name) + ": PyType_Ready failed (" + + detail::error_string() + ")!"); m_ptr = type_holder.ptr(); @@ -687,9 +694,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; @@ -809,31 +830,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: @@ -846,9 +854,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 @@ -862,7 +867,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)..., false }; + (void) unused; /* Process optional arguments, if any */ detail::process_attributes::init(extra..., &record); @@ -875,6 +882,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_), @@ -1016,7 +1033,7 @@ private: /// Initialize holder object, variant 2: try to construct from existing holder object, if possible template ::value, int>::type = 0> + detail::enable_if_t::value, int> = 0> static void init_holder_helper(instance_type *inst, const holder_type *holder_ptr, const void * /* dummy */) { if (holder_ptr) new (&inst->holder) holder_type(*holder_ptr); @@ -1026,7 +1043,7 @@ private: /// Initialize holder object, variant 3: holder is not copy constructible (e.g. unique_ptr), always initialize from raw pointer template ::value, int>::type = 0> + detail::enable_if_t::value, int> = 0> static void init_holder_helper(instance_type *inst, const holder_type * /* unused */, const void * /* dummy */) { new (&inst->holder) holder_type(inst->value); } @@ -1196,7 +1213,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 { @@ -1221,7 +1238,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 ba5d2c4c6..8681bbb12 100644 --- a/include/pybind11/pytypes.h +++ b/include/pybind11/pytypes.h @@ -43,10 +43,8 @@ public: bool is_none() const { return m_ptr == Py_None; } template T cast() const; template - #if __cplusplus > 201103L - [[deprecated("call(...) was deprecated in favor of operator()(...)")]] - #endif - object call(Args&&... args) const; + PYBIND11_DEPRECATED("call(...) was deprecated in favor of operator()(...)") + object call(Args&&... args) const; template object operator()(Args&&... args) const; operator bool() const { return m_ptr != nullptr; } @@ -492,7 +490,7 @@ class int_ : public object { public: PYBIND11_OBJECT_DEFAULT(int_, object, PYBIND11_LONG_CHECK) template ::value, int>::type = 0> + detail::enable_if_t::value, int> = 0> int_(T value) { if (sizeof(T) <= sizeof(long)) { if (std::is_signed::value) @@ -509,7 +507,7 @@ public: } template ::value, int>::type = 0> + detail::enable_if_t::value, int> = 0> operator T() const { if (sizeof(T) <= sizeof(long)) { if (std::is_signed::value) @@ -614,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.h b/include/pybind11/stl.h index 4390efac9..1f052ee0b 100644 --- a/include/pybind11/stl.h +++ b/include/pybind11/stl.h @@ -112,7 +112,7 @@ template struct list_caster { } template ().reserve(0)), void>::value, int>::type = 0> + enable_if_t().reserve(0)), void>::value, int> = 0> void reserve_maybe(list l, Type *) { value.reserve(l.size()); } void reserve_maybe(list, void *) { } diff --git a/include/pybind11/stl_bind.h b/include/pybind11/stl_bind.h index efdb3f994..24963aaa0 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 @@ -42,20 +40,20 @@ struct is_comparable : std::false_type { }; /* For non-map data structures, check whether operator== can be instantiated */ template struct is_comparable< - T, typename std::enable_if::is_element && - container_traits::is_comparable>::type> + T, enable_if_t::is_element && + container_traits::is_comparable>> : std::true_type { }; /* For a vector/map data structure, recursively check the value type (which is std::pair for maps) */ template -struct is_comparable::is_vector>::type> { +struct is_comparable::is_vector>> { static constexpr const bool value = is_comparable::value; }; /* For pairs, recursively check the two data types */ template -struct is_comparable::is_pair>::type> { +struct is_comparable::is_pair>> { static constexpr const bool value = is_comparable::value && is_comparable::value; @@ -66,13 +64,13 @@ template void vector_if_copy_constructibl template void vector_if_equal_operator(const Args&...) { } template void vector_if_insertion_operator(const Args&...) { } -template::value, int>::type = 0> +template::value, int> = 0> void vector_if_copy_constructible(Class_ &cl) { cl.def(pybind11::init(), "Copy constructor"); } -template::value, int>::type = 0> +template::value, int> = 0> void vector_if_equal_operator(Class_ &cl) { using T = typename Vector::value_type; @@ -378,7 +376,7 @@ template void map_if_copy_assi ); } -template::value, int>::type = 0> +template::value, int> = 0> void map_if_copy_assignable(Class_ &cl) { using KeyType = typename Map::key_type; using MappedType = typename Map::mapped_type; diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index e52e982a6..42d7c6450 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -29,9 +29,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_callbacks.py b/tests/test_callbacks.py index 8f867d43a..c2668aa95 100644 --- a/tests/test_callbacks.py +++ b/tests/test_callbacks.py @@ -83,7 +83,7 @@ def test_cpp_function_roundtrip(): with pytest.raises(TypeError) as excinfo: test_dummy_function(dummy_function2) - assert "Incompatible function arguments" in str(excinfo.value) + assert "incompatible function arguments" in str(excinfo.value) with pytest.raises(TypeError) as excinfo: test_dummy_function(lambda x, y: x + y) 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_inheritance.py b/tests/test_inheritance.py index d6997b09c..351fe6b2c 100644 --- a/tests/test_inheritance.py +++ b/tests/test_inheritance.py @@ -24,9 +24,10 @@ def test_inheritance(msg): with pytest.raises(TypeError) as excinfo: dog_bark(polly) assert msg(excinfo.value) == """ - Incompatible function arguments. The following argument types are supported: + dog_bark(): incompatible function arguments. The following argument types are supported: 1. (arg0: m.Dog) -> str - Invoked with: + + Invoked with: """ diff --git a/tests/test_issues.py b/tests/test_issues.py index ad3d39d51..e2ab0b45c 100644 --- a/tests/test_issues.py +++ b/tests/test_issues.py @@ -65,17 +65,19 @@ def test_no_id(capture, msg): with pytest.raises(TypeError) as excinfo: get_element(None) assert msg(excinfo.value) == """ - Incompatible function arguments. The following argument types are supported: + get_element(): incompatible function arguments. The following argument types are supported: 1. (arg0: m.issues.ElementA) -> int - Invoked with: None + + Invoked with: None """ with pytest.raises(TypeError) as excinfo: expect_int(5.2) assert msg(excinfo.value) == """ - Incompatible function arguments. The following argument types are supported: + expect_int(): incompatible function arguments. The following argument types are supported: 1. (arg0: int) -> int - Invoked with: 5.2 + + Invoked with: 5.2 """ assert expect_float(12) == 12 @@ -90,10 +92,11 @@ def test_str_issue(msg): with pytest.raises(TypeError) as excinfo: str(StrIssue("no", "such", "constructor")) assert msg(excinfo.value) == """ - Incompatible constructor arguments. The following argument types are supported: + __init__(): incompatible constructor arguments. The following argument types are supported: 1. m.issues.StrIssue(arg0: int) 2. m.issues.StrIssue() - Invoked with: no, such, constructor + + Invoked with: no, such, constructor """ diff --git a/tests/test_kwargs_and_defaults.py b/tests/test_kwargs_and_defaults.py index 0e1ea805e..852d03c6e 100644 --- a/tests/test_kwargs_and_defaults.py +++ b/tests/test_kwargs_and_defaults.py @@ -35,9 +35,10 @@ def test_named_arguments(msg): # noinspection PyArgumentList kw_func2(x=5, y=10, z=12) assert msg(excinfo.value) == """ - Incompatible function arguments. The following argument types are supported: + kw_func2(): incompatible function arguments. The following argument types are supported: 1. (x: int=100, y: int=200) -> str - Invoked with: + + Invoked with: """ assert kw_func4() == "{13 17}" 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 diff --git a/tests/test_opaque_types.py b/tests/test_opaque_types.py index 943686383..8a6a4c3f3 100644 --- a/tests/test_opaque_types.py +++ b/tests/test_opaque_types.py @@ -35,9 +35,10 @@ def test_pointers(msg): with pytest.raises(TypeError) as excinfo: get_void_ptr_value([1, 2, 3]) # This should not work assert msg(excinfo.value) == """ - Incompatible function arguments. The following argument types are supported: + get_void_ptr_value(): incompatible function arguments. The following argument types are supported: 1. (arg0: capsule) -> int - Invoked with: [1, 2, 3] + + Invoked with: [1, 2, 3] """ assert return_null_str() is None