I'm trying to mimic implicit conversion/construction of a simple struct in Python using pybind11
:
struct MyType {
double a;
double b;
};
void func(size_t foo, const MyType& bar) {
// ...
}
// ...
PYBIND11_MODULE(pymylib, module) {
// ...
py::class_< MyType >(module, "MyType")
.def(py::init< double, double >(), "a"_a, "b"_a)
.def(py::init([](const std::array< double, 2 >& ab){ return MyType({ab[0], ab[1]}); }), "ab"_a = std::array< double, 2>{ 1.0, 0.25 }) // not implicit
.def_readwrite("a", &MyType::a)
.def_readwrite("b", &MyType::b);
py::implicitly_convertible< std::array< double, 2 >, MyType >(); // conversion is not implicit
module.def("func", &func, "foo"_a, "bar"_a);
}
While in C++ one can use brace initialization {}
without having to implicitly call MyType
constructor (simplifying the syntax), I am still unable to have an implicit conversion from a list object when passing MyType as argument to func
:
obj = module.MyType(8.7, 5.6)
module.func(47, obj) # works
module.func(47, module.MyType([4.1, 7.8])) # works
module.func(47, [4.1, 7.8]) # does not work ('incompatible function arguments')
From what I understand from pybind11
documentation, the constructor declaration py::init< double, double >()
should bind brace-initialization implicitly, but it's not working as I expected:
TypeError: func(): incompatible function arguments. The following argument types are supported:
1. module.func(a: float, b: module.MyType)
I tried adding a custom constructor accepting a list/array, but it is still not called implicitly.
Due to the simplicity of the struct, having to explicitly call the constructor seems unnecessary, particularly if defined in a Python submodule or nested on another class; How - if possible - do I achieve this behavior in Python?
The documentation you are citing refers to the way the python initializer calls the C++ constructor. It makes no remark as far as I can tell regarding implicit conversion from list
. As you suspected, you need to register a special py::init()
for it.
Now regarding why your special py::init()
does not work: My best guess is you are missing #include <pybind11/stl.h>
, see https://pybind11.readthedocs.io/en/stable/advanced/cast/stl.html:
When including the additional header file
pybind11/stl.h
, conversions betweenstd::vector<>
/std::deque<>
/std::list<>
/std::array<>
/std::valarray<>
,std::set<>
/std::unordered_set<>
, andstd::map<>
/std::unordered_map<>
and the Pythonlist
,set
anddict
data structures are automatically enabled. The typesstd::pair<>
andstd::tuple<>
are already supported out of the box with just the corepybind11/pybind11.h
header.
Here is a working demo: https://godbolt.org/z/WrPrb8cM8