1

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?

Community
  • 1
  • 1
Sam Goodin
  • 78
  • 6
  • Possible duplicate of [Resolve build errors due to circular dependency amongst classes](https://stackoverflow.com/questions/625799/resolve-build-errors-due-to-circular-dependency-amongst-classes) – drescherjm Apr 22 '19 at 22:21
  • Forward declarations solve this problem. https://stackoverflow.com/questions/4757565/what-are-forward-declarations-in-c Not an actual answer because I'm using a phone. – Peter Apr 22 '19 at 22:22
  • If `Visitor.h` includes `ElementA.h` then `ElementA.h` can not include `Visitor.h` – drescherjm Apr 22 '19 at 22:22
  • `Visitor.h` does not need to include `ElementA.h`. Just use a forward declaration of `ElementA` – drescherjm Apr 22 '19 at 22:23
  • You are not allowed to create names such as `_DATA_STRUCTURE_H_` –  Apr 22 '19 at 22:24
  • @drescherjm But if I replace #include "ElementA.h" with class ElementA;, I get errors of incomplete type in OneVisitor.h. – Sam Goodin Apr 22 '19 at 22:24
  • This is about the rules of naming (specifically about the names starting with an underscore): https://stackoverflow.com/questions/228783/what-are-the-rules-about-using-an-underscore-in-a-c-identifier – drescherjm Apr 22 '19 at 22:25
  • You need to implement Visit in `OneVisitor.cpp`. There you can add `#include "ElementA.h"` – drescherjm Apr 22 '19 at 22:26

2 Answers2

2

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));
}
Alecto Irene Perez
  • 10,321
  • 23
  • 46
2

By using a forward declaration of the class ElementA; in Visitor.h

#ifndef _VISITOR_H_
#define _VISITOR_H_

// Just use a forward declaration of the class ElementA;
// NOTE1: The include of ElementA.h is not needed anymore.
// NOTE2: The visitor.h doesn't need to know what is defined
// in ElementA, only your .cpp needs, this is how forward 
// declaration works.
class ElementA;

class Visitor {

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

};

#endif
Vuwox
  • 2,331
  • 18
  • 33
  • To execute, you should just need to call a->accept in DataStructure.h then, right? However when I do this, I can't get the a.talk() to call. – Sam Goodin Apr 22 '19 at 22:45