Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to wrap templated classes with pybind11

I've got a class structure similar to the one below, where I have two types A and B with similar signatures that only differ in argument/return type. I then use a class template to be able to process both classes, achieving a static variant of ducktyping in Python. Now I want to wrap this code into Python with pybind11, where I hope to get a class that takes both types using standard, dynamic ducktyping. How do I do this?

I'm basically looking for a way to disable the strict type check in pybind11 or specify multiple types such that both TypeA and TypeB are accepted. The way the example below is defined, only TypeA passes the check. I also can't unify A and B into a base class because of the different function signatures.

#include <pybind11/pybind11.h>
#include <iostream>

class TypeA {
public:
    typedef double InType;
    typedef std::string OutType;
    const OutType operator()(const InType arg)
    {
        return std::to_string(arg);
    }
};

class TypeB {
public:
    typedef std::string InType;
    typedef double OutType;
    const OutType operator()(const InType arg)
    {
        return std::stod(arg);
    }
};

template<typename T>
class DuckType {
public:
    void runType(const typename T::InType arg)
    {
        T x;
        const typename T::OutType y = x(arg);
        std::cout << y << std::endl;
    }
};

namespace py = pybind11;

PYBIND11_PLUGIN(ducktyping) {

    pybind11::module m("ducktyping", "Testing ducktyping with templates");

    typedef DuckType<TypeA> Type;
    py::class_<Type>(m, "DuckType")
    .def("run_type", &Type::runType);
    ;
    return m.ptr();
}
like image 707
Johan Avatar asked Jul 05 '17 11:07

Johan


1 Answers

If the function you're binding has arguments that can "drive" inference in C++, you could potentially use them to drive the overload resolution in pybind11: https://pybind11.readthedocs.io/en/stable/advanced/functions.html#overload-resolution-order

For example, you can bind your instantiations as overloads, and let pybind11 figger it out:

// Let's say `DuckType::runType` is static.
m.def("runType", &DuckType<TypeA>::runType, py::arg("arg"));
m.def("runType", &DuckType<TypeB>::runType, py::arg("arg"));

Then the dispatch will work in Python, assuming that users can instantiate those types, and the InTypes are defined such that they won't accidentally intercept another class's type:

runType(arg=1.2)  # TypeA
runType(arg="hello world")  # TypeB
like image 92
eacousineau Avatar answered Nov 05 '22 20:11

eacousineau