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

Boost X3 中能否在析取式解析中避免使用variant类型成员

问题描述

我想要解析 string | (string, int) 格式的内容,并将其存储到一个结构体中,其中 int 分量默认取指定值。这类解析结构在X3中的属性类型是 variant<string, tuple<string, int>>。

我原本设想可以定义一个能接收 string 或 (string, int) 参数的struct来自动完成赋值:

struct bar
{
    bar (std::string x = "", int y = 0) : baz1 {x}, baz2 {y} {}

    std::string          baz1;
    int                  baz2;
};

BOOST_FUSION_ADAPT_STRUCT (disj::ast::bar, baz1, baz2)

随后编写解析规则如下:

const x3::rule<class bar, ast::bar> bar = "bar";
using x3::int_;
using x3::ascii::alnum;

auto const bar_def = (+(alnum) | ('(' >> +(alnum) >> ',' >> int_ >> ')')) >> ';';

BOOST_SPIRIT_DEFINE(bar);

但这段代码无法正常编译,报错信息如下:

/usr/include/boost/spirit/home/x3/core/detail/parse_into_container.hpp:139:59: error: static assertion failed: Expecting a single element fusion sequence
  139 |             static_assert(traits::has_size<Attribute, 1>::value,

即使将 baz2 设为 optional 类型也无法解决该问题。目前我找到的一种解决方案是在结构体中定义variant字段,或者继承对应variant类型:

struct string_int {
    std::string s;
    int i;
};

struct foo {
    boost::variant<std::string, string_int>  var;
};

BOOST_FUSION_ADAPT_STRUCT (disj::ast::string_int, s, i)
BOOST_FUSION_ADAPT_STRUCT (disj::ast::foo, var)

注:出于某些原因,我需要使用 boost::variant 而非 x3::variant 才能让 operator<< 正常工作;另外 string_int 如果使用 std::pair 或 tuple 也无法正常工作,但 boost::fusion::deque 可以。

之后我可以给 foo 类添加访问器来获取其中存储的字符串和整数值。请问在Boost X3中实现该需求的正确、简洁方式是什么?有没有比上述方案更自然的实现,不需要额外给 foo 编写访问器?


解决方案

最简洁直接的方式是通过语义动作直接构造目标结构体,绕开中间variant层,完全匹配你最初的设计预期:

// 语义动作:单个字符串分支构造bar
auto make_str_bar = [](auto& ctx) { _val(ctx) = ast::bar(_attr(ctx)); };
// 语义动作:字符串+整数分支构造bar
auto make_pair_bar = [](auto& ctx) {
    auto& attr = _attr(ctx);
    _val(ctx) = ast::bar(
        boost::fusion::at_c<0>(attr),
        boost::fusion::at_c<1>(attr)
    );
};

auto const bar_def = (
    (+alnum)[make_str_bar]
    | ('(' >> +alnum >> ',' >> int_ >> ')')[make_pair_bar]
) >> ';';

这种方案不需要定义额外的variant包装类、不需要编写访问器、也不需要调整你最初的bar结构体定义,解析结果直接就是可用的bar实例。

如果你不想使用语义动作,也可以通过特化X3的属性转换规则实现自动映射:

namespace boost::spirit::x3::traits {
template<>
struct transform_attribute<
    ast::bar,
    boost::variant<std::string, std::tuple<std::string, int>>,
    void
> {
    using type = boost::variant<std::string, std::tuple<std::string, int>>;
    static type pre(ast::bar&) { return {}; }
    static void post(ast::bar& val, type const& attr) {
        if (auto* s = boost::get<std::string>(&attr)) {
            val = ast::bar(*s);
        } else {
            auto& t = boost::get<std::tuple<std::string, int>>(attr);
            val = ast::bar(std::get<0>(t), std::get<1>(t));
        }
    }
    static void fail(ast::bar&) {}
};
}

特化完成后你原来的bar_def规则不需要做任何修改即可正常编译运行。


内容的提问来源于stack exchange,提问作者Michaël

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.10.03 12:39:03