I have the following structures:
enum class Tag
{
QR,
April,
Chili
}
enum class Constraint
{
Tag,
GPS,
User
}
struct Options
{
boost::optional<std::list<Tag>> tags;
boost::optional<std::list<Constraint>> constraints;
}
I want to expose them in a Python package, using boost python. I wrote custom code to convert the std::list and boost::optionalstd::list to/from Python:
template<typename T>
struct pylist_converter
{
static void* convertible(PyObject* object)
{
if (!PyList_Check(object)) {
return nullptr;
}
size_t sz = PySequence_Size(object);
for (size_t i = 0; i < sz; ++i)
{
if (!boost::python::extract<T>::extract(PyList_GetItem(object, i)))
return nullptr;
}
return object;
}
static void construct(PyObject* object, boost::python::converter::rvalue_from_python_stage1_data* data)
{
typedef boost::python::converter::rvalue_from_python_storage<std::list<T>> storage_type;
void* storage = reinterpret_cast<storage_type*>(data)->storage.bytes;
data->convertible = new (storage) std::list<T>();
std::list<T>* l = (std::list<T>*)(storage);
size_t sz = PySequence_Size(object);
for (size_t i = 0; i < sz; ++i)
{
l->push_back(boost::python::extract<T>(PyList_GetItem(object, i)));
}
}
pylist_converter()
{
boost::python::converter::registry::push_back(
&convertible,
&construct,
boost::python::type_id<std::list<T>>());
}
};
template<typename T>
struct from_python_optional_list
{
static void* convertible(PyObject* obj_ptr)
{
if (obj_ptr == object().ptr()) // = None
return 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<T>::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)
{
if (obj_ptr == object().ptr()) // = None
{
void* storage = (
(boost::python::converter::rvalue_from_python_storage<boost::optional<std::list<T>> >*)
data)->storage.bytes;
new (storage) boost::optional<std::list<T>>(boost::none);
data->convertible = storage;
return;
}
std::list<T> l;
size_t sz = PySequence_Size(obj_ptr);
for (size_t i = 0; i < sz; ++i)
{
l.push_back(boost::python::extract<T>(PyList_GetItem(obj_ptr, i)));
}
assert(value);
void* storage = (
(boost::python::converter::rvalue_from_python_storage<boost::optional<std::list<T>> >*)
data)->storage.bytes;
new (storage) boost::optional<std::list<T>>(l);
data->convertible = storage;
}
from_python_optional_list()
{
boost::python::converter::registry::push_back(
&convertible,
&construct,
boost::python::type_id<boost::optional<std::list<T>> >());
}
};
#define STD_LIST_PYTHON(type) \
boost::python::to_python_converter<std::list<type>, std_list_to_python<type>>(); \
pylist_converter<type>(); \
boost::python::to_python_converter<boost::optional<std::list<type>>, to_python_optional<std::list<type>> >(); \
from_python_optional_list<type>();
I've tested the above code with "regular" type (like int), it works fine.
Here's what's failing:
enum_<Tag>("Tag", "Values for Tag")
.value("QR", Tag::QR)
.value("April", Tag::April)
.value("Chili", Tag::Chili)
;
enum_<Constraint>("Constraint", "Values for Constraint")
.value("Tag", Constraint::Tag)
.value("GPS", Constraint::GPS)
.value("User", Constraint::User)
;
STD_LIST_PYTHON(Tag);
STD_LIST_PYTHON(Constraints);
This is the output I have from VS2022:
Build started...
1>------ Build started: Project: PyPackage, Configuration: Release x64 ------
1>Automatic MOC and UIC for target PyPackage
1>pypack.cpp
1>D:\dev\pypack.cpp(378,56): error C2678: binary '!': no operator found which takes a left-hand operand of type 'boost::python::extract<T>' (or there is no acceptable conversion)
1> with
1> [
1> T=Tag
1> ]
1>D:\dev\pypack.cpp(378,56): message : could be 'built-in C++ operator!(bool)'
1>D:\dev\pypack.cpp(378,56): message : while trying to match the argument list '(boost::python::extract<T>)'
1> with
1> [
1> T=Tag
1> ]
1>D:\dev\pypack.cpp(365,1): message : while compiling class template member function 'void *from_python_optional_list<Tag>::convertible(PyObject *)'
1>D:\dev\pypack.cpp(420,54): message : see reference to function template instantiation 'void *from_python_optional_list<Tag>::convertible(PyObject *)' being compiled
1>D:\dev\pypack.cpp(504,5): message : see reference to class template instantiation 'from_python_optional_list<Tag>' being compiled
1>D:\dev\pypack.cpp(378,21): error C2088: '!': illegal for struct
1>Done building project "PyPackage.vcxproj" -- FAILED.
========== Build: 0 succeeded, 1 failed, 42 up-to-date, 0 skipped ==========
========== Build started at 10:22 AM and took 09.334 seconds ==========
How can I extract the enums correctly?
Edit: I tried to read int from Python, then cast them as enum. I simplified my problem for now: forget about the optional, I want a list of enum.
template<typename E>
struct from_python_list_enum
{
static void* convertible(PyObject* obj_ptr)
{
if (!PyList_Check(obj_ptr)) { // Check with have a list
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))) // Check we can extract item as int
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)); // Cast the int as Enum
}
}
from_python_list_enum()
{
boost::python::converter::registry::push_back(
&convertible,
&construct,
boost::python::type_id<std::list<E>>());
}
};
I'm registering/exposing what's needed. It compiles, but when running this Python code:
import my_package
options = my_package.Options()
options.tags = [my_package.Tag.April]
I have the following exception:
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> >)