我正在尝试实现用于行走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的副本,因为我也试图接受返回模板类型。
答案 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;
};