Boost Spirit X3 v1.69与VS2017编译兼容问题求助
问题分析
你遇到的是VS2017对Boost Spirit X3的omit组件与融合适配结构体结合时的模板实例化限制问题。GCC和Clang对模板参数包的处理更宽松,但VS2017的旧版编译器在处理超过一定数量的unused_type元素时,会触发Boost MPL的断言(比如REQUESTED_PUSH_BACK_SPECIALIZATION_FOR_SEQUENCE_DOES_NOT_EXIST)——这是因为你的解析规则生成的属性序列长度超出了VS2017兼容的默认融合vector上限,而omit[int_[item::fn_n_items]]恰好又向序列中多添加了一个unused_type,直接触发了这个限制。
替代解决方案
下面提供两种可在VS2017中正常编译的替代方案,既保留原有解析逻辑,又绕过编译器的模板限制:
方案1:使用状态传递替代全局变量(推荐)
这种方式更符合Spirit X3的设计理念,同时避免全局变量的副作用,从根源上减少属性序列的冗余元素:
#include <vector> #include <iostream> #include <boost/spirit/home/x3.hpp> #include <boost/fusion/adapted/struct.hpp> #include <boost/fusion/include/vector.hpp> namespace x3 = boost::spirit::x3; namespace definitions { enum DIRECTION { NOMINAL, REVERSE }; enum SIDE { LEFT, RIGHT }; } namespace structs { struct Door { std::string label; int start_id; double start_location; definitions::DIRECTION dir; std::vector<int> list; int end_id; double end_location; definitions::SIDE side; }; } BOOST_FUSION_ADAPT_STRUCT(structs::Door, label, start_id, start_location, dir, list, end_id, end_location, side) namespace parsers { using x3::int_; using x3::char_; using x3::double_; using x3::eol; using x3::omit; using x3::seek; using x3::skip; using x3::blank; using x3::lexeme; using x3::ascii::space; using x3::eps; using x3::attr; auto const quoted = '"' >> lexeme[*~char_('"')] >> '"'; auto const comment = -("--" >> omit[*~char_("\r\n")]) >> eol; auto const skippers = blank | '(' | ',' | ')'; struct direction_ : x3::symbols<definitions::DIRECTION> { direction_() { add("NOMINAL", definitions::NOMINAL) ("REVERSE", definitions::REVERSE); } } direction; struct side_ : x3::symbols<definitions::SIDE> { side_() { add("LEFT", definitions::LEFT) ("RIGHT", definitions::RIGHT); } } side; // 定义状态结构体保存计数信息 struct ListCountState { int n_items = 0; int n_read = 0; }; // 声明状态类型的key struct list_count_tag; using list_count_type = x3::rule<list_count_tag, std::vector<int>, decltype(skippers)>; BOOST_SPIRIT_DECLARE(list_count_type) namespace door { // 整合计数逻辑到列表解析规则中 auto const list_impl = x3::rule<struct list_impl_tag, std::vector<int>>{} = int_[([](auto& ctx){ x3::get<ListCountState>(ctx).n_items = _attr(ctx); x3::get<ListCountState>(ctx).n_read = 0; })] >> *(eps([](auto& ctx){ return x3::get<ListCountState>(ctx).n_read < x3::get<ListCountState>(ctx).n_items; }) >> *comment >> int_[([](auto& ctx){ _val(ctx).push_back(_attr(ctx)); ++x3::get<ListCountState>(ctx).n_read; })]); // 替换原规则中的omit计数行 auto const text = eol >> "[AREA]" >> *comment >> quoted >> *comment >> int_ >> *comment >> double_ >> *comment >> direction >> *comment >> list_impl >> *comment >> int_ >> *comment >> double_ >> *comment >> side; // 使用with传递状态 auto const start = x3::with<ListCountState>(ListCountState{})[skip(skippers)[*seek[text]]]; } BOOST_SPIRIT_DEFINE(list_count_type) } // namespace parsers std::string text = "\n\ [AREA]\n\ \n\ \"LABEL-1\" --Comment\n\ --Start\n\ -- Id Location(m) Dir\n\ -- --------------------------------\n\ 374 84.8 NOMINAL -- Comment\n\ \n\ -- List\n\ -- ---------------------------------------------- -\n\ 0 --There are none\n\ \n\ -- End\n\ -- Id Location(m)\n\ -- -------------------- -\n\ 374 6.7 --Comment\n\ \n\ --------------------------------------------------------------------------------\n\ RIGHT -- Doors\n"; int main() { std::vector<structs::Door> doors; const bool result = x3::parse(text.begin(), text.end(), parsers::door::start, doors); std::cout << std::boolalpha << result << "\n"; std::cout << doors.size() << "\n"; for (const auto& door : doors) std::cout << door.label << "\n"; return 0; }
方案2:简化属性序列,避免多余unused元素
如果你不想修改状态管理方式,可以把omit[int_[item::fn_n_items]]替换为不产生属性的动作规则,直接读取整数并执行逻辑,不向融合序列中添加元素:
// 在parsers::door命名空间中,替换原有的omit[int_[item::fn_n_items]]行 >> *comment >> x3::raw[int_][([](auto& ctx){ std::string s(_attr(ctx).begin(), _attr(ctx).end()); item::n_items = std::stoi(s); item::n_read_items = 0; })]
这种方式通过raw获取字符串后手动转换为整数,避免了omit带来的unused_type元素,减少属性序列长度,从而绕过VS2017的模板限制。
为什么原代码在GCC/Clang中正常?
GCC和Clang对模板参数的处理更灵活,支持更长的模板参数序列;而VS2017的MSVC编译器在Boost 1.69版本下,对MPL vector的默认上限处理更严格,当序列中的unused_type元素过多时就会触发断言错误。
内容的提问来源于stack exchange,提问作者Pablo

