Search code examples
c++visitor-pattern

How to write includes with Visitor Pattern? [Simple Example]


I can't seem to figure out how to write the includes of the Visitor Pattern with this simple example. No matter what I do I always end up with circular dependencies, but no other way makes sense.

Also I apologize for the different header guards (pragma vs. #ifndef), I was testing #pragma out and hadn't updated the files yet.

Client.cpp

#include "OneVisitor.h"
#include "DataStructure.h"

int main (int argc, char * argv [])
{
    OneVisitor v;
    DataStructure d;

}

DataStructure.h

#ifndef _DATA_STRUCTURE_H_
#define _DATA_STRUCTURE_H_

#include "ElementA.h"

class DataStructure {

    public:
        DataStructure (Visitor & v)
        {   
            std::cout << "ACCEPTS";
            a->accept(v);
        };
    
    private:
        ElementA * a;

};

#endif

Element.h

#ifndef _ELEMENT_H_
#define _ELEMENT_H_

#include "Visitor.h"
#include <iostream>

class Element {

    public:
        virtual void accept (Visitor & v) = 0;

        void talk ()
        {
            std::cout << "ELEMENT TALKING";
        };

};

#endif

ElementA.h

#pragma once

#include "Element.h"
#include "Visitor.h"

class ElementA : public Element {

    public:
        virtual void accept (Visitor & v) override
        {
            v.Visit(*this);
        };

        void talk ()
        {
            std::cout << "ELEMENT A TALKING";
        };

};

Visitor.h

#ifndef _VISITOR_H_
#define _VISITOR_H_

#include "ElementA.h"

class Visitor {

    public:
        virtual void Visit (ElementA & a) = 0;
    
};

#endif

OneVisitor.h

#ifndef _ONE_VISITOR_H_
#define _ONE_VISITOR_H_

#include "Visitor.h"

class OneVisitor : public Visitor {

    public:
        virtual void Visit (ElementA & a) override
        {
            a.talk();
        };
};

#endif

When I run this, I get the error "Visitor has not been declared" in Element.h, ElementA.h, ElementB.h. How can i get Visitor defined in these classes without causing circular dependencies?


Solution

  • A visitor is a very abstract concept, and it makes sense to template it in this case. Using templates allows us to get rid of circular dependencies, and simplify things considerably.

    // Visitor.hpp
    #pragma once
    
    template<class T>
    class Visitor {
       public:
        virtual void visit(T& item) = 0;
        virtual ~Visitor() = default; 
    };
    

    Now, if you want to have visitor for Element, you could just use Visitor<Element>:

    // Element.hpp
    #pragma once
    #include "Visitor.hpp"
    #include <iostream>
    
    class Element
    {
       public:
        virtual void accept(Visitor<Element>& v) 
        {
            v.visit(*this); 
        }
        virtual void talk() {
            std::cout << "Element talking!\n"; 
        }
        virtual ~Element() = default; 
    };
    

    Now that we have these things, we can also write a function to convert lambdas into visitors:

    template<class T, class Func>
    struct FunctionVisitor : public Visitor<T> {
        Func func;
        FunctionVisitor() = default;
        FunctionVisitor(FunctionVisitor const&) = default;
        FunctionVisitor(FunctionVisitor&&) = default;
    
        FunctionVisitor(Func const& func) 
          : func(func) 
        {
        }
        void visit(T& item) override {
            func(item); 
        }
    };
    
    template<class T, class Func>
    FunctionVisitor<T, Func> makeVisitor(Func const& f) {
        return FunctionVisitor<T, Func>(f); 
    }
    

    Bringing it all together

    This allows us to write nice code like this:

    #include "Element.hpp"
    #include "Visitor.hpp"
    #include <vector>
    
    class ElemA : public Element {
       public:
        void talk() override {
            std::cout << "ElemA talking!\n";
        }
    };
    class ElemB : public Element {
       public:
        void talk() override {
            std::cout << "ElemB talking!\n";
        }
    };
    class ElemC : public Element {
       public:
        void talk() override {
            std::cout << "ElemC talking!\n";
        }
    };
    
    void visitAll(std::vector<Element*>& elements, Visitor<Element>& visitor) {
        for(auto e : elements) {
            e.accept(visitor); 
        }
    }
    
    int main() {
        std::vector<Element*> elements {
            new ElemA(),
            new ElemB(),
            new ElemC()
        };
    
        auto talk = [](Element& e) { e.talk(); };
    
        visitAll(elements, makeVisitor<Element>(talk));
    }