Skip to content

Function pybind11_select_caster uses ADL to select the caster. #3643

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 7 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
120 changes: 70 additions & 50 deletions docs/advanced/cast/custom.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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<T>`` 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<inty> {
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<T>`` 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<T>`` 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> : 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!
59 changes: 45 additions & 14 deletions include/pybind11/cast.h
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,10 @@ PYBIND11_NAMESPACE_BEGIN(PYBIND11_NAMESPACE)
PYBIND11_NAMESPACE_BEGIN(detail)

template <typename type, typename SFINAE = void> class type_caster : public type_caster_base<type> { };
template <typename type> using make_caster = type_caster<intrinsic_t<type>>;

template <class type> type_caster<intrinsic_t<type>> pybind11_select_caster(type);

template <class type> using make_caster = decltype(pybind11_select_caster(static_cast<intrinsic_t<type> *>(nullptr)));

// Shortcut for calling a caster's `cast_op_type` cast operator for casting a type_caster to a T
template <typename T> typename make_caster<T>::template cast_op_type<T> cast_op(make_caster<T> &caster) {
Expand Down Expand Up @@ -443,21 +446,41 @@ template <typename StringType, bool IsView = false> struct string_caster {
bool load_bytes(enable_if_t<!std::is_same<C, char>::value, handle>) { return false; }
};

PYBIND11_NAMESPACE_END(detail)
PYBIND11_NAMESPACE_END(PYBIND11_NAMESPACE)

namespace std {

// pybind11::detail::string_caster<std::string>
// pybind11_select_caster(std::string*);
template <typename CharT, class Traits, class Allocator>
struct type_caster<std::basic_string<CharT, Traits, Allocator>, enable_if_t<is_std_char_type<CharT>::value>>
: string_caster<std::basic_string<CharT, Traits, Allocator>> {};
pybind11::detail::enable_if_t<
pybind11::detail::is_std_char_type<CharT>::value,
pybind11::detail::string_caster<
std::basic_string<CharT, Traits, Allocator>>>
pybind11_select_caster(std::basic_string<CharT, Traits, Allocator>*);

#ifdef PYBIND11_HAS_STRING_VIEW
// pybind11::detail::string_caster<std::string_view, true>
// pybind11_select_caster(std::string_view*);
template <typename CharT, class Traits>
struct type_caster<std::basic_string_view<CharT, Traits>, enable_if_t<is_std_char_type<CharT>::value>>
: string_caster<std::basic_string_view<CharT, Traits>, true> {};
pybind11::detail::enable_if_t<
pybind11::detail::is_std_char_type<CharT>::value,
pybind11::detail::string_caster<
std::basic_string_view<CharT, Traits>, true>>
pybind11_select_caster(std::basic_string_view<CharT, Traits>*);
#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 <typename CharT> struct type_caster<CharT, enable_if_t<is_std_char_type<CharT>::value>> {
using StringType = std::basic_string<CharT>;
using StringCaster = type_caster<StringType>;
using StringCaster = make_caster<StringType>;
StringCaster str_caster;
bool none = false;
CharT one_char = 0;
Expand Down Expand Up @@ -863,7 +886,7 @@ template <typename Return> struct return_value_policy_override<Return,
};

// Basic python -> C++ casting; throws if casting fails
template <typename T, typename SFINAE> type_caster<T, SFINAE> &load_type(type_caster<T, SFINAE> &conv, const handle &handle) {
template <typename T, typename caster> 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)");
Expand All @@ -874,10 +897,13 @@ template <typename T, typename SFINAE> type_caster<T, SFINAE> &load_type(type_ca
}
return conv;
}
template <typename T, typename SFINAE> type_caster<T, SFINAE> &load_type(type_caster<T, SFINAE> &conv, const handle &handle) {
return load_type<T, type_caster<T, SFINAE>>(conv, handle);
}
// Wrapper around the above that also constructs and returns a type_caster
template <typename T> make_caster<T> load_type(const handle &handle) {
make_caster<T> conv;
load_type(conv, handle);
load_type<T>(conv, handle);
return conv;
}

Expand Down Expand Up @@ -964,19 +990,24 @@ template <typename ret_type> 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 <typename T> enable_if_t<cast_is_temporary_value_reference<T>::value, T> cast_ref(object &&o, make_caster<T> &caster) {
return cast_op<T>(load_type(caster, o));
return cast_op<T>(load_type<T>(caster, o));
}
template <typename T> enable_if_t<!cast_is_temporary_value_reference<T>::value, T> cast_ref(object &&, override_unused &) {
pybind11_fail("Internal error: cast_ref fallback invoked"); }

// 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 <typename T> enable_if_t<!cast_is_temporary_value_reference<T>::value, T> cast_safe(object &&o) {
return pybind11::cast<T>(std::move(o)); }
template <typename T> enable_if_t<cast_is_temporary_value_reference<T>::value, T> cast_safe(object &&) {
pybind11_fail("Internal error: cast_safe fallback invoked"); }
template <> inline void cast_safe<void>(object &&) {}
template <typename T>
enable_if_t<!std::is_same<void, intrinsic_t<T>>::value &&
!cast_is_temporary_value_reference<T>::value, T>
cast_safe(object &&o) { return pybind11::cast<T>(std::move(o)); }
template <typename T>
enable_if_t<cast_is_temporary_value_reference<T>::value, T>
cast_safe(object &&) { pybind11_fail("Internal error: cast_safe fallback invoked"); }
template <typename T>
enable_if_t<std::is_same<void, intrinsic_t<T>>::value, void>
cast_safe(object &&) {}

PYBIND11_NAMESPACE_END(detail)

Expand Down