Skip to content
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

Add pybind11_select_caster #3862

Draft
wants to merge 16 commits into
base: master
Choose a base branch
from
Draft
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
183 changes: 115 additions & 68 deletions docs/advanced/cast/custom.rst
Original file line number Diff line number Diff line change
@@ -1,93 +1,140 @@
Custom type casters
===================

In very rare cases, applications may require custom type casters that cannot be
expressed using the abstractions provided by pybind11, thus requiring raw
Python C API calls. This is fairly advanced usage and should only be pursued by
experts who are familiar with the intricacies of Python reference counting.
Some applications may prefer custom type casters that convert between existing
Python types and C++ types, similar to the ``list`` ↔ ``std::vector``
and ``dict`` ↔ ``std::map`` conversions which are built into pybind11.
Implementing custom type casters is fairly advanced usage and requires
familiarity with the intricacies of the Python C API.

The following snippets demonstrate how this works for a very simple ``inty``
type that that should be convertible from Python types that provide a
``__int__(self)`` method.
type that we want to be convertible to C++ from any Python type that provides
an ``__int__`` method, and is converted to a Python ``int`` when returned from
C++ to Python.

.. code-block:: cpp

struct inty { long long_value; };
..
PLEASE KEEP THE CODE BLOCKS IN SYNC WITH
tests/test_docs_advanced_cast_custom.cpp
Ideally, change the test, run pre-commit (incl. clang-format),
then copy the changed code back here.

void print(inty s) {
std::cout << s.long_value << std::endl;
}
.. code-block:: cpp

The following Python snippet demonstrates the intended usage from the Python side:
namespace user_space {

.. code-block:: python
struct inty {
long long_value;
};

class A:
def __int__(self):
return 123
std::string to_string(const inty &s) { return std::to_string(s.long_value); }

inty return_42() { return inty{42}; }

from example import print
} // namespace user_space

print(A())
The necessary conversion routines are defined by a caster class, which
is then "plugged into" pybind11 using one of two alternative mechanisms.

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.
Starting with the example caster class:

.. 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());
namespace user_space {

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, pybind11::detail::const_name("inty"));

// Python -> C++: convert a PyObject into an inty instance or return false upon failure. The
// second argument indicates whether implicit conversions should be allowed.
bool load(pybind11::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;
}

/**
* 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);
// Now try to convert into a C++ int.
value.long_value = PyLong_AsLong(tmp);
Py_DECREF(tmp);
// Ensure PyLong_AsLong succeeded (to catch out-of-range errors etc).
if (PyErr_Occurred()) {
PyErr_Clear();
return false;
}
};
}} // namespace pybind11::detail
return true;
}

// 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 often ignored by custom casters.
static pybind11::handle
cast(inty src, pybind11::return_value_policy /* policy */, pybind11::handle /* parent */) {
return PyLong_FromLong(src.long_value);
}
};

} // namespace user_space

.. note::

A ``type_caster<T>`` defined with ``PYBIND11_TYPE_CASTER(T, ...)`` requires
A caster class using ``PYBIND11_TYPE_CASTER(T, ...)`` requires
that ``T`` is default-constructible (``value`` is first default constructed
and then ``load()`` assigns to it).
and then ``load()`` assigns to it). It is possible but more involved to define
a caster class for types that are not default-constructible.

.. warning::
The caster class defined above can be plugged into pybind11 in two ways:

* Starting with pybind11 v2.10, a new — and the recommended — alternative is to *declare* a
function named ``pybind11_select_caster``:

.. code-block:: cpp

namespace user_space {

inty_type_caster pybind11_select_caster(inty *);

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.
} // namespace user_space

The argument is a *pointer* to the C++ type, the return type is the caster type.
This function has no implementation! Its only purpose is to associate the C++ type
with its caster class. pybind11 exploits C++ Argument Dependent Lookup
(`ADL <https://en.cppreference.com/w/cpp/language/adl>`_)
to discover the association.

Note that ``pybind11_select_caster`` can alternatively be declared as a ``friend``
function of the C++ type, if that is practical and preferred:

.. code-block:: cpp

struct inty_type_caster;

struct inty {
...
friend inty_type_caster pybind11_select_caster(inty *);
};

* An older alternative is to specialize the ``pybind11::detail::type_caster<T>`` template.
Although the ``detail`` namespace is involved, adding a ``type_caster`` specialization
is explicitly allowed:

.. code-block:: cpp

namespace pybind11 {
namespace detail {
template <>
struct type_caster<user_space::inty> : user_space::inty_type_caster {};
} // namespace detail
} // namespace pybind11

.. note::
``type_caster` specializations may be full (as in this simple example) or partial.

.. warning::
With either alternative, for a given type ``T``, the ``pybind11_select_caster``
declaration or ``type_caster`` specialization must be consistent across all compilation
units of a Python extension module, to satisfy the C++ One Definition Rule
(`ODR <https://en.cppreference.com/w/cpp/language/definition>`_).
43 changes: 38 additions & 5 deletions include/pybind11/cast.h
Original file line number Diff line number Diff line change
Expand Up @@ -33,8 +33,35 @@ PYBIND11_NAMESPACE_BEGIN(detail)

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

template <typename IntrinsicType>
type_caster<IntrinsicType> 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 <typename IntrinsicType, typename SFINAE = void>
struct make_caster_impl;

template <typename IntrinsicType>
struct make_caster_impl<IntrinsicType, enable_if_t<std::is_arithmetic<IntrinsicType>::value>>
: type_caster<IntrinsicType> {};

template <typename IntrinsicType>
struct make_caster_impl<IntrinsicType, enable_if_t<!std::is_arithmetic<IntrinsicType>::value>>
: decltype(pybind11_select_caster(static_cast<IntrinsicType *>(nullptr))) {};

template <typename type>
using make_caster = make_caster_impl<intrinsic_t<type>>;

#else

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

#endif

// Shortcut for calling a caster's `cast_op_type` cast operator for casting a type_caster to a T
template <typename T>
Expand Down Expand Up @@ -1007,8 +1034,8 @@ struct return_value_policy_override<
};

// 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(PYBIND11_DETAILED_ERROR_MESSAGES)
throw cast_error("Unable to cast Python instance to C++ type (#define "
Expand All @@ -1021,11 +1048,17 @@ type_caster<T, SFINAE> &load_type(type_caster<T, SFINAE> &conv, const handle &ha
}
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 @@ -1152,7 +1185,7 @@ using override_caster_t = conditional_t<cast_is_temporary_value_reference<ret_ty
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 &&,
Expand Down
2 changes: 2 additions & 0 deletions tests/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,7 @@ set(PYBIND11_TEST_FILES
test_custom_type_casters
test_custom_type_setup
test_docstring_options
test_docs_advanced_cast_custom
test_eigen
test_enum
test_eval
Expand All @@ -147,6 +148,7 @@ set(PYBIND11_TEST_FILES
test_operator_overloading
test_pickling
test_pytypes
test_select_caster
test_sequences_and_iterators
test_smart_ptr
test_stl
Expand Down
Loading