From 177928840ec1f7dc7ba8675056cf334f84aab193 Mon Sep 17 00:00:00 2001 From: Dustin Spicuzza Date: Sat, 29 Jan 2022 17:38:30 -0500 Subject: [PATCH 1/7] Document how to bind templates (#3665) --- docs/advanced/classes.rst | 52 +++++++++++++++++++++++++++++++++++++ docs/advanced/functions.rst | 35 +++++++++++++++++++++++++ 2 files changed, 87 insertions(+) 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); From 07103d6570f9d89ad2bdf05c3758addeaad78443 Mon Sep 17 00:00:00 2001 From: Mattia Basaglia Date: Sat, 29 Jan 2022 23:44:48 +0100 Subject: [PATCH 2/7] Remove extra semicolon (#3666) --- include/pybind11/detail/internals.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/pybind11/detail/internals.h b/include/pybind11/detail/internals.h index 462d32474..5c11b40a4 100644 --- a/include/pybind11/detail/internals.h +++ b/include/pybind11/detail/internals.h @@ -322,7 +322,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) { From dc4717bac28b2a320e32e3f823eb4d7da3736aeb Mon Sep 17 00:00:00 2001 From: Mattia Basaglia Date: Mon, 31 Jan 2022 17:10:45 +0100 Subject: [PATCH 3/7] fix: module extension detection for python 3.10 (#3663) * Fix module extension detection for python 3.10 * Fix for python < 3.10 * Use importlib --- tools/pybind11NewTools.cmake | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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) From bf7e5f9284a977d9d177b4dc4128542c291818f0 Mon Sep 17 00:00:00 2001 From: Henry Schreiner Date: Mon, 31 Jan 2022 11:38:06 -0500 Subject: [PATCH 4/7] fix(setup): support overriding CMake args (#3577) * fix: support conda-forge * Update setup.py * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- setup.py | 7 +++++++ 1 file changed, 7 insertions(+) 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) From 3a8d92308d3291e21c55baca8aa7b8dd5b3a8d56 Mon Sep 17 00:00:00 2001 From: Aaron Gokaslan Date: Mon, 31 Jan 2022 12:19:48 -0500 Subject: [PATCH 5/7] Fix caster optimization regression introduced in #3650 (#3659) * Fix optimization bug introduced in #3650 * Add simple Python extension test for MVF * Improve comments * Clarify comment * Clarify another comment * Add test docstring * Fix typo --- include/pybind11/detail/internals.h | 4 +++- include/pybind11/pybind11.h | 11 ++++++----- tests/test_multiple_inheritance.cpp | 2 +- tests/test_multiple_inheritance.py | 22 ++++++++++++++++++++++ 4 files changed, 32 insertions(+), 7 deletions(-) diff --git a/include/pybind11/detail/internals.h b/include/pybind11/detail/internals.h index 5c11b40a4..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; diff --git a/include/pybind11/pybind11.h b/include/pybind11/pybind11.h index df33d5182..fd60ea29f 100644 --- a/include/pybind11/pybind11.h +++ b/include/pybind11/pybind11.h @@ -1206,13 +1206,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/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 From 978617f6b548cdf15aba81fd79caacf455131e84 Mon Sep 17 00:00:00 2001 From: Aaron Gokaslan Date: Mon, 31 Jan 2022 12:57:32 -0500 Subject: [PATCH 6/7] fix issue 3668 by removing bool casts in numpy.h (#3669) --- include/pybind11/numpy.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/include/pybind11/numpy.h b/include/pybind11/numpy.h index 8e83b506e..95a743ace 100644 --- a/include/pybind11/numpy.h +++ b/include/pybind11/numpy.h @@ -170,10 +170,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_)(); From ce18721d830a9deb8cf1883b513bcb8f98090c35 Mon Sep 17 00:00:00 2001 From: Aaron Gokaslan Date: Mon, 31 Jan 2022 15:13:05 -0500 Subject: [PATCH 7/7] Ensure TypeError use raise_from for C++->Python overload res. (#3671) --- include/pybind11/pybind11.h | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/include/pybind11/pybind11.h b/include/pybind11/pybind11.h index fd60ea29f..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; }