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

