Boost Spirit X3:x3::_val与x3::_attr的语义差异及示例
Boost Spirit X3 中
x3::_val 与 x3::_attr 的核心差异解析 一句话戳破本质区别
x3::_val(ctx):当前绑定规则自身的属性,是你最终要填充的「结果容器」。x3::_attr(ctx):当前语义动作所附着的解析器片段的属性,是这段解析刚产出的「临时值」。
重新解读官方文档的抽象描述
官方的定义太绕,换成人话:
_val:不管语义动作嵌套多深,只要在某条规则的范围内,它永远指向这条规则声明的属性(比如rule<..., T>里的T类型变量),是规则的最终输出目标。_attr:你把语义动作挂在哪个解析器上,它就对应那个解析器的直接解析结果——比如挂在int_上就是刚解析的整数,挂在+digit上就是刚匹配的数字字符串。
默认情况下,如果规则属性和解析器属性类型完全一致(比如用 int_ 解析到 rule<..., int>),两者会指向同一个值——因为Spirit会直接把解析结果赋值给规则属性,不需要额外转换。但一旦遇到属性类型不匹配或嵌套规则,差异会立刻显现。
差异显著的场景示例
场景1:规则属性与解析器属性类型不同(需要手动转换)
比如我们要把字符串形式的数字解析成整数,先用string解析器获取字符串,再转成int赋值给规则属性:
#include <boost/spirit/home/x3.hpp> #include <string> #include <iostream> namespace x3 = boost::spirit::x3; int main() { using namespace x3; auto str_to_int = [](auto& ctx) { // _attr 是 +digit 解析出的 std::string // _val 是规则的 int 类型目标属性 _val(ctx) = std::stoi(_attr(ctx)); }; rule<struct int_from_str_tag, int> int_from_str = "int_from_str"; auto int_from_str_def = lexeme[+digit][str_to_int]; BOOST_SPIRIT_DEFINE(int_from_str); std::string input = "4567"; int result; parse(input.begin(), input.end(), int_from_str, result); std::cout << result << std::endl; // 输出4567 }
这里_attr和_val类型完全不同,必须通过语义动作完成转换赋值。
场景2:嵌套规则,用_val维护全局结果状态
比如解析加法表达式,用_val累加所有数字,_attr提供当前解析的单个数字:
#include <boost/spirit/home/x3.hpp> #include <iostream> namespace x3 = boost::spirit::x3; int main() { using namespace x3; auto accumulate = [](auto& ctx) { // _attr 是当前 int_ 解析出的单个数字 // _val 是外层 add_expr 规则的属性,用于存储累加结果 _val(ctx) += _attr(ctx); }; rule<struct add_expr_tag, int> add_expr = "add_expr"; auto add_expr_def = int_[accumulate] >> *(lit('+') >> int_[accumulate]); BOOST_SPIRIT_DEFINE(add_expr); std::string input = "9+3+7"; int result = 0; parse(input.begin(), input.end(), add_expr, result); std::cout << result << std::endl; // 输出19 }
每次触发语义动作时,_attr都是当前解析的临时数字,_val则是规则的全局结果容器——这就是你看到的「_attr是即时状态,_val是全局状态」的典型场景。
场景3:嵌套不同属性类型的规则
比如外层规则属性是std::vector<int>,内层规则解析单个整数,用_attr取内层结果,_val存到外层容器:
#include <boost/spirit/home/x3.hpp> #include <vector> #include <iostream> namespace x3 = boost::spirit::x3; int main() { using namespace x3; rule<struct single_int_tag, int> single_int = "single_int"; auto single_int_def = int_; BOOST_SPIRIT_DEFINE(single_int); auto push_to_vec = [](auto& ctx) { // _attr 是内层 single_int 规则的 int 结果 // _val 是外层 int_list 规则的 vector<int> 容器 _val(ctx).push_back(_attr(ctx)); }; rule<struct int_list_tag, std::vector<int>> int_list = "int_list"; auto int_list_def = single_int[push_to_vec] >> *(lit(',') >> single_int[push_to_vec]); BOOST_SPIRIT_DEFINE(int_list); std::string input = "10,20,30"; std::vector<int> result; parse(input.begin(), input.end(), int_list, result); for (int num : result) { std::cout << num << " "; // 输出10 20 30 } }
这里_attr和_val类型不同,语义动作负责将内层解析结果添加到外层容器中。
最后总结
- 操作规则的最终结果时用
_val:它是规则的「输出变量」,贯穿整个规则的解析过程。 - 获取当前解析片段的直接产出时用
_attr:它是解析过程中的「临时输入」,只对应当前语义动作附着的解析器。 - 只有当规则属性与解析器属性完全匹配且无嵌套时,两者才会指向同一值——一旦涉及类型转换、嵌套规则,差异会立刻显现。
内容的提问来源于stack exchange,提问作者Raven
相关产品推荐
相关产品推荐

