You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.10 19:14:56