Boost Python暴露含可选枚举列表的C++结构遇编译及运行异常
Boost Python枚举类与std::list/boost::optional互转问题解决
问题背景
C++定义
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; };
编译错误
尝试自定义转换器时出现编译错误:
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 ==========
运行时错误
改用提取int再强转的方案后编译通过,但Python运行时出现类型不匹配:
Python代码:
import my_package options = my_package.Options() options.tags = [my_package.Tag.April]
异常信息:
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> >)
解决方法
1. 正确暴露枚举类到Python
使用BOOST_PYTHON_ENUM_CLASS注册枚举,让Boost Python能识别Python中的枚举实例:
#include <boost/python/enum.hpp> namespace bp = boost::python; // 注册Tag枚举 BOOST_PYTHON_ENUM_CLASS(Tag, Tag) .value("QR", Tag::QR) .value("April", Tag::April) .value("Chili", Tag::Chili); // 注册Constraint枚举 BOOST_PYTHON_ENUM_CLASS(Constraint, Constraint) .value("Tag", Constraint::Tag) .value("GPS", Constraint::GPS) .value("User", Constraint::User);
2. 为std::list<枚举类型>编写转换器
实现从Python列表到C++std::list<Tag>的双向转换:
#include <boost/python/list.hpp> #include <boost/python/converter.hpp> struct ListTagConverter { // C++ list转Python列表 static PyObject* convert(const std::list<Tag>& lst) { bp::list py_list; for (const auto& tag : lst) { py_list.append(bp::object(tag)); } return bp::incref(py_list.ptr()); } // 检查Python对象是否可转换为std::list<Tag> static void* convertible(PyObject* obj_ptr) { if (!PyList_Check(obj_ptr)) return nullptr; bp::list py_list(bp::handle<>(bp::borrowed(obj_ptr))); for (int i = 0; i < bp::len(py_list); ++i) { if (!bp::extract<Tag>(py_list[i]).check()) { return nullptr; } } return obj_ptr; } // 构造C++ std::list<Tag>对象 static void construct(PyObject* obj_ptr, bp::converter::rvalue_from_python_stage1_data* data) { bp::list py_list(bp::handle<>(bp::borrowed(obj_ptr))); auto storage = reinterpret_cast<bp::converter::rvalue_from_python_storage<std::list<Tag>>*>(data)->storage.bytes; // 原地构造std::list new (storage) std::list<Tag>(); auto lst = static_cast<std::list<Tag>*>(storage); for (int i = 0; i < bp::len(py_list); ++i) { lst->push_back(bp::extract<Tag>(py_list[i])); } data->convertible = storage; } };
注册该转换器:
// 注册到Python的转换 bp::to_python_converter<std::list<Tag>, ListTagConverter>(); // 注册从Python到C++的转换 bp::converter::registry::push_back( &ListTagConverter::convertible, &ListTagConverter::construct, bp::type_id<std::list<Tag>>() );
3. 为boost::optional<std::list<枚举>>编写转换器
处理可选列表的转换,支持Python的None值:
#include <boost/optional.hpp> struct OptionalListTagConverter { // C++ optional list转Python对象(None或列表) static PyObject* convert(const boost::optional<std::list<Tag>>& opt_lst) { if (!opt_lst) { return bp::incref(Py_None); } return ListTagConverter::convert(*opt_lst); } // 检查Python对象是否可转换为optional<list<Tag>> static void* convertible(PyObject* obj_ptr) { if (obj_ptr == Py_None) return obj_ptr; return ListTagConverter::convertible(obj_ptr); } // 构造C++ optional<list<Tag>>对象 static void construct(PyObject* obj_ptr, bp::converter::rvalue_from_python_stage1_data* data) { auto storage = reinterpret_cast<bp::converter::rvalue_from_python_storage<boost::optional<std::list<Tag>>>*>(data)->storage.bytes; if (obj_ptr == Py_None) { // 构造空的optional new (storage) boost::optional<std::list<Tag>>(); data->convertible = storage; return; } // 先构造std::list<Tag>,再包装到optional中 std::list<Tag> lst; bp::converter::rvalue_from_python_stage1_data sub_data{}; sub_data.convertible = &lst; ListTagConverter::construct(obj_ptr, &sub_data); new (storage) boost::optional<std::list<Tag>>(std::move(lst)); data->convertible = storage; } };
注册该转换器:
bp::to_python_converter<boost::optional<std::list<Tag>>, OptionalListTagConverter>(); bp::converter::registry::push_back( &OptionalListTagConverter::convertible, &OptionalListTagConverter::construct, bp::type_id<boost::optional<std::list<Tag>>>() );
4. 暴露Options结构体
最后将Options暴露给Python:
bp::class_<Options>("Options") .def_readwrite("tags", &Options::tags) .def_readwrite("constraints", &Options::constraints);
关键注意事项
- 编译错误原因:
boost::python::extract<T>没有重载!运算符,需使用.check()方法检查是否可提取目标类型。 - 运行时错误原因:未为
std::list<Tag>注册转换器,Boost Python无法自动完成Python列表到C++枚举列表的转换。 - 对
Constraint类型的处理可复用上述模板,只需将Tag替换为Constraint即可。
内容的提问来源于stack exchange,提问作者Cyril
相关产品推荐
相关产品推荐

