I have the following struct:
enum class Tag
{
QR,
April,
Chili
}
struct Options
{
std::list<Tag> tags;
}
that I want to expose in a Python package using boost python.
Here's my code for converting list from/to python:
// Convert std list to python list
template<typename T>
struct std_list_to_python
{
static PyObject* convert(std::list<T> const& l)
{
boost::python::list result;
for (auto const& value : l)
{
result.append(value);
}
return boost::python::incref(result.ptr());
}
};
// Convert Python list of enum to std::list of enum
template<typename E>
struct from_python_list_enum
{
static void* convertible(PyObject* obj_ptr)
{
if (!PyList_Check(obj_ptr)) {
return nullptr;
}
size_t sz = PySequence_Size(obj_ptr);
for (size_t i = 0; i < sz; ++i)
{
if (!boost::python::extract<int>::extract(PyList_GetItem(obj_ptr, i)))
return nullptr;
}
return obj_ptr;
}
static void construct(PyObject* obj_ptr, boost::python::converter::rvalue_from_python_stage1_data* data)
{
typedef boost::python::converter::rvalue_from_python_storage<std::list<E>> storage_type;
void* storage = reinterpret_cast<storage_type*>(data)->storage.bytes;
data->convertible = new (storage) std::list<E>();
std::list<E>* l = (std::list<E>*)(storage);
size_t sz = PySequence_Size(obj_ptr);
for (size_t i = 0; i < sz; ++i)
{
int v = typename boost::python::extract<int>::extract(PyList_GetItem(obj_ptr, i));
l->push_back(static_cast<E>(v));
}
}
from_python_list_enum()
{
boost::python::converter::registry::push_back(
&convertible,
&construct,
boost::python::type_id<std::list<E>>());
}
};
Here's the code for exporting the classes:
void export_test()
{
enum_<Tag>("Tag", "Values for Tag")
.value("QR", Tag::QR)
.value("April", Tag::April)
.value("Chili", Tag::Chili)
;
boost::python::to_python_converter<std::list<Tag>, std_list_to_python<Tag>>();
from_python_list_enum<Tag>();
class_<Options>("Options", "Struct with a list of enum")
.add_property("tags", make_getter(&Options::tags, return_value_policy<return_by_value>()), make_setter(&Options::tags, return_value_policy<return_by_value>()), "Tags")
;
}
Compilation works fine. However, upon running the following Python code:
import my_package
options = my_package.Options()
options.tags = [my_package.Tag.April, my_package.Tag.QR]
I have the following exception on the last line:
Boost.Python.ArgumentError
Python argument types in
None.None(Options, list)
did not match C++ signature:
None(struct Options{lvalue}, class std::list<enum Tag,class std::allocator<enum Tag> >)
What can I do to make this work?
Note: I'm able to wrap list of integers without issues with a very similar code. I have also tested that the cast from int to enum works in another scenario (boost::optional).
Your convertible
check has a bug:
if (!BP::extract<int>(PyList_GetItem(obj_ptr, i)))
return nullptr;
does not check that an int can be extracted, but rather that the integer value returned is true
-is when contextually converted to bool. You can witness this by omitting the QR
value from the list, e.g.
print([my_package.Tag.April, my_package.Tag.QR])
options.tags = [my_package.Tag.April, my_package.Tag.Chili]
print(options.tags)
print("\nNow the trouble starts:\n--")
options.tags = [my_package.Tag.April, my_package.Tag.QR]
Gives the following output:
[my_package.Tag.April, my_package.Tag.QR]
[my_package.Tag.April, my_package.Tag.Chili]
Now the trouble starts:
--
Traceback (most recent call last):
File "/home/sehe/Projects/stackoverflow/./test.py", line 12, in <module>
options.tags = [my_package.Tag.April, my_package.Tag.QR]
Boost.Python.ArgumentError: Python argument types in
None.None(Options, list)
did not match C++ signature:
None(Options {lvalue}, std::__cxx11::list<Tag, std::allocator<Tag> >)
Fix it e.g. by doing
BP::extract<int> n(PyList_GetItem(obj_ptr, i));
if (!n.check())
return nullptr;
Now the output is
[my_package.Tag.April, my_package.Tag.QR]
[my_package.Tag.April, my_package.Tag.Chili]
Now the trouble starts:
--
[my_package.Tag.April, my_package.Tag.QR]