Boost.Spirit X3交替运算符解析混合整数列表异常问题咨询
搞懂Boost.Spirit X3交替运算符的行为,解决你的列表解析问题
你对交替运算符的理解误区
首先得戳破这个关键点:Spirit X3里的交替运算符|不是“整个输入匹配失败才尝试下一个分支”,而是按顺序尝试每个分支,只要某个分支能匹配输入的任意前缀(哪怕只是开头一小段),就会立刻停止,不会再试后面的分支。这就是你遇到问题的根源。
你看解析"1|20000000000|30000000000"时的情况:
- 第一个分支
x3::uint32 % '|'先执行,成功解析了1,然后碰到|,接着尝试解析20000000000——这个数远超uint32_t的最大值(4294967295),所以uint32解析失败。 - 但你没给解析器加“必须匹配到输入末尾”的限制,
x3::parse函数默认只要能匹配输入的一部分就算成功。所以第一个分支虽然只匹配了1,但解析器已经认为这个分支“成功了”,直接停止,根本不会去碰第二个uint64的分支。
怎么解决?强制分支匹配整个输入
要实现你想要的逻辑——“先试着用uint32_t解析整个列表,全成功就用它;只要有一个数解析失败,就换uint64_t解析整个列表”——你得让每个分支都必须匹配完整的输入,而不是随便一段前缀。这就需要用到x3::eoi(end of input,输入结束标记),它会强制解析器必须读到输入的末尾才算分支匹配成功。
修改后的代码示例
#include <boost/spirit/home/x3.hpp> #include <boost/spirit/home/x3/support/ast/variant.hpp> #include <vector> #include <iostream> struct printer { template <typename int_type> void operator()(std::vector<int_type> const& vec) { std::cout << "vec(" << sizeof(int_type) << "): { "; for(auto const &elem : vec ){ std::cout << elem << ", "; } std::cout << "}\n"; } }; template <typename Iterator> void parse_int_list(Iterator first, Iterator last) { namespace x3 = boost::spirit::x3; using vec_variant = x3::variant<std::vector<uint32_t>, std::vector<uint64_t>>; vec_variant vecs; // 定义两个完整匹配的分支:必须把输入全解析完才算成功 auto uint32_full = x3::uint32 % '|' >> x3::eoi; auto uint64_full = x3::uint64 % '|' >> x3::eoi; // 先试uint32_t的完整匹配,失败了再重置迭代器试uint64_t bool success = x3::parse(first, last, uint32_full, vecs); if (!success) { first = Iterator{last.base()}; // 重置迭代器到输入开头 success = x3::parse(first, last, uint64_full, vecs); } boost::apply_visitor(printer{}, vecs); } int main() { std::string test1 = "1|2|3"; parse_int_list(test1.begin(), test1.end()); std::string test2 = "10000000000|20000000000|30000000000"; parse_int_list(test2.begin(), test2.end()); std::string test3 = "1|20000000000|30000000000"; parse_int_list(test3.begin(), test3.end()); return 0; }
更简洁的写法:带eoi的交替分支
其实你也可以把两个带eoi的分支直接放到交替里,Spirit会自动处理回溯:
template <typename Iterator> void parse_int_list(Iterator first, Iterator last) { namespace x3 = boost::spirit::x3; using vec_variant = x3::variant<std::vector<uint32_t>, std::vector<uint64_t>>; vec_variant vecs; // 先试uint32_t全匹配,失败回溯后试uint64_t全匹配 auto full_parser = (x3::uint32 % '|' >> x3::eoi) | (x3::uint64 % '|' >> x3::eoi); x3::parse(first, last, full_parser, vecs); boost::apply_visitor(printer{}, vecs); }
这个写法里,第一个分支只有在整个输入都能被解析成uint32_t列表时才会成功;如果失败(比如有数值超出范围,或者没读到末尾),解析器会回溯到输入开头,尝试第二个分支,完美符合你的需求。
核心要点回顾
- 别被交替运算符的字面意思误导:
A | B不是“先试A解析整个输入,失败再试B”,而是“先试A能不能匹配输入的任意前缀,能就停,不能才试B”。 - 要实现“全输入匹配”的分支切换,必须给每个分支加上
x3::eoi,确保只有整个输入被匹配时,该分支才算成功。 - 没有
eoi的话,解析器会“见好就收”,哪怕只匹配了开头几个字符,也会认为分支成功,直接跳过后续分支。
内容的提问来源于stack exchange,提问作者kalaxy
相关产品推荐
相关产品推荐

