0

I am trying to implement the visitor pattern for walking an AST. I have defined an ASTNode which can accept a Visitor, and will allow the visitor to visit itself. The example below contains one concrete implementation each of the Visitor and the 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;
    }
};

Then, I can walk the AST using something like this:

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

As you can see, I am trying to use templates to allow for a parameter and return value. However, I get the compiler error:

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

I have a vague understanding of why this is an error. However, how can I accomplish this legally?

Edit: I don't think this is a duplicate of this question, because I'm also trying to have accept return a template type.

dylhunn
  • 989
  • 2
  • 8
  • 25
  • [visitor-pattern-example-in-c++](https://stackoverflow.com/documentation/design-patterns/4579/visitor-pattern/15127/visitor-pattern-example-in-c#t=201708181449395731485) shows how to have return type in visitor, you can do the same for extra argument. – Jarod42 Aug 18 '17 at 14:52

2 Answers2

1

Visit/accept functions shouldn't accept extra arguments or return anything other than void. The concrete visitor object gets extra data at construction time. It can also store any results you want to return from your method. This is bog standard co struction that doesn't need any templates.

  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;
  };
n. m. could be an AI
  • 112,515
  • 14
  • 128
  • 243
0

You get this error message because C++ forbids defining a virtual template function. Removing the virtual keyword will fix the compilartion error.

I've just finished writing a parser/lexer and found that using lambdas was a great time-saver.

Here is my implementation of lambda visitors. It compile in VS 2017, and should also compile under gcc.

I got the code from this talk: "C++Now 2017: Vittorio Romeo “Implementing variant visitation using lambdas"

File 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...);
    };
}

Example at the interpreter level:

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{}; 
            }
        );
);
Michaël Roy
  • 6,338
  • 1
  • 15
  • 19