Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Is stateless visitor pattern possible in C++?

I was trying to translate the following Haskell code to C++:

data List t = Nil | Cons t (List t)

The straightforward translation of the algebraic data type to the stateless Visitor pattern yields the following Java code

interface List<T> {
  <R> R accept(ListVisitor<T,R> v);
}

interface ListVisitor<T,R> {
  R visitNil();
  R visitCons(T head, List<T> tail);
}

class Nil<T> implements List<T> {
  @Override
  public <R> R accept(ListVisitor<T,R> v) {
    return v.visitNil();
  }
}

class Cons<T> implements List<T> {
  public final T head;
  public final List<T> tail;
  public Cons(T head, List<T> tail) {
    this.head = head;
    this.tail = tail;
  }
  @Override
  public <R> R accept(ListVisitor<T,R> v) {
    return v.visitCons(head, tail);
  }
}

The following is the C++ code I have so far:

template<class T> class List;

template<class T, class R> class ListVisitor {
  virtual R visitNil() = 0;
  virtual R visitCons(T head, List<T> tail) = 0;
};

template<class T> class List {
  template<class R> virtual R accept(ListVisitor<T,R> v) = 0;
};

Note that the Java version uses a virtual generic function accept. When I translate it to C++, I end up with a virtual template function, which is not allowed by C++.

Is there a solution to it other than making accept return void and require visitors to be stateful?

Update: As requested, here are some examples of how the interfaces could be used (modulo smart pointers and possible compile errors):

template<class T> struct LengthVisitor : ListVisitor<T, int> {
  bool visitNil() { return 0; }
  bool visitCons(const T&, const List<T> &tail) { return 1 + tail.accept(*this); }
};

template<class T> struct ConcatVisitor : ListVisitor<T, const List<T> *> {
  const List<T> *right;
  ConcatVisitor(const List<T> *right) : right(right) {} 
  List<T> * visitNil() { return right; }
  List<T> * visitCons(const T &head, const List<T> & tail) {
    return new Cons(head, tail.accept(*this));
  }
};

Another example, a higher-level function fold, in Java, can be found here: http://hpaste.org/54650

like image 999
Rotsor Avatar asked Nov 29 '11 15:11

Rotsor


People also ask

In which scenario would you use the visitor pattern?

The visitor pattern is used when: Similar operations have to be performed on objects of different types grouped in a structure (a collection or a more complex structure). There are many distinct and unrelated operations needed to be performed.

Should I use visitor pattern?

The visitor pattern is useful when you want to process a data structure containing different kinds of objects, and you want to perform a specific operation on each of them, depending on its type. Your example is not the best, since you pass a single homogeneous list as input, so there is really no need for the pattern.

What is visitor design pattern in C++?

Visitor in C++ Visitor is a behavioral design pattern that allows adding new behaviors to existing class hierarchy without altering any existing code.

Which design pattern can be used by visitors to apply an operation over an object structure?

The Visitor pattern represents an operation to be performed on the elements of an object structure without changing the classes on which it operates.


1 Answers

This can certainly be improved (use smart pointers for tail ownership, for example), but the basic idea:

template <typename T>
struct cons_list {
     T head;
     cons_list<T>* tail;

     explicit cons_list(T head, cons_list *tail = nullptr)
         : head(head), tail(tail) {}

     template <template<typename> class Visitor>
     typename Visitor<T>::return_type accept(const Visitor<T>& visitor) {
          return visitor.visit(head, tail);
     }
};

template <typename T>
struct some_visitor {
     typedef void return_type;

     return_type visit(T head, cons_list<T>* tail) const {
          std::cout << head << '\n';
          if (tail != nullptr) tail->accept(*this);
     }
};

Demo. No need for virtual dispatch and class hierarchies. nullptr is C++11, but it should work just fine on 03.

It might be a better idea to implement accept as free function, and not use null pointers as nil node, but as I said, that's the basic thing.

Note: this is more-or-less the idea behind boost::static_visitor.

A full C++11 Boost.Variant version (needs template aliases). Not tested, because I don't have g++ 4.7 nearby.

struct nil_node {};
template <typename T> cons_node;

template <typename T>
using cons_list = boost::make_recursive_variant<
     nil_node, cons_node<T>
>::type;

template <typename T>
struct cons_node {
     T head;
     cons_list<T> tail;

     explicit cons_node(T head, const cons_list<T>& tail)
         : head(head), tail(tail)
     {}
};

template <typename T>
struct some_visitor : boost::static_visitor<T> {
     void operator()(nil_node&) {}
     void operator()(cons_node<T>& node) {
         std::cout << node.head << '\n';
         boost::apply_visitor(node.tail, *this);
     }
};

int main() {
    cons_node<int> x(1, cons_node<int>(2, cons_node<int>(3, nil_node())));
    boost::apply_visitor(some_visitor<int>(), x);
};
like image 109
Cat Plus Plus Avatar answered Sep 18 '22 05:09

Cat Plus Plus