Question

Related question: link.

In one of the answers to the question above, I was recommended to use the visitor pattern to resolve some of the issues with my class inheritance structure. However, I am not sure if it is possible to use it in my context because my derived classes can be non-type templates.

To showcase the problem I used a modified code from this source: http://sourcemaking.com/design_patterns/visitor/cpp/2. The example below does not compile because it is not possible to define a virtual template method. However, I believe, the code demonstrates what I am trying to achieve. Are there any alternatives solutions to the problem?

// 1. Add an accept(Visitor) method to the "element" hierarchy
class Element
{
  public:
    virtual void accept(class Visitor &v) = 0;
};

template <unsigned int N>
class This: public Element
{
  public:
     /*virtual*/void accept(Visitor &v);
    string thiss()
    {
        return "This";
    }
};

class That: public Element
{
  public:
     /*virtual*/void accept(Visitor &v);
    string that()
    {
        return "That";
    }
};

// 2. Create a "visitor" base class w/ a visit() method for every "element" type
class Visitor
{
  public:
    template<unsigned int N>
    virtual void visit(This<N> *e) = 0;
    virtual void visit(That *e) = 0;

};

 template<unsigned int N>
 /*virtual*/void This<N>::accept(Visitor &v)
{
  v.visit(this);
}

 /*virtual*/void That::accept(Visitor &v)
{
  v.visit(this);
}

// 3. Create a "visitor" derived class for each "operation" to do on "elements"
class UpVisitor: public Visitor
{
     /*virtual*/void visit(This *e)
    {
        cout << "do Up on " + e->thiss() << '\n';
    }
     /*virtual*/void visit(That *e)
    {
        cout << "do Up on " + e->that() << '\n';
    }

};

class DownVisitor: public Visitor
{
     /*virtual*/void visit(This *e)
    {
        cout << "do Down on " + e->thiss() << '\n';
    }
     /*virtual*/void visit(That *e)
    {
        cout << "do Down on " + e->that() << '\n';
    }

}; 

    int main() 
    {

        Element *list[] = 
        {
            new This<3>(), new That()
        };
        UpVisitor up; // 4. Client creates
         DownVisitor down; //    "visitor" objects
         for (int i = 0; i < 2; i++) list[i]->accept(up);
         for (int i = 0; i < 2; i++) list[i]->accept(down);
    }
Was it helpful?

Solution

The problem is your Visitor class is tightly coupled with classes that derive from Element. As you expand your design this is going to get in the way more than it already is. You can reduce/eliminate the right coupling by providing a "destination" class that defines all the requirements of a visitable object. Since the name of a derived classes is a common attribute you can place the storage and access to it into the destination class as well.

// 1. Define out visitor and destination interfaces
struct Destination
{
    Destination(const std::string& name) : name_(name) {}
    virtual std::string ident() const { return name_; }

    const std::string name_;
};

struct Visitor
{
    virtual void visit(Destination *e) = 0;
};

This keeps the requirements of the visitor separate from the Element class which seems to be your intention. Then your This and That classes inherit from Destination and provide the necessary implementations.

// 2. Define our element and it's derived classes
class Element
{
public:
    virtual void accept(class Visitor &v) = 0;
};

template <unsigned int N>
class This: public Element, public Destination
{
public:
    This() : Destination("This") {}
    virtual void accept(Visitor &v)
    {
        v.visit(this);
    }
};

class That: public Element, public Destination
{
public:
    That() : Destination("That") {}
    virtual void accept(Visitor &v)
    {
        v.visit(this);
    }
};

Now your up and down visitors are simplified into something like the following

// 3. Create a "visitor" derived class for each "operation" to do on "elements"
class UpVisitor: public Visitor
{
    void visit(Destination *e) {
        cout << "do Up on " + e->ident() << '\n';
    }
};

class DownVisitor: public Visitor
{
    void visit(Destination *e) {
        cout << "do Down on " + e->ident() << '\n';
    }
}; 

Although I did not change it in the solution above I recommend changing visit to take a reference instead of a pointer. Since C++ has no notion of a null reference this indicates that Destination is required where as a pointer could be considered optional.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top