虚拟模板功能:使用参数实现访客模式

时间:2017-08-18 05:42:00

标签: c++ templates design-patterns abstract-syntax-tree visitor

我正在尝试实现用于行走AST的访客模式。我已经定义了ASTNode,可以接受Visitor,并允许访问者自行访问。下面的示例包含Visitor和ASTNode的每个具体实现。

class ASTNode;

template <class P, class R>
class Visitor {
    public:
        virtual ~Visitor() {}
        virtual R visit(ASTNode& node, P p) const = 0;
};

class ASTNode {
    public:
        virtual ~ASTNode() {}
        template <class P, class R>
        virtual R accept(Visitor<R, P>& v, P p) {
            return v.visit(*this);
        }
};

class Roman : public ASTNode {
    public:
        Roman(Numeral n, optional<Accidental> a) : numeral(n), alteration(a) {};
        const Numeral numeral;
        const optional<Accidental> alteration;
};

class ToStringVisitor : public Visitor<string, int> {
    virtual string visit(Roman& node, int param) {
        string result = NumeralStrings[node.numeral];
        if (node.alteration.has_value()) result = accidentalToString(node.alteration.value()) + result;
        return result;
    }
};

然后,我可以使用类似的东西走AST:

Roman r;
ToStringVisitor tsv;
// ...
return r.accept(tsv, 42);

如您所见,我正在尝试使用模板来允许参数和返回值。但是,我收到编译器错误:

 error: templates may not be 'virtual'
       virtual R accept(Visitor<R, P>& v, P p) {

我对这是一个错误的原因有一个模糊的理解。但是,我怎样才能合法地完成这项工作呢?

编辑:我不认为这是this question的副本,因为我也试图接受返回模板类型。

2 个答案:

答案 0 :(得分:0)

您收到此错误消息,因为C ++禁止定义虚拟模板函数。删除虚拟关键字将修复编译错误。

我刚刚写完一个解析器/词法分析器,发现使用lambdas是一个很好的节省时间。

这是我对lambda访客的实现。它在VS 2017中编译,也应该在gcc下编译。

我从这次演讲中得到了代码:“C ++ Now 2017:Vittorio Romeo”使用lambdas实现variant访问“

文件match.h

#pragma once

#include <type_traits>
#include <variant>

template<typename TF, typename...TFs>
struct overload_set : TF, overload_set<TFs...>
{
    using TF::operator();
    using overload_set<TFs...>::operator();

    template<typename TFFwd, typename...TFFwds>
    constexpr overload_set(TFFwd&& f, TFFwds&&...rest)
        : TF { std::forward<TFFwd>(f) }
        , overload_set<TFs...>{ std::forward<TFFwds>(rest)... }
    { }
};

template<typename TF>
struct overload_set<TF> : TF
{
    using TF::operator();

    template<typename TFFwd>
    constexpr overload_set(TFFwd&& f)
        : TF { std::forward<TFFwd>(f) }
    { }
};

template<typename...Tfs>
constexpr auto overload(Tfs&...fs)
{
    return overload_set<std::remove_reference_t<Tfs>...>(std::forward<Tfs>(fs)...);
}

template<typename Visitor, typename...TVariants>
constexpr decltype(auto) visit_recursively(Visitor&& vis, TVariants&&...vars)
{
    return std::visit(
        std::forward<Visitor>(vis),
        std::forward<TVariants>(variants)._data...
    );
}

template<typename...TVariants>
constexpr auto match(TVariants&&...vs)
{
    return [&vs...](auto&&...fs) //-> decltype(auto)
    {
        return std::visit(overload(std::forward<decltype(fs)>(fs)...), vs...);
    };
}

翻译级别的示例:

template<>
std::string convertTo<std::string>(const variant& v)
{
    return match(v)(
        [](const std::string& s) { return s; },
        [](const auto&) { throw InternalError("cannot convert type to string"); return std::string{}; }
    );
}

variant Interpreter::evaluate(ast::RValue & rv)
{
    // maps to overloads for all the types held by variant type RValue
    return match(rv)(
        [&](auto& x) { return evaluate(x); }
    );
}

// recursion...
variant evaluate(std::unique_ptr<ast::Spheref>& rv)
{
    return match(rv->vec_) (
        [](ast::Vector4f& v) { return variant{ std::make_shared<Vector4f>(std::move(v)) }; },
        [&](std::vector<ast::RValue>& v)
    {
        if (v.size() != 4)
        {
            throw InternalError{ "sphere must have 4 parameters" };
        }
        Vector4f r;
        r[0] = convertTo<F32>(evaluate(v[0]));
        r[1] = convertTo<F32>(evaluate(v[1]));
        r[2] = convertTo<F32>(evaluate(v[2]));
        r[3] = convertTo<F32>(evaluate(v[3]));
        return variant{ std::make_shared<Vector4f>(std::move(r)) };
    }
    );
}

// cascading calls...
ObjectRef or = match(o->value_) (
    [](Identifier& id) -> ObjectRef
    { 
        return { std::make_shared<ast::Identifier>(std::move(id)) };  
    },
    [&](ast::ObjectValueBlock& bl) -> ObjectRef
    {
        return match(std::move(evaluate(bl))) (
            [](std::shared_ptr<Object>&& x) { return ObjectRef{ x }; },
            [](std::shared_ptr<Identifier>&& x) { return ObjectRef{ x };},
            [](auto&&) { 
                throw InternalError{ "unexpected type in Object array expansion" }; 
                return ObjectRef{}; 
            }
        );
);

答案 1 :(得分:0)

访问/接受函数不应接受额外的参数或返回void以外的任何内容。具体的访问者对象在构建时获取额外的数据。它还可以存储您想要从方法返回的任何结果。这是沼泽标准结构,不需要任何模板。

  class NodeVisitor {
      virtual void visit (Roman*) = 0;
      ...
  };

  class ToStringVisitor : public NodeVisitor {
      ToStringVisitor (int param) : param(param) {}
      void visit (Roman* r) {
          result = ...;
      }
      ...
      int param;
      std::string result;
  };