Summarizing the Visitor Pattern (C ++)

Disadvantages of a typical implementation

The article intentionally does not provide an example of a typical implementation of the visitor pattern in C ++.





If you are not familiar with this pattern, you can familiarize yourself with it here .





In a nutshell, this pattern is very useful if you need to traverse a collection of pointers to an abstract base class, applying some kind of operation to them depending on the type that is hidden behind the abstraction.





Therefore, let's go straight to the shortcomings that we would like to eliminate.





  • ( ), . ( - , .)





  • , , . - visit ( , - ). - visit .





  • .





?

  • , - , dynamic_cast static_cast .





  • , .





  • .





.





Source:
template< class T >
struct AbstractVisitor
{
    virtual ~AbstractVisitor() = default;
    virtual void visit( T& ) = 0;
};
      
      







(: - visit , AbstractVisitor .. T , )





. .





TypeList AbstractVisitors. AbstractVisitors , .





Source:
template< class ... T >
struct TypeList
{

};

template< class T >
struct AbstractVisitor
{
    virtual ~AbstractVisitor() = default;
    virtual void visit( T& ) = 0;
};

template< class ...T >
struct AbstractVisitors;

template< class ... T >
struct AbstractVisitors< TypeList< T... > > : AbstractVisitor< T >...
{
};
      
      







.. , ( , ). Dispatcher.





Source:
template< class Functor, class ... T >
struct Dispatcher;

template< class Functor, class ... T >
struct Dispatcher< Functor, TypeList< T... > > : AbstractVisitors< TypeList< T... > >
{
    Dispatcher( Functor functor ) : functor( functor ) {}

    Functor functor;
};
      
      







- visit.





Resolver, . Dispatcher Resolver-.





, (CRTP) Dispatcher Resolver.





( CRTP ).





Source:
template< class Dispatcher, class T >
struct Resolver : AbstractVisitor< T >
{
    void visit( T& obj ) override 
    {
        static_cast< Dispatcher* >( this )->functor( obj );
    };
};

template< class Functor, class ... T >
struct Dispatcher< Functor, TypeList< T... > > : AbstractVisitors< TypeList< T... > >, Resolver< Dispatcher< Functor, TypeList< T... > >, T >...
{
    Dispatcher( Functor functor ) : functor( functor ) {}

    Functor functor;
};
      
      







.





Dispatcher. , Dispatcher , ?





, Resolver, Dispatcher .





, AbstractVisitor< T >.( .)





Source:
template< class ... T >
struct AbstractVisitors< TypeList< T... > > : virtual AbstractVisitor< T >...
{
};

template< class Dispatcher, class T >
struct Resolver : virtual AbstractVisitor< T >
{
    void visit( T& obj ) override 
    {
        static_cast< Dispatcher* >( this )->functor( obj );
    };
};
      
      







(AbstractObject) - (Object1, Object2), .





test test, .





:





Source:
struct Object1;
struct Object2;

using ObjectList = TypeList< Object1, Object2 >;

struct AbstractObject
{
    virtual void accept( AbstractVisitors< ObjectList >& visitor ) = 0; 
};

struct Object1 : AbstractObject
{
    void accept( AbstractVisitors< ObjectList >& visitor ) override 
    { 
        static_cast< AbstractVisitor< Object1 >& >( visitor ).visit( *this );  
    };
};

struct Object2 : AbstractObject
{
    void accept( AbstractVisitors< ObjectList >& visitor ) override 
    { 
        static_cast< AbstractVisitor< Object2 >& >( visitor ).visit( *this );
    };
};

void test( Object1& obj )
{
    std::cout << "1" << std::endl;
}

template< class T >
void test( T& obj )
{
    std::cout << "2" << std::endl;
}

int main()
{
    Object1 t1,t2,t3,t4;
    Object2 e1,e2,e3;

    std::vector< AbstractObject* > vector = { &t1, &e1, &t2, &t3, &e2, &e3, &t4 };
		
    auto l = []( auto& obj ){ test(obj); };
    Dispatcher<decltype(l), ObjectList> dispatcher;
  
    for( auto* obj : vector )
    {
        obj->accept( dispatcher );
    }
}
      
      







(: visitor.visit( *this )



, , .)





Dispatcher - , .





, - accept AbstractObject, Object1 Object2, .. , .





Dispatchable. C - accept - . Dispatcher.





DISPATCHED, - accept Object1 Object2.





