From 4f1493d479fd4557cae40822a3223a82c1fbe76c Mon Sep 17 00:00:00 2001 From: Amaury Forgeot d'Arc Date: Tue, 25 Jan 2022 17:08:52 +0100 Subject: [PATCH 1/6] Function pybind11_select_caster uses ADL to select the caster. - There is no ODR violation when different translation units define different conversions. - The declarations need to be defined in the original namespace. --- include/pybind11/cast.h | 44 ++++++++++++++++++++++++++++++++--------- 1 file changed, 35 insertions(+), 9 deletions(-) diff --git a/include/pybind11/cast.h b/include/pybind11/cast.h index 165102443c..3c267dd61b 100644 --- a/include/pybind11/cast.h +++ b/include/pybind11/cast.h @@ -31,7 +31,10 @@ PYBIND11_NAMESPACE_BEGIN(PYBIND11_NAMESPACE) PYBIND11_NAMESPACE_BEGIN(detail) template class type_caster : public type_caster_base { }; -template using make_caster = type_caster>; + +template type_caster> pybind11_select_caster(type); + +template using make_caster = decltype(pybind11_select_caster(static_cast *>(nullptr))); // Shortcut for calling a caster's `cast_op_type` cast operator for casting a type_caster to a T template typename make_caster::template cast_op_type cast_op(make_caster &caster) { @@ -443,21 +446,41 @@ template struct string_caster { bool load_bytes(enable_if_t::value, handle>) { return false; } }; +PYBIND11_NAMESPACE_END(detail) +PYBIND11_NAMESPACE_END(PYBIND11_NAMESPACE) + +namespace std { + +// pybind11::detail::string_caster +// pybind11_select_caster(std::string*); template -struct type_caster, enable_if_t::value>> - : string_caster> {}; +pybind11::detail::enable_if_t< + pybind11::detail::is_std_char_type::value, + pybind11::detail::string_caster< + std::basic_string>> +pybind11_select_caster(std::basic_string*); #ifdef PYBIND11_HAS_STRING_VIEW +// pybind11::detail::string_caster +// pybind11_select_caster(std::string_view*); template -struct type_caster, enable_if_t::value>> - : string_caster, true> {}; +pybind11::detail::enable_if_t< + pybind11::detail::is_std_char_type::value, + pybind11::detail::string_caster< + std::basic_string_view, true>> +pybind11_select_caster(std::basic_string_view*); #endif +} // namespace std + +PYBIND11_NAMESPACE_BEGIN(PYBIND11_NAMESPACE) +PYBIND11_NAMESPACE_BEGIN(detail) + // Type caster for C-style strings. We basically use a std::string type caster, but also add the // ability to use None as a nullptr char* (which the string caster doesn't allow). template struct type_caster::value>> { using StringType = std::basic_string; - using StringCaster = type_caster; + using StringCaster = make_caster; StringCaster str_caster; bool none = false; CharT one_char = 0; @@ -863,7 +886,7 @@ template struct return_value_policy_override C++ casting; throws if casting fails -template type_caster &load_type(type_caster &conv, const handle &handle) { +template caster& load_type(caster& conv, const handle& handle) { if (!conv.load(handle, true)) { #if defined(NDEBUG) throw cast_error("Unable to cast Python instance to C++ type (compile in debug mode for details)"); @@ -874,10 +897,13 @@ template type_caster &load_type(type_ca } return conv; } +template type_caster &load_type(type_caster &conv, const handle &handle) { + return load_type>(conv, handle); +} // Wrapper around the above that also constructs and returns a type_caster template make_caster load_type(const handle &handle) { make_caster conv; - load_type(conv, handle); + load_type(conv, handle); return conv; } @@ -964,7 +990,7 @@ template using override_caster_t = conditional_t< // Trampoline use: for reference/pointer types to value-converted values, we do a value cast, then // store the result in the given variable. For other types, this is a no-op. template enable_if_t::value, T> cast_ref(object &&o, make_caster &caster) { - return cast_op(load_type(caster, o)); + return cast_op(load_type(caster, o)); } template enable_if_t::value, T> cast_ref(object &&, override_unused &) { pybind11_fail("Internal error: cast_ref fallback invoked"); } From ae4bf8eea89961f7671825bf2b92a8edd34e5fb6 Mon Sep 17 00:00:00 2001 From: Amaury Forgeot d'Arc Date: Tue, 25 Jan 2022 19:17:23 +0100 Subject: [PATCH 2/6] Add documentation.: --- docs/advanced/cast/custom.rst | 119 ++++++++++++++++++++-------------- 1 file changed, 69 insertions(+), 50 deletions(-) diff --git a/docs/advanced/cast/custom.rst b/docs/advanced/cast/custom.rst index 1df4d3e14b..7e4bcabb6b 100644 --- a/docs/advanced/cast/custom.rst +++ b/docs/advanced/cast/custom.rst @@ -31,63 +31,82 @@ The following Python snippet demonstrates the intended usage from the Python sid print(A()) -To register the necessary conversion routines, it is necessary to add an -instantiation of the ``pybind11::detail::type_caster`` template. -Although this is an implementation detail, adding an instantiation of this -type is explicitly allowed. +To register the necessary conversion routines, it is necessary to define a +`type_caster` class, and register it with the other pybind11 casters: .. code-block:: cpp - namespace pybind11 { namespace detail { - template <> struct type_caster { - public: - /** - * This macro establishes the name 'inty' in - * function signatures and declares a local variable - * 'value' of type inty - */ - PYBIND11_TYPE_CASTER(inty, const_name("inty")); - - /** - * Conversion part 1 (Python->C++): convert a PyObject into a inty - * instance or return false upon failure. The second argument - * indicates whether implicit conversions should be applied. - */ - bool load(handle src, bool) { - /* Extract PyObject from handle */ - PyObject *source = src.ptr(); - /* Try converting into a Python integer value */ - PyObject *tmp = PyNumber_Long(source); - if (!tmp) - return false; - /* Now try to convert into a C++ int */ - value.long_value = PyLong_AsLong(tmp); - Py_DECREF(tmp); - /* Ensure return code was OK (to avoid out-of-range errors etc) */ - return !(value.long_value == -1 && !PyErr_Occurred()); - } - - /** - * Conversion part 2 (C++ -> Python): convert an inty instance into - * a Python object. The second and third arguments are used to - * indicate the return value policy and parent object (for - * ``return_value_policy::reference_internal``) and are generally - * ignored by implicit casters. - */ - static handle cast(inty src, return_value_policy /* policy */, handle /* parent */) { - return PyLong_FromLong(src.long_value); - } - }; - }} // namespace pybind11::detail + struct inty_type_caster { + public: + /** + * This macro establishes the name 'inty' in + * function signatures and declares a local variable + * 'value' of type inty + */ + PYBIND11_TYPE_CASTER(inty, const_name("inty")); + + /** + * Conversion part 1 (Python->C++): convert a PyObject into a inty + * instance or return false upon failure. The second argument + * indicates whether implicit conversions should be applied. + */ + bool load(handle src, bool) { + /* Extract PyObject from handle */ + PyObject *source = src.ptr(); + /* Try converting into a Python integer value */ + PyObject *tmp = PyNumber_Long(source); + if (!tmp) + return false; + /* Now try to convert into a C++ int */ + value.long_value = PyLong_AsLong(tmp); + Py_DECREF(tmp); + /* Ensure return code was OK (to avoid out-of-range errors etc) */ + return !(value.long_value == -1 && !PyErr_Occurred()); + } + + /** + * Conversion part 2 (C++ -> Python): convert an inty instance into + * a Python object. The second and third arguments are used to + * indicate the return value policy and parent object (for + * ``return_value_policy::reference_internal``) and are generally + * ignored by implicit casters. + */ + static handle cast(inty src, return_value_policy /* policy */, handle /* parent */) { + return PyLong_FromLong(src.long_value); + } + }; .. note:: - A ``type_caster`` defined with ``PYBIND11_TYPE_CASTER(T, ...)`` requires + A `type_caster` defined with ``PYBIND11_TYPE_CASTER(T, ...)`` requires that ``T`` is default-constructible (``value`` is first default constructed and then ``load()`` assigns to it). -.. warning:: +The `type_caster` defined above must be registered with pybind11. +There are two ways to do this: - When using custom type casters, it's important to declare them consistently - in every compilation unit of the Python extension module. Otherwise, - undefined behavior can ensue. +* As an instantiation of the ``pybind11::detail::type_caster`` template. + Although this is an implementation detail, adding an instantiation of this + type is explicitly allowed: + + .. code-block:: cpp + + namespace pybind11 { namespace detail { + template <> struct type_caster : inty_type_caster {}; + }} // namespace pybind11::detail + + .. warning:: + + When using this method, it's important to declare them consistently + in every compilation unit of the Python extension module. Otherwise, + undefined behavior can ensue. + +* The preferred method is to *declare* a function named `pybind11_declare_caster`, + its only purpose is to associate the C++ type with its caster class: + + .. code-block:: cpp + + inty_type_caster pybind11_declare_caster(inty*); + + The argument is a *pointer* to the C++ type, the return type is the + `caster_type`. This function has no implementation! From fb6f5d18b9ddde58d7b93ad9f55070b2a23d44f6 Mon Sep 17 00:00:00 2001 From: Amaury Forgeot d'Arc Date: Tue, 25 Jan 2022 19:19:52 +0100 Subject: [PATCH 3/6] Use correct function name in docs. --- docs/advanced/cast/custom.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/advanced/cast/custom.rst b/docs/advanced/cast/custom.rst index 7e4bcabb6b..028b10baa9 100644 --- a/docs/advanced/cast/custom.rst +++ b/docs/advanced/cast/custom.rst @@ -101,12 +101,12 @@ There are two ways to do this: in every compilation unit of the Python extension module. Otherwise, undefined behavior can ensue. -* The preferred method is to *declare* a function named `pybind11_declare_caster`, +* The preferred method is to *declare* a function named `pybind11_select_caster`, its only purpose is to associate the C++ type with its caster class: .. code-block:: cpp - inty_type_caster pybind11_declare_caster(inty*); + inty_type_caster pybind11_select_caster(inty*); The argument is a *pointer* to the C++ type, the return type is the `caster_type`. This function has no implementation! From bfac107d7a2791af888bb2ffe1eaff604d3dab7f Mon Sep 17 00:00:00 2001 From: Amaury Forgeot d'Arc Date: Tue, 25 Jan 2022 23:39:34 +0100 Subject: [PATCH 4/6] Attempt to fix compilation error on MSVC 2015 "explicit specialization 'void pybind11::detail::cast_safe(pybind11::object &&)' is not a specialization of a function template" --- include/pybind11/cast.h | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/include/pybind11/cast.h b/include/pybind11/cast.h index 3c267dd61b..d65cca1ec7 100644 --- a/include/pybind11/cast.h +++ b/include/pybind11/cast.h @@ -998,11 +998,16 @@ template enable_if_t::value, // Trampoline use: Having a pybind11::cast with an invalid reference type is going to static_assert, even // though if it's in dead code, so we provide a "trampoline" to pybind11::cast that only does anything in // cases where pybind11::cast is valid. -template enable_if_t::value, T> cast_safe(object &&o) { - return pybind11::cast(std::move(o)); } -template enable_if_t::value, T> cast_safe(object &&) { - pybind11_fail("Internal error: cast_safe fallback invoked"); } -template <> inline void cast_safe(object &&) {} +template +enable_if_t> && + !cast_is_temporary_value_reference::value, T> +cast_safe(object &&o) { return pybind11::cast(std::move(o)); } +template +enable_if_t::value, T> +cast_safe(object &&) { pybind11_fail("Internal error: cast_safe fallback invoked"); } +template +enable_if_t>, void> +cast_safe(object &&) {} PYBIND11_NAMESPACE_END(detail) From ccf6300e25da99376cd2c9be5fc7e1b89227a561 Mon Sep 17 00:00:00 2001 From: Amaury Forgeot d'Arc Date: Tue, 25 Jan 2022 23:46:10 +0100 Subject: [PATCH 5/6] Address rst conformance checks. --- docs/advanced/cast/custom.rst | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/docs/advanced/cast/custom.rst b/docs/advanced/cast/custom.rst index 028b10baa9..9dbd0f4482 100644 --- a/docs/advanced/cast/custom.rst +++ b/docs/advanced/cast/custom.rst @@ -32,7 +32,7 @@ The following Python snippet demonstrates the intended usage from the Python sid print(A()) To register the necessary conversion routines, it is necessary to define a -`type_caster` class, and register it with the other pybind11 casters: +caster class, and register it with the other pybind11 casters: .. code-block:: cpp @@ -78,12 +78,12 @@ To register the necessary conversion routines, it is necessary to define a .. note:: - A `type_caster` defined with ``PYBIND11_TYPE_CASTER(T, ...)`` requires + A caster class defined with ``PYBIND11_TYPE_CASTER(T, ...)`` requires that ``T`` is default-constructible (``value`` is first default constructed and then ``load()`` assigns to it). -The `type_caster` defined above must be registered with pybind11. -There are two ways to do this: +The caster defined above must be registered with pybind11. +There are two ways to do it: * As an instantiation of the ``pybind11::detail::type_caster`` template. Although this is an implementation detail, adding an instantiation of this @@ -101,12 +101,13 @@ There are two ways to do this: in every compilation unit of the Python extension module. Otherwise, undefined behavior can ensue. -* The preferred method is to *declare* a function named `pybind11_select_caster`, - its only purpose is to associate the C++ type with its caster class: +* The preferred method is to *declare* a function named + ``pybind11_select_caster``, its only purpose is to associate the C++ type + with its caster class: .. code-block:: cpp inty_type_caster pybind11_select_caster(inty*); The argument is a *pointer* to the C++ type, the return type is the - `caster_type`. This function has no implementation! + caster type. This function has no implementation! From e7d6c8be494cf2db7bd1e93e9c115dbecb005194 Mon Sep 17 00:00:00 2001 From: Amaury Forgeot d'Arc Date: Wed, 26 Jan 2022 00:28:08 +0100 Subject: [PATCH 6/6] Fix compilation. --- include/pybind11/cast.h | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/include/pybind11/cast.h b/include/pybind11/cast.h index d65cca1ec7..89a6542923 100644 --- a/include/pybind11/cast.h +++ b/include/pybind11/cast.h @@ -999,15 +999,15 @@ template enable_if_t::value, // though if it's in dead code, so we provide a "trampoline" to pybind11::cast that only does anything in // cases where pybind11::cast is valid. template -enable_if_t> && +enable_if_t>::value && !cast_is_temporary_value_reference::value, T> cast_safe(object &&o) { return pybind11::cast(std::move(o)); } template enable_if_t::value, T> cast_safe(object &&) { pybind11_fail("Internal error: cast_safe fallback invoked"); } template -enable_if_t>, void> -cast_safe(object &&) {} +enable_if_t>::value, void> +cast_safe(object &&) {} PYBIND11_NAMESPACE_END(detail)