首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >组合boost::递归变体的变体访问者

组合boost::递归变体的变体访问者
EN

Stack Overflow用户
提问于 2021-08-03 14:37:03
回答 1查看 69关注 0票数 1

我有一个有几个boost::variants的应用程序,它们共享许多字段。我希望能够在不复制和粘贴一堆代码的情况下,将这些访问者组合成“较大”变体的访问者。对于非递归变体来说,这样做似乎很简单,但是一旦有了递归变量,访问者(当然)中的自引用就指向错误的类。要使这个具体(并从boost::中滚动):

代码语言:javascript
复制
#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,而是引用表达式。

有没有人建议我克服这个问题,还是我只是在胡闹呢?

EN

回答 1

Stack Overflow用户

回答已采纳

发布于 2021-08-04 11:58:08

首先,我建议该变体包括所有可能的节点类型,而不是区分multexpression。这种区别在AST级别没有意义,只是在解析器阶段(如果您以递归/PEG的方式实现操作符优先)。

除此之外,以下是一些观察结果:

  • 如果将apply_visitor分派封装到您的评估函式中,则可以将代码复制减少一个很大的因素。
  • 您真正的问题似乎不是关于组合变体,而是通过继承来组合访问者。 您可以使用using将继承的重载拉到范围中进行重载解析,因此这可能是最直接的答案: 住在Coliru struct better_mult_calculator :计算器{使用计算器::运算符();自动操作符()( const & binop&二进制)const{返回boost::apply_visitor(*this,binary.left) * boost::apply_visitor(*this,binary.right);};

进步!

那份清单开始,让我们刮掉一些噪音!

  1. 删除未终止的AST区分(-40行,减少到55行代码)
  2. 概括这些操作;<functional>标头是标准的: 名称空间AST {模板结构绑定;使用add = binop>;使用sub = binop>;使用mult = binop>;使用expr = boost::variant;使用模板结构{ expr;};} //命名空间AST 现在整个calculator可以是: 结构计算器: boost::static_visitor { int运算符()( int值) const {返回值;}模板int运算符()(AST::binop&二进制) const {返回Op}( boost::apply_visitor(*this,binary.left),boost::apply_visitor(*this,binary.right));}; 在这里,您的变体可以添加任意操作,甚至不需要触摸计算器。 现场演示,43行代码
  3. 就像我说的开始,封装探视! 结构化计算器{ template int运算符()(boost::variant const& v) const {返回boost::apply_visitor(*this,v);} template int运算符()(T const& lit) const { return;} template int运算符()(AST:binop& bin) const {bin}(运算符()(bin.left),运算符()(bin.right));}; 现在你只需调用你的计算器,就像你想要的那样: 自动result1 = calc(e1); 当您使用operatios甚至其他文字类型(如double)扩展变量时,它将工作。它甚至可以工作,不管您是否将包含节点类型子集的不兼容变体类型传递给它。
  4. 为了实现可维护性/可读性,我建议只将operator()作为一个分派函数:

全演示

住在Coliru

代码语言:javascript
复制
#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 = add{sub{7, 3}, 8};
    expr e1       = sub_expr;
    expr e2       = mult{sub_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";
}

仍印

代码语言:javascript
复制
result1:  12 Success? true
result2:  24 Success? true
票数 1
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/68638064

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档