Skip to content

Custom Implicit Type Conversion

Target

I have define a c++ class named Data, and I want it be converted to python object implicitly and vice versa. For example:

class Data
{
    public:
        Data(int val): _val{val} {}
        int _val;
}

void py2cpp(const data& data)
{
    cout << data._val << endl;
}

Data cpp2py()
{
    return Data(41);
}


import example

example.py2cpp(42)

assert isinstance(example.cpp2py(), int)

Solution

We need to implement a type_caster to define how to convert c++ object to python object. type_caster has two methods, a cast to convert c++ to python and a load to convert python to c++. Here is an example:


// def inty
struct inty { long long_value; };

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_NAMESPACE::detail

The code is self-explanatory. In the load method, we need to manually convert handle to target c++ type and assign it to value. If the conversion is success, return true, otherwise return false. In the cast method, we need to convert c++ type to handle object and return it. The PYBIND11_TYPE_CASTER macro is used to define the name of the type caster and a local variable value of type inty.

As for test binding code, we can do like this:

PYBIND11_MODULE(example, m)
{
    py::class_<inty>(m, "inty")
        .def(py::init<int>())
        .def_property("value", &inty::_val, &inty::_val);

    m.def("py2cpp", [](const inty& data) {
        std::cout << data._val << std::endl;
    });

    m.def("cpp2py", []() {
        return inty(41);
    });
}

Be careful! When you directly initilize a inty object, you will get a python object with no convert.

>>> import example
>>> example.inty(42)
>>> <example.TypedData at 0x7f6c98aaa1b0>  # not what you want
>>> example.cpp2py()
>>> 41
>>> example.py2cpp(42)  # return nothing but print

If you want to see an actual code, you can check it out on cppdict.

It seems type_caster only take effect locally

I have tried to define a type_caster in another .cpp file and link it with others. But it seems that the type_caster only take effect in the cpp file where it is defined. Or you can define it in a header file and include it to the cpp file where you want to use it.

Reference

[1] pybind11

[2] cppdict