diff --git a/docs/advanced/classes.rst b/docs/advanced/classes.rst index ba006252a..f3339336d 100644 --- a/docs/advanced/classes.rst +++ b/docs/advanced/classes.rst @@ -1161,6 +1161,58 @@ error: .. versionadded:: 2.6 +Binding classes with template parameters +======================================== + +pybind11 can also wrap classes that have template parameters. Consider these classes: + +.. code-block:: cpp + + struct Cat {}; + struct Dog {}; + + template + struct Cage { + Cage(PetType& pet); + PetType& get(); + }; + +C++ templates may only be instantiated at compile time, so pybind11 can only +wrap instantiated templated classes. You cannot wrap a non-instantiated template: + +.. code-block:: cpp + + // BROKEN (this will not compile) + py::class_(m, "Cage"); + .def("get", &Cage::get); + +You must explicitly specify each template/type combination that you want to +wrap separately. + +.. code-block:: cpp + + // ok + py::class_>(m, "CatCage") + .def("get", &Cage::get); + + // ok + py::class_>(m, "DogCage") + .def("get", &Cage::get); + +If your class methods have template parameters you can wrap those as well, +but once again each instantiation must be explicitly specified: + +.. code-block:: cpp + + typename + struct MyClass { + template + T fn(V v); + }; + + py::class>(m, "MyClassT") + .def("fn", &MyClass::fn); + Custom automatic downcasters ============================ diff --git a/docs/advanced/functions.rst b/docs/advanced/functions.rst index d37b8ecab..bf5b5fa00 100644 --- a/docs/advanced/functions.rst +++ b/docs/advanced/functions.rst @@ -578,3 +578,38 @@ prefers earlier-defined overloads to later-defined ones. .. versionadded:: 2.6 The ``py::prepend()`` tag. + +Binding functions with template parameters +========================================== + +You can bind functions that have template parameters. Here's a function: + +.. code-block:: cpp + + template + void set(T t); + +C++ templates cannot be instantiated at runtime, so you cannot bind the +non-instantiated function: + +.. code-block:: cpp + + // BROKEN (this will not compile) + m.def("set", &set); + +You must bind each instantiated function template separately. You may bind +each instantiation with the same name, which will be treated the same as +an overloaded function: + +.. code-block:: cpp + + m.def("set", &set); + m.def("set", &set); + +Sometimes it's more clear to bind them with separate names, which is also +an option: + +.. code-block:: cpp + + m.def("setInt", &set); + m.def("setString", &set); diff --git a/include/pybind11/detail/internals.h b/include/pybind11/detail/internals.h index 462d32474..9edb9492e 100644 --- a/include/pybind11/detail/internals.h +++ b/include/pybind11/detail/internals.h @@ -200,7 +200,9 @@ struct type_info { void *get_buffer_data = nullptr; void *(*module_local_load)(PyObject *, const type_info *) = nullptr; /* A simple type never occurs as a (direct or indirect) parent - * of a class that makes use of multiple inheritance */ + * of a class that makes use of multiple inheritance. + * A type can be simple even if it has non-simple ancestors as long as it has no descendants. + */ bool simple_type : 1; /* True if there is no multiple inheritance in this type's inheritance tree */ bool simple_ancestors : 1; @@ -322,7 +324,7 @@ inline bool raise_err(PyObject *exc_type, const char *msg) { #endif PyErr_SetString(exc_type, msg); return false; -}; +} inline void translate_exception(std::exception_ptr p) { if (!p) { diff --git a/include/pybind11/numpy.h b/include/pybind11/numpy.h index 29adc28e0..e0b897a80 100644 --- a/include/pybind11/numpy.h +++ b/include/pybind11/numpy.h @@ -177,10 +177,10 @@ struct npy_api { } bool PyArray_Check_(PyObject *obj) const { - return (bool) PyObject_TypeCheck(obj, PyArray_Type_); + return PyObject_TypeCheck(obj, PyArray_Type_) != 0; } bool PyArrayDescr_Check_(PyObject *obj) const { - return (bool) PyObject_TypeCheck(obj, PyArrayDescr_Type_); + return PyObject_TypeCheck(obj, PyArrayDescr_Type_) != 0; } unsigned int (*PyArray_GetNDArrayCFeatureVersion_)(); diff --git a/include/pybind11/pybind11.h b/include/pybind11/pybind11.h index df33d5182..ede35e186 100644 --- a/include/pybind11/pybind11.h +++ b/include/pybind11/pybind11.h @@ -988,6 +988,13 @@ protected: } append_note_if_missing_header_is_suspected(msg); +#if PY_VERSION_HEX >= 0x03030000 + // Attach additional error info to the exception if supported + if (PyErr_Occurred()) { + raise_from(PyExc_TypeError, msg.c_str()); + return nullptr; + } +#endif PyErr_SetString(PyExc_TypeError, msg.c_str()); return nullptr; } @@ -1206,13 +1213,14 @@ protected: if (rec.bases.size() > 1 || rec.multiple_inheritance) { mark_parents_nonsimple(tinfo->type); tinfo->simple_ancestors = false; - tinfo->simple_type = false; } else if (rec.bases.size() == 1) { - auto parent_tinfo = get_type_info((PyTypeObject *) rec.bases[0].ptr()); - tinfo->simple_ancestors = parent_tinfo->simple_ancestors; - // a child of a non-simple type can never be a simple type - tinfo->simple_type = parent_tinfo->simple_type; + auto *parent_tinfo = get_type_info((PyTypeObject *) rec.bases[0].ptr()); + assert(parent_tinfo != nullptr); + bool parent_simple_ancestors = parent_tinfo->simple_ancestors; + tinfo->simple_ancestors = parent_simple_ancestors; + // The parent can no longer be a simple type if it has MI and has a child + parent_tinfo->simple_type = parent_tinfo->simple_type && parent_simple_ancestors; } if (rec.module_local) { diff --git a/setup.py b/setup.py index a2326287d..0e7348982 100644 --- a/setup.py +++ b/setup.py @@ -146,6 +146,13 @@ with remove_output("pybind11/include", "pybind11/share"): "-DBUILD_TESTING=OFF", "-DPYBIND11_NOPYTHON=ON", ] + if "CMAKE_ARGS" in os.environ: + fcommand = [ + c + for c in os.environ["CMAKE_ARGS"].split() + if "DCMAKE_INSTALL_PREFIX" not in c + ] + cmd += fcommand cmake_opts = dict(cwd=DIR, stdout=sys.stdout, stderr=sys.stderr) subprocess.check_call(cmd, **cmake_opts) subprocess.check_call(["cmake", "--install", tmpdir], **cmake_opts) diff --git a/tests/test_multiple_inheritance.cpp b/tests/test_multiple_inheritance.cpp index 44b9876eb..4689df4e4 100644 --- a/tests/test_multiple_inheritance.cpp +++ b/tests/test_multiple_inheritance.cpp @@ -235,7 +235,7 @@ TEST_SUBMODULE(multiple_inheritance, m) { // - functions are get_{base}_{var}, return {var} struct MVB { MVB() = default; - MVB(const MVB&) = default; + MVB(const MVB &) = default; virtual ~MVB() = default; int b = 1; diff --git a/tests/test_multiple_inheritance.py b/tests/test_multiple_inheritance.py index 71741b925..abdf25d60 100644 --- a/tests/test_multiple_inheritance.py +++ b/tests/test_multiple_inheritance.py @@ -472,3 +472,25 @@ def test_pr3635_diamond_f(): assert o.get_f_e() == 5 assert o.get_f_f() == 6 + + +def test_python_inherit_from_mi(): + """Tests extending a Python class from a single inheritor of a MI class""" + + class PyMVF(m.MVF): + g = 7 + + def get_g_g(self): + return self.g + + o = PyMVF() + + assert o.b == 1 + assert o.c == 2 + assert o.d0 == 3 + assert o.d1 == 4 + assert o.e == 5 + assert o.f == 6 + assert o.g == 7 + + assert o.get_g_g() == 7 diff --git a/tools/pybind11NewTools.cmake b/tools/pybind11NewTools.cmake index 9e2fd2b40..0b4e21cce 100644 --- a/tools/pybind11NewTools.cmake +++ b/tools/pybind11NewTools.cmake @@ -110,7 +110,7 @@ if(NOT DEFINED PYTHON_MODULE_EXTENSION) execute_process( COMMAND "${${_Python}_EXECUTABLE}" "-c" - "from distutils import sysconfig as s;print(s.get_config_var('EXT_SUFFIX') or s.get_config_var('SO'))" + "import sys, importlib; s = importlib.import_module('distutils.sysconfig' if sys.version_info < (3, 10) else 'sysconfig'); print(s.get_config_var('EXT_SUFFIX') or s.get_config_var('SO'))" OUTPUT_VARIABLE _PYTHON_MODULE_EXTENSION ERROR_VARIABLE _PYTHON_MODULE_EXTENSION_ERR OUTPUT_STRIP_TRAILING_WHITESPACE)