Visitor and double dispatch without overriding "accept" method in c++

693 Views Asked by At

Ok: here´s my problem: I have a base composit class which accepts a visitor an then iterates over its nodes. Works like a charm. But then, I have to used a derived from this composit and recognized that I have to override the "accept()" method in the derived class to have correct double dispatching (which I did not understand).

This brings out two flaws: first, I have to break the hidden structure of the base, and second, I have to duplicate code. To clearify, here´s my pseudo-code:

struct Visitor
{
    void visit( BaseComposit*)    { throw( "not expected"); };
    void visit( DerivedComposit*) { throw( "ok"); };
};

class BaseComposit 
{
private:  

    std::vector< BaseComposit*> nodes;

public:

    virtual void accept( Visitor* v)
    {
        v->visit( this);

        for( int i = 0; i < nodes.size(); i++)
            nodes[ i]->accept( v);
    }
};

class DerivedComposit : public BaseComposit
{
public:
};

Any ellegant solution on that ? Thank you !

Edit: added "virtual" to "accept()" to make it more precise...

2

There are 2 best solutions below

10
On

Any ellegant solution on that ?

Not really. That's what makes the visitor pattern a bit of a pain. Though you could mitigate the duplication a bit with the help of a template:

class BaseComposit 
{
private:  

    std::vector<BaseComposit*> nodes;

protected:

    template<class T>
    void accept_impl( Visitor* v, T* this_ )
    {
        v->visit( this_ );

        for(int i = 0; i < nodes.size(); i++)
            nodes[i]->accept(v);
    }

public:

    virtual void accept( Visitor* v ) { accept_impl( v, this ); }
};

Now the duplication that accept must incur is smaller.

Also, as @Oliv pointed out, your example really ought to have accept be a virtual function. Otherwise the whole thing won't work.


If you feel really adventurous, you can introduce a macro to ease the "injection" of accept into each class. Like so:

#define INJECT_ACCEPT() void accept( Visitor* v ) override { accept_impl( v, this ); }

But you'd still need to use it in each derived class to make the definition appear.

class DerivedComposit : public BaseComposit
{
public:
    INJECT_ACCEPT();
};

The semi-colon is allowed by a curious feature of the C++ grammar. So I suppose one may argue the above looks "natural".

2
On

If you want to force some code to be always executed, use the non-virtual interface (NVI) pattern:

class BaseComposit 
{
private:  
    std::vector<BaseComposit*> nodes;

    virtual void call_visit(Visitor* v) { v->visit(this); }

public:

    void accept(Visitor* v)
    {
        call_visit(v);

        for(int i = 0; i < nodes.size(); i++)
            nodes[i]->accept(v);
    }
};

class DerivedComposit : public BaseComposit
{
    void call_visit(Visitor* v) override { v->visit(this); }
public:
};