Menu

a better improvement on the cyclic visitor

Developers
2008-08-14
2013-04-08
  • Alejandro Aragón

    Hello everyone,

    I just got what I really wanted with the cyclic visitor. I changed the Strict / Nonstrict enumerated type from my previous post so that it takes a Functor that allows to implement whatever behavior you want for the Nonstrict visitor. In this way you can create a cyclic visitor as follows:

    // defaults to mutable (non-const), do-nothing nonstrict visitor
    typedef visitor::Visitor<ReturnType, ClassList> ClassVisitor1;

    // do-nothing non-strict visitor for immutable objects (const)
    typedef visitor::Visitor<ReturnType, ClassList, Immutable> ClassVisitor2;

    // personalize the behavior of the non-strict visitor
    template <class R>
    struct Functor1 {
        template <class T>
        R operator()(T&) {
            // do whatever with T, when the function is not implemented
            // in the visitor
        }
    };
    typedef visitor::Visitor<ReturnType, ClassList, Immutable, Functor1> ClassVisitor3;

    // use strict visitor (compiler errors when R Visit(T&) not defined
    // mutable
    typedef visitor::Visitor<ReturnType, ClassList, Mutable, Strict> ClassVisitor4;
    // immutable
    typedef visitor::Visitor<ReturnType, ClassList, Immutable, Strict> ClassVisitor5;

    I think this is much better than my previous post, even though I don't think anyone has read that yet. The code is given below.

    Best regards,

    Alejandro M. Aragón

    #ifndef VISITOR_HPP_
    #define VISITOR_HPP_

    #include <Loki/Typelist.h>

    namespace visitor {

    using Loki::NullType;
    using Loki::Typelist;

    // visitor class template, adapted from the Andrei Alexandrescu's "Modern C++ Design"

    enum Visit_type { Mutable, Immutable };

    template <class T, typename R = void, Visit_type = Mutable>
    class StrictVisitor;

    template <class T, typename R>
    class StrictVisitor<T, R, Mutable>
    {
    public:
        typedef R ReturnType;
        typedef T ParamType;
        virtual ~StrictVisitor() {}
        virtual ReturnType Visit(ParamType&) = 0;
    };

    template <class T, typename R>
    class StrictVisitor<T, R, Immutable>
    {
    public:
        typedef R ReturnType;
        typedef const T ParamType;
        virtual ~StrictVisitor() {}
        virtual ReturnType Visit(ParamType&) = 0;
    };

    /// class template StrictVisitor (specialization)

    template <class Head, class Tail, typename R>
    class StrictVisitor<Typelist<Head, Tail>, R, Mutable>
    : public StrictVisitor<Head, R, Mutable>, public StrictVisitor<Tail, R, Mutable>
    {
    public:
        typedef R ReturnType;
        typedef Head ParamType;
        //    using StrictVisitor<Head, R>::Visit;
        //    using StrictVisitor<Tail, R>::Visit;
    };

    template <class Head, typename R>
    class StrictVisitor<Typelist<Head, NullType>, R, Mutable> : public StrictVisitor<Head, R, Mutable>
    {
    public:
        typedef R ReturnType;
        typedef Head ParamType;
        using StrictVisitor<Head, R, Mutable>::Visit;
    };

    template <class Head, class Tail, typename R>
    class StrictVisitor<Typelist<Head, Tail>, R, Immutable>
    : public StrictVisitor<Head, R, Immutable>, public StrictVisitor<Tail, R, Immutable>
    {
    public:
        typedef R ReturnType;
        typedef Head ParamType;
        //    using StrictVisitor<Head, R>::Visit;
        //    using StrictVisitor<Tail, R>::Visit;
    };

    template <class Head, typename R>
    class StrictVisitor<Typelist<Head, NullType>, R, Immutable> : public StrictVisitor<Head, R, Immutable>
    {
    public:
        typedef R ReturnType;
        typedef Head ParamType;
        using StrictVisitor<Head, R, Immutable>::Visit;
    };

    ////////////////////////////////////////////////////////////////////////////////
    // class template NonStrictVisitor
    // Implements non-strict visitation (you can implement only part of the Visit
    //     functions)
    ////////////////////////////////////////////////////////////////////////////////

    template <class R>
    struct DefaultFunctor {
        template <class T>
        R operator()(T&) { return R(); }
    };

    template <class T, typename R = void, Visit_type V = Mutable, class F = DefaultFunctor<R> > class BaseVisitorImpl;

    template <class Head, class Tail, typename R, Visit_type V, class F>
    class BaseVisitorImpl<Typelist<Head, Tail>, R, V, F>
    : public StrictVisitor<Head, R, V>, public BaseVisitorImpl<Tail, R, V, F> {
    public:
        typedef typename StrictVisitor<Head, R, V>::ParamType ParamType;
        virtual R Visit(ParamType& h)
        { return F()(h); }
    };

    template <class Head, typename R, Visit_type V, class F >
    class BaseVisitorImpl<Typelist<Head, NullType>, R, V, F> : public StrictVisitor<Head, R, V>
    {
    public:
        typedef typename StrictVisitor<Head, R, V>::ParamType ParamType;
        virtual R Visit(ParamType& h)
        { return F()(h); }
    };

    /// Visitor

    template <class R>
    struct Strict {};

    template <typename R, class TList, Visit_type V = Mutable, template <class> class FunctorPolicy = DefaultFunctor>
    class Visitor : public BaseVisitorImpl<TList, R, V, FunctorPolicy<R> > {
    public:
        typedef R ReturnType;

        template <class Visited>
        ReturnType GenericVisit(Visited& host) {
            StrictVisitor<Visited, ReturnType, V>& subObj = *this;
            return subObj.Visit(host);
        }
    };

    template <typename R, class TList, Visit_type V>
    class Visitor<R, TList, V, Strict> : public StrictVisitor<TList, R, V> {
    public:
        typedef R ReturnType;

        template <class Visited>
        ReturnType GenericVisit(Visited& host) {
            StrictVisitor<Visited, ReturnType, V>& subObj = *this;
            return subObj.Visit(host);
        }
    };

    } // namespace visitor

    #endif /* VISITOR_HPP_ */

     
    • Peter Kuemmel

      Peter Kuemmel - 2008-08-18

      Hi Alejandro,

      I like the idea to have a strict mode.
      And in case of non-strict mode you could introduce a policy to switch between different fallback impls,
      your mentioned template-template parameter.
      What about reimplementing the visitor example with your code, then the differences become clearer?

      Peter

       
    • Alejandro Aragón

      Hi Peter, thanks for replying.

      I came up with this little example that uses a small class list. Let's say your classes allow both strict and non-strict visit, which means that they define two accept virtual functions, depending on the type of the visitor:

      #include <stdexcept>
      #include <iostream>
      #include <typeinfo>
      #include "visitor.hpp"

      using std::cout;
      using std::endl;

      class Base;
      class Class1;
      class Class2;
      class Class3;

      template <class R>
      struct NonStrictVisitor {
          template <class T>
          R operator()(T& t) {
                  // whatever default behavior goes here
                  cout<<"visiting class "<<typeid(t).name()<<endl;
          }
      };

      // list of classes to visit
      typedef LOKI_TYPELIST_3(Class1, Class2, Class3) ClassList;

      // non-strict visitor
      typedef visitor::Visitor<void, ClassList, visitor::Mutable, NonStrictVisitor> ClassVisitor;

      // strict visitor
      typedef visitor::Visitor<void, ClassList, visitor::Mutable, visitor::Strict> StrictClassVisitor;

      // concrete non-strict visitor, only defines the Visit function for Class1
      class VVisitor : public ClassVisitor{
          ReturnType Visit( Class1&) {
              cout<<"inside class 1"<<endl;
          }
      };

      // concrete strict visitor, the code will not compile if Visit is not defined for every class
      // in the list because of pure virtual Visit function
      class SVisitor : public StrictClassVisitor {
          ReturnType Visit(Class1&) {
              cout<<"strict visit for class 1"<<endl;
          }
          ReturnType Visit(Class2&) {
              cout<<"strict visit for class 2"<<endl;
          }
          ReturnType Visit(Class3&) {
              cout<<"strict visit for class 3"<<endl;
          }
      };

      // class hierarchy
      struct Base {
          virtual void accept(ClassVisitor& vis) = 0;
          virtual ~Base() {}
      };

      // the following classes define accept function that accept the strict and
      // the non-strict visitors

      struct Class1 : public Base {
          virtual void accept(ClassVisitor& vis)  {
              vis.GenericVisit(*this);
          }
          virtual void strict_accept(StrictClassVisitor& vis)  {
              vis.GenericVisit(*this);
          }
      };
      struct Class2 : public Base {
          virtual void accept(ClassVisitor& vis)  {
              vis.GenericVisit(*this);
          }
          virtual void strict_accept(StrictClassVisitor& vis)  {
              vis.GenericVisit(*this);
          }
      };
      struct Class3 : public Base {
          virtual void accept(ClassVisitor& vis)  {
              vis.GenericVisit(*this);
          }
          virtual void strict_accept(StrictClassVisitor& vis)  {
              vis.GenericVisit(*this);
          }
      };

      int main(int argc, char* argv[]) {
         
          Class1 c1;
          Class2 c2;
          Class3 c3;

          VVisitor vis;
          c1.accept(vis);
          c2.accept(vis);
          c3.accept(vis);
         
          SVisitor vis2;
          c1.strict_accept(vis2);
          c2.strict_accept(vis2);
          c3.strict_accept(vis2);
         
              return 0;
      }

      This code prints:

      aaragon@~/Desktop/visitor$./a.out
      inside class 1
      visiting class 6Class2
      visiting class 6Class3
      strict visit for class 1
      strict visit for class 2
      strict visit for class 3

      Alejandro M. Aragón

       
    • Omer Katz

      Omer Katz - 2009-02-01

      Is this added to the feature requests?

       

Log in to post a comment.