Source:
template< class TypeList >
struct Dispatchable
{
    virtual ~Dispatchable() = default;
    virtual void accept( AbstractVisitors< TypeList >& ) = 0;

    template< class Functor >
    void dispatch( Functor functor )
    {
        static Dispatcher< decltype(functor), TypeList > dispatcher( functor );
        accept( dispatcher );
    };
};

#define DISPATCHED( TYPE, TYPE_LIST ) \
    void accept( AbstractVisitors< TYPE_LIST >& visitor ) override \
    { \
        static_cast< AbstractVisitor< TYPE >& >( visitor ).visit( *this );  \
    }
      
      











AbstractObject Dispatchable. Object1 Object2 DISPATCHED.





Source:

struct Object1;
struct Object2;

using ObjectList = TypeList< Object1, Object2 >;

struct AbstractObject : Dispatchable< ObjectList >
{
};

struct Object1 : AbstractObject
{
    DISPATCHED( Object1, ObjectList )
};

struct Object2 : AbstractObject
{
    DISPATCHED( Object2, ObjectList )
};

      
      







, - accept . .





:





Source:
void test( Object1& obj )
{
    std::cout << "1" << std::endl;
}

template< class T >
void test( T& obj )
{
    std::cout << "2" << std::endl;
}

int main()
{
    Object1 t1,t2,t3,t4;
    Object2 e1,e2,e3;

    std::vector< AbstractObject* > vector = { &t1, &e1, &t2, &t3, &e2, &e3, &t4 };

    for( auto* obj : vector )
    {
        obj->dispatch( []( auto& obj ) { test(obj); } );
    }
}
      
      











Output:

1





2





1





1





2





2





1





  • , , .





  • , .. .





  • We can take full advantage of the function templates.





  • It remains possible to use the usual implementation of the visitor, it is enough to just make your inheritor from AbstractVisitors and pass it to the accept function .









What are the disadvantages?





  • Additional indirection, since Dispatcher contains a functor.









Link to code in compiler explorer.





Full source:
#include <type_traits>
#include <iostream>
#include <vector>

template< class ... T >
struct TypeList
{

};

template< class T >
struct AbstractVisitor
{
    virtual ~AbstractVisitor() = default;
    virtual void visit( T& ) = 0;
};

template< class ...T >
struct AbstractVisitors;

template< class ... T >
struct AbstractVisitors< TypeList< T... > > : virtual AbstractVisitor< T >...
{
};

template< class Dispatcher, class T >
struct Resolver : virtual AbstractVisitor< T >
{
    void visit( T& obj ) override 
    {
        static_cast< Dispatcher* >( this )->functor( obj );
    };
};

template< class Functor, class ... T >
struct Dispatcher;

template< class Functor, class ... T >
struct Dispatcher< Functor, TypeList< T... > > : AbstractVisitors< TypeList< T... > >, Resolver< Dispatcher< Functor, TypeList< T... > >, T >...
{
    Dispatcher( Functor functor ) : functor( functor ) {}

    Functor functor;
};

template< class TypeList >
struct Dispatchable
{
    virtual ~Dispatchable() = default;
    virtual void accept( AbstractVisitors< TypeList >& ) = 0;

    template< class Functor >
    void dispatch( Functor functor )
    {
        static Dispatcher< decltype(functor), TypeList > dispatcher( functor );
        accept( dispatcher );
    };
};

#define DISPATCHED( TYPE, TYPE_LIST ) \
    void accept( AbstractVisitors< TYPE_LIST >& visitor ) override \
    { \
        static_cast< AbstractVisitor< TYPE >& >( visitor ).visit( *this );  \
    }

struct Object1;
struct Object2;

using ObjectList = TypeList< Object1, Object2 >;

struct AbstractObject : Dispatchable< ObjectList >
{
};

struct Object1 : AbstractObject
{
    DISPATCHED( Object1, ObjectList )
};

struct Object2 : AbstractObject
{
    DISPATCHED( Object2, ObjectList )
};

void test( Object1& obj )
{
    std::cout << "1" << std::endl;
}

template< class T >
void test( T& obj )
{
    std::cout << "2" << std::endl;
}

int main()
{
    Object1 t1,t2,t3,t4;
    Object2 e1,e2,e3;

    std::vector< AbstractObject* > vector = { &t1, &e1, &t2, &t3, &e2, &e3, &t4 };

    for( auto* obj : vector )
    {
        obj->dispatch( []( auto& obj ) { test(obj); } );
    }
}
      
      














All Articles