基于Spirit X3将Kleene算子解析为备选集合的适配器实现问题
嘿,我来帮你拆解这个问题!看起来你已经在Spirit的解析路上走了不少路,卡在了TEST_ADAPT的关联逻辑上,核心诉求是不用variant,把解析结果存入普通容器,而且已经通过TEST_REF拿到了适配Spirit的存储引用元组,咱们一步步来解决:
核心矛盾点先理清楚
你提到Kleene算子需要单个顺序容器,但备选方案会产出不同类型的结果——这正是问题的关键:如果不用variant,我们没法把不同类型的元素塞进同一个单类型容器里,所以得换个思路存储这些不同类型的解析结果。
方案1:分类存储到Tuple容器(最贴合你现有成果)
既然你已经能拿到存储引用元组,那可以直接用std::tuple<std::vector<Type1>, std::vector<Type2>, ...>作为最终容器,把不同备选分支的解析结果分别塞进tuple对应的vector里,完全不需要variant。
举个具体的代码示例,结合Spirit Phoenix语义动作来实现关联:
// 假设你的备选解析结果类型是TypeA、TypeB、TypeC using MyContainer = std::tuple<std::vector<TypeA>, std::vector<TypeB>, std::vector<TypeC>>; MyContainer my_data; // 先拿到你通过TEST_REF获取的存储引用元组(假设是ref_tuple) auto ref_tuple = get_storage_ref_tuple(my_data); // 定义解析规则,给每个备选分支绑定语义动作,把结果推到对应vector auto parse_rule = (type_a_parser[phoenix::push_back(phoenix::at_c<0>(ref_tuple), _1)] | type_b_parser[phoenix::push_back(phoenix::at_c<1>(ref_tuple), _1)] | type_c_parser[phoenix::push_back(phoenix::at_c<2>(ref_tuple), _1)]) % ','; // 这里用%表示带分隔符的重复,对应Kleene算子的需求 // 执行解析 bool success = qi::parse(begin(input), end(input), parse_rule);
这个思路里,Kleene算子作用于「备选分支的整体」,每个分支的结果都被引导到专属的vector中,完美避开了variant的依赖。
方案2:多态基类容器(如果需要统一遍历)
如果你需要后续能统一遍历所有解析结果,可以定义一个抽象基类,让所有备选类型继承它,然后用std::vector<std::unique_ptr<BaseType>>作为容器。这时候需要给Spirit写一个自定义的适配,或者用语义动作把解析到的对象包装成智能指针存入容器:
struct BaseElement { virtual ~BaseElement() = default; // 可以加统一的接口方法 }; struct TypeA : BaseElement { /* ... */ }; struct TypeB : BaseElement { /* ... */ }; std::vector<std::unique_ptr<BaseElement>> my_data; auto parse_rule = (type_a_parser[phoenix::push_back(phoenix::ref(my_data), phoenix::new_<TypeA>(_1))] | type_b_parser[phoenix::push_back(phoenix::ref(my_data), phoenix::new_<TypeB>(_1))]) * qi::space; // Kleene算子直接重复匹配
这个方案适合需要统一处理所有元素的场景,但会引入多态的开销。
把TEST_ADAPT和容器关联起来
你现在的TEST_ADAPT未完成,本质上就是要把上述的规则和容器绑定:
- 如果用Tuple方案,直接把你通过
TEST_REF拿到的存储引用元组,代入语义动作的phoenix::at_c<N>(ref_tuple)中,让解析结果直接写入对应的vector - 如果用多态方案,把容器的引用传入语义动作,直接push_back包装后的智能指针
补充:避开语义动作的进阶方式
如果你不想用Phoenix语义动作,可以自定义Spirit的traits::container特化,让Spirit自动识别你的Tuple容器,把不同类型的解析结果分发到对应的vector里。不过这种方式需要对Spirit的traits系统有一定了解,相对复杂一些,适合追求更“原生”适配的场景。
内容的提问来源于stack exchange,提问作者lakeweb

