how to use template "inheritance" to avoid boilerplate code when binding C++ to python with pybind11

Viewed 91

I managed to bind template definitions doing

 py::class_<T<a>>(m, "Ta")
 py::class_<T<b>>(m, "Tb")
 ...

where T is the template class and a and b typenames defining the template. But the problem I have tells me that this is not the really good way to expose these template definitions, because now I am forced to duplicate code for all the functions and parameters of the template class T:

 py::class_<T<a>>(m, "Ta")
   .def(py::init<string, string>())
   .def("do_smthg", &T<a>::do_sthg)
   ...;
 py::class_<T<b>>(m, "Tb")
   .def(py::init<string, string>())
   .def("do_smthg", &T<b>::do_sthg)
   ...;

when class T itself for instance has do_smthg, that I would then expect to have to define only once. I tried to browse the doc and the web, but I failed to find the right pointer.

1 Answers

You may just create a simple helper function that instantiates a list of templates, like following:

template <typename x, typename ... Tail, typename ModT>
void ApplyTemplate(ModT & m, std::vector<std::string> names) {
    py::class_<T<x>>(m, names.at(0))
        .def(py::init<string, string>())
        .def("do_smthg", &T<x>::do_sthg)
    ;
    names.erase(names.begin());
    if constexpr(sizeof...(Tail) > 0)
        ApplyTemplate<Tail...>(m, names);
}

Now call this function within module context with all possible arguments to template:

PYBIND11_MODULE(example, m) {
    ApplyTemplate<a, b>(m, {"Ta", "Tb"});
}

If for some reason your T template is not available within ApplyTemplate context then you may pass it as a template <typename> typename argument:

template <template <typename> typename T, typename x, typename ... Tail, typename ModT>
void ApplyTemplate(.....

Instead of function you can also use a simple macro:

PYBIND11_MODULE(example, m) {
    // Define macro
    #define MY_CLASS(param, name) \
        py::class_<T<param>>(m, name) \
            .def(py::init<string, string>()) \
            .def("do_smthg", &T<param>::do_sthg) \
        ;
    
    // Use macro
    MY_CLASS(a, "Ta");
    MY_CLASS(b, "Tb");

    // Remove macro    
    #undef MY_CLASS
}
Related