为递归变体编写 boost::variant 访问者
Posted
技术标签:
【中文标题】为递归变体编写 boost::variant 访问者【英文标题】:Composing boost::variant visitors for recursive variants 【发布时间】:2021-08-03 14:37:03 【问题描述】:我有一个应用程序,其中有几个 boost::variants 共享许多字段。我希望能够将这些访问者组合成“更大”变体的访问者,而无需复制和粘贴一堆代码。对非递归变体执行此操作似乎很简单,但是一旦有了递归变体,访问者中的自引用(当然)指向错误的类。为了使这个具体(并从 boost::variant 文档中抄袭):
#include "boost/variant.hpp"
#include <iostream>
struct add;
struct sub;
template <typename OpTag> struct binop;
typedef boost::variant<
int
, boost::recursive_wrapper< binop<add> >
, boost::recursive_wrapper< binop<sub> >
> expression;
template <typename OpTag>
struct binop
expression left;
expression right;
binop( const expression & lhs, const expression & rhs )
: left(lhs), right(rhs)
;
// Add multiplication
struct mult;
typedef boost::variant<
int
, boost::recursive_wrapper< binop<add> >
, boost::recursive_wrapper< binop<sub> >
, boost::recursive_wrapper< binop<mult> >
> mult_expression;
class calculator : public boost::static_visitor<int>
public:
int operator()(int value) const
return value;
int operator()(const binop<add> & binary) const
return boost::apply_visitor( *this, binary.left )
+ boost::apply_visitor( *this, binary.right );
int operator()(const binop<sub> & binary) const
return boost::apply_visitor( *this, binary.left )
- boost::apply_visitor( *this, binary.right );
;
class mult_calculator : public boost::static_visitor<int>
public:
int operator()(int value) const
return value;
int operator()(const binop<add> & binary) const
return boost::apply_visitor( *this, binary.left )
+ boost::apply_visitor( *this, binary.right );
int operator()(const binop<sub> & binary) const
return boost::apply_visitor( *this, binary.left )
- boost::apply_visitor( *this, binary.right );
int operator()(const binop<mult> & binary) const
return boost::apply_visitor( *this, binary.left )
* boost::apply_visitor( *this, binary.right );
;
// I'd like something like this to compile
// class better_mult_calculator : public calculator
//
// public:
// int operator()(const binop<mult> & binary) const
//
// return boost::apply_visitor( *this, binary.left )
// * boost::apply_visitor( *this, binary.right );
//
// ;
int main(int argc, char **argv)
// result = ((7-3)+8) = 12
expression result(binop<add>(binop<sub>(7,3), 8));
assert( boost::apply_visitor(calculator(),result) == 12 );
std::cout << "Success add" << std::endl;
// result2 = ((7-3)+8)*2 = 12
mult_expression result2(binop<mult>(binop<add>(binop<sub>(7,3), 8),2));
assert( boost::apply_visitor(mult_calculator(),result2) == 24 );
std::cout << "Success mult" << std::endl;
我真的很想注释掉 better_mult_expression 来编译(和工作),但它没有——因为基本计算器访问者中的 this
指针不引用 mult_expression,而是表达式。
有没有人有克服这个问题的建议,还是我只是在找错树?
【问题讨论】:
【参考方案1】:首先,我建议该变体包含所有可能的节点类型,而不是区分 mult
和 expression
。这种区别在 AST 级别没有意义,仅在解析器阶段(如果您以递归/PEG 方式实现运算符优先级)。
除此之外,还有一些观察:
如果您将 apply_visitor
调度封装到您的评估函子中,您可以大大减少代码重复
您真正的问题似乎不是关于组合变体,而是组合访问者,更具体地说,是通过继承。
您可以使用using
将继承的重载拉入范围以进行重载解析,因此这可能是最直接的答案:
Live On Coliru
struct better_mult_calculator : calculator
using calculator::operator();
auto operator()(const binop<mult>& binary) const
return boost::apply_visitor(*this, binary.left) *
boost::apply_visitor(*this, binary.right);
;
正在改进!
从that listing开始,让我们刮掉一些噪音!
删除不必要的 AST 区别 (-40 lines, down to 55 lines of code)
概括操作; <functional>
标头是这些标头的标准配置:
namespace AST
template <typename> struct binop;
using add = binop<std::plus<>>;
using sub = binop<std::minus<>>;
using mult = binop<std::multiplies<>>;
using expr = boost::variant<int,
recursive_wrapper<add>,
recursive_wrapper<sub>,
recursive_wrapper<mult>>;
template <typename> struct binop expr left, right; ;
// namespace AST
现在整个calculator
可以是:
struct calculator : boost::static_visitor<int>
int operator()(int value) const return value;
template <typename Op>
int operator()(AST::binop<Op> const& binary) const
return Op(boost::apply_visitor(*this, binary.left),
boost::apply_visitor(*this, binary.right));
;
在这里,您的变体可以添加任意操作,甚至无需触摸计算器。
Live Demo,43 行代码
就像我提到的开始,封装访问!
struct Calculator
template <typename... T> int operator()(boost::variant<T...> const& v) const
return boost::apply_visitor(*this, v);
template <typename T>
int operator()(T const& lit) const return lit;
template <typename Op>
int operator()(AST::binop<Op> const& bin) const
return Op(operator()(bin.left), operator()(bin.right));
;
现在您可以像预期的那样调用您的计算器:
Calculator calc;
auto result1 = calc(e1);
当您使用 operatios 甚至其他文字类型(例如 double
)扩展变体时,它将起作用。它甚至可以工作,无论您是否向它传递包含节点类型子集的不兼容变体类型。
为了实现可维护性/可读性,我建议将 operator()
设为仅调度函数:
完整演示
Live On Coliru
#include <boost/variant.hpp>
#include <iostream>
namespace AST
using boost::recursive_wrapper;
template <typename> struct binop;
using add = binop<std::plus<>>;
using sub = binop<std::minus<>>;
using mult = binop<std::multiplies<>>;
using expr = boost::variant<int,
recursive_wrapper<add>,
recursive_wrapper<sub>,
recursive_wrapper<mult>>;
template <typename> struct binop expr left, right; ;
// namespace AST
struct Calculator
auto operator()(auto const& v) const return call(v);
private:
template <typename... T> int call(boost::variant<T...> const& v) const
return boost::apply_visitor(*this, v);
template <typename T>
int call(T const& lit) const return lit;
template <typename Op>
int call(AST::binop<Op> const& bin) const
return Op(call(bin.left), call(bin.right));
;
int main()
using namespace AST;
std::cout << std::boolalpha;
auto sub_expr = addsub7, 3, 8;
expr e1 = sub_expr;
expr e2 = multsub_expr, 2;
Calculator calc;
auto result1 = calc(e1);
std::cout << "result1: " << result1 << " Success? " << (12 == result1) << "\n";
// result2 = ((7-3)+8)*2 = 12
auto result2 = calc(e2);
std::cout << "result2: " << result2 << " Success? " << (24 == result2) << "\n";
仍然打印
result1: 12 Success? true
result2: 24 Success? true
【讨论】:
以上是关于为递归变体编写 boost::variant 访问者的主要内容,如果未能解决你的问题,请参考以下文章
boost::variant - 在变体上应用算术的最简单方法
(C++, boost::variant) boost 变体映射的数据类型并对其执行数学运算