diff --git a/docs/advanced/cast/custom.rst b/docs/advanced/cast/custom.rst index 1df4d3e14b..9dbd0f4482 100644 --- a/docs/advanced/cast/custom.rst +++ b/docs/advanced/cast/custom.rst @@ -31,63 +31,83 @@ 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 +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 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). -.. warning:: +The caster defined above must be registered with pybind11. +There are two ways to do it: - 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_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! diff --git a/include/pybind11/cast.h b/include/pybind11/cast.h index 165102443c..e3615141a3 100644 --- a/include/pybind11/cast.h +++ b/include/pybind11/cast.h @@ -31,7 +31,34 @@ 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(IntrinsicType *); + +// MSVC 2015 generates an internal compiler error for the common code (in the #else branch below). +// MSVC 2017 in C++14 mode produces incorrect code, leading to a tests/test_stl runtime failure. +// Luckily, the workaround for MSVC 2015 also resolves the MSVC 2017 C++14 runtime failure. +#if defined(_MSC_VER) && (_MSC_VER < 1910 || (_MSC_VER < 1920 && !defined(PYBIND11_CPP17))) + +template +struct make_caster_impl; + +template +struct make_caster_impl::value>> +: type_caster {}; + +template +struct make_caster_impl::value>> +: decltype(pybind11_select_caster(static_cast(nullptr))) {}; + +template +using make_caster = make_caster_impl>; + +#else + +template +using make_caster = decltype(pybind11_select_caster(static_cast *>(nullptr))); + +#endif // 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) { @@ -457,7 +484,7 @@ struct type_caster, enable_if_t 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 +890,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 +901,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 +994,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"); } @@ -972,11 +1002,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>::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>::value, void> +cast_safe(object &&) {} PYBIND11_NAMESPACE_END(detail) diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 9040cf8c06..2ff03ff839 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -137,6 +137,8 @@ set(PYBIND11_TEST_FILES test_iostream test_kwargs_and_defaults test_local_bindings + test_make_caster_adl + test_make_caster_adl_alt test_methods_and_attributes test_modules test_multiple_inheritance diff --git a/tests/test_make_caster_adl.cpp b/tests/test_make_caster_adl.cpp new file mode 100644 index 0000000000..0fe66b62c9 --- /dev/null +++ b/tests/test_make_caster_adl.cpp @@ -0,0 +1,89 @@ +// adl = Argument Dependent Lookup + +#include "pybind11_tests.h" + +namespace have_a_ns { +struct type_mock {}; +struct mock_caster { + static int num() { return 101; } +}; +mock_caster pybind11_select_caster(type_mock *); +} // namespace have_a_ns + +// namespace global { +struct global_ns_type_mock {}; +struct global_ns_mock_caster { + static int num() { return 202; } +}; +global_ns_mock_caster pybind11_select_caster(global_ns_type_mock *); +// } // namespace global + +namespace { +struct unnamed_ns_type_mock {}; +struct unnamed_ns_mock_caster { + static int num() { return 303; } +}; +unnamed_ns_mock_caster pybind11_select_caster(unnamed_ns_type_mock *); +#if !defined(_MSC_VER) +// Dummy implementation, purely to avoid compiler warnings (unused function). +// Easier than managing compiler-specific pragmas for warning suppression. +// MSVC happens to not generate any warnings. Leveraging that to prove that +// this test actually works without an implementation. +unnamed_ns_mock_caster pybind11_select_caster(unnamed_ns_type_mock *) { + return unnamed_ns_mock_caster{}; +} +#endif +} // namespace + +namespace mrc_ns { // minimal real caster + +struct type_mrc { + int value = -9999; +}; + +struct minimal_real_caster { + static constexpr auto name = py::detail::const_name(); + + static py::handle + cast(type_mrc const &src, py::return_value_policy /*policy*/, py::handle /*parent*/) { + return py::int_(src.value + 1000).release(); + } + + // Maximizing simplicity. This will go terribly wrong for other arg types. + template + using cast_op_type = const type_mrc &; + + // NOLINTNEXTLINE(google-explicit-constructor) + operator type_mrc const &() { + static type_mrc obj; + obj.value = 404; + return obj; + } + + bool load(py::handle src, bool /*convert*/) { + // Only accepts str, but the value is ignored. + return py::isinstance(src); + } +}; + +minimal_real_caster pybind11_select_caster(type_mrc *); + +} // namespace mrc_ns + +TEST_SUBMODULE(make_caster_adl, m) { + m.def("have_a_ns_num", []() { return py::detail::make_caster::num(); }); + m.def("global_ns_num", []() { return py::detail::make_caster::num(); }); + m.def("unnamed_ns_num", []() { return py::detail::make_caster::num(); }); + + m.def("mrc_return", []() { + mrc_ns::type_mrc obj; + obj.value = 505; + return obj; + }); + m.def("mrc_arg", [](mrc_ns::type_mrc const &obj) { return obj.value + 2000; }); + +#if !defined(_MSC_VER) + // Dummy call, purely to avoid compiler warnings (unused function). + (void) pybind11_select_caster(static_cast(nullptr)); +#endif +} diff --git a/tests/test_make_caster_adl.py b/tests/test_make_caster_adl.py new file mode 100644 index 0000000000..d420607cf0 --- /dev/null +++ b/tests/test_make_caster_adl.py @@ -0,0 +1,23 @@ +# -*- coding: utf-8 -*- +import pytest + +from pybind11_tests import make_caster_adl as m +from pybind11_tests import make_caster_adl_alt as m_alt + + +def test_mock_casters(): + assert m.have_a_ns_num() == 101 + assert m.global_ns_num() == 202 + assert m.unnamed_ns_num() == 303 + + +def test_mock_casters_alt(): + assert m_alt.have_a_ns_num() == 121 + + +def test_minimal_real_caster(): + assert m.mrc_return() == 1505 + assert m.mrc_arg(u"ignored") == 2404 + with pytest.raises(TypeError) as excinfo: + m.mrc_arg(None) + assert "(arg0: mrc_ns::type_mrc) -> int" in str(excinfo.value) diff --git a/tests/test_make_caster_adl_alt.cpp b/tests/test_make_caster_adl_alt.cpp new file mode 100644 index 0000000000..1feb5be50e --- /dev/null +++ b/tests/test_make_caster_adl_alt.cpp @@ -0,0 +1,13 @@ +#include "pybind11_tests.h" + +namespace have_a_ns { +struct type_mock {}; +struct mock_caster_alt { + static int num() { return 121; } +}; +mock_caster_alt pybind11_select_caster(type_mock *); +} // namespace have_a_ns + +TEST_SUBMODULE(make_caster_adl_alt, m) { + m.def("have_a_ns_num", []() { return py::detail::make_caster::num(); }); +} diff --git a/tests/test_make_caster_adl_alt.py b/tests/test_make_caster_adl_alt.py new file mode 100644 index 0000000000..d66093e589 --- /dev/null +++ b/tests/test_make_caster_adl_alt.py @@ -0,0 +1,7 @@ +# -*- coding: utf-8 -*- + +from pybind11_tests import make_caster_adl_alt as m + + +def test_mock_casters(): + assert m.have_a_ns_num() == 121