【问题标题】:Virtual template functions: implementing the Visitor pattern with parameters虚拟模板函数:实现带参数的访问者模式
【发布时间】:2017-08-18 05:42:00
【问题描述】:

我正在尝试实现访问 AST 的访问者模式。我定义了一个ASTNode,它可以接受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 的重复,因为我也在尝试让接受返回模板类型。

【问题讨论】:

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


【解决方案1】:

访问/接受函数不应接受额外参数或返回 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;
  };

【讨论】:

  • 这应该有一个虚拟析构函数,否则你会收到警告。
【解决方案2】:

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

我刚刚写完一个解析器/词法分析器,发现使用 lambda 可以节省大量时间。

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

我从这个演讲中得到了代码:“C++Now 2017: Vittorio Romeo “Implementing variantvisitation using lambdas”

文件匹配.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{}; 
            }
        );
);

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2023-03-28
    • 2011-02-22
    • 1970-01-01
    • 2017-06-10
    • 1970-01-01
    • 1970-01-01
    • 2010-12-02
    相关资源
    最近更新 更多