Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How can I provide a default implementation for an overridable method?

I am playing around with the Visitor pattern, and I have the following bit of code which compiles:

class DerivedVisitee;

class Visitor
{
public:
    void visit(DerivedVisitee &v);
};

class Visitee
{
public:
    virtual void accept(Visitor &v) = 0;
};

class DerivedVisitee : public Visitee
{
public:
    void accept(Visitor &v) { v.visit(*this); }
};

I would like to provide a default visit method for all descendants of Visitee. As such, I tried to do the following:

class DerivedVisitee;

class Visitor
{
public:
    void visit(DerivedVisitee &v);
};

class Visitee
{
public:
    virtual void accept(Visitor &v) { v.visit(*this); } // added implementation here
};

class DerivedVisitee : public Visitee
{
    // removed overridden method
};

But compilation fails with 'void Visitor::visit(DerivedVisitee &)' : cannot convert argument 1 from 'Visitee' to 'DerivedVisitee &' (MSVC). Can you explain why this happens, and what is a correct method for doing what I'm trying to do?

EDIT: Visitor::visit needs to work on DerivedVisitee objects only; to put it another way, I intend to use multiple overloaded Visitor::visit methods with different implementations, for different descendants of Visitee.

like image 784
Dan Nestor Avatar asked Jan 20 '26 15:01

Dan Nestor


1 Answers

The basic answer is: you cannot in pure object oriented code.

By nature the Visitor pattern is about passing to visit the derived type, and in Visitee said type is unknown (it's a runtime property).


In C++, there exists a pattern called CRTP:

template <typename Derived, typename Base>
class VisiteeHelper: public Base {
public:
    virtual void accept(Visitor& v) override {
        Derived& d = static_cast<Derived&>(*this);
        v.visit(d);
}; // class VisiteeHelper

and then you can derive from this:

// And there we see the "Curiously Recurring" part:
class DerivedVisitee: public VisiteeHelper<DerivedVisitee, Visitee> {
}; // class DerivedVisitee

class MoreDerivedVisitee: public VisiteeHelper<MoreDerivedVisitee, DerivedVisitee> {
}; // MoreDerivedVisitee

It's up to you to decide whether you prefer the dead-simple boilerplate or the smart (but potentially confusing) CRTP solution.

Personally, unless you have multiple overloads of accept (up to 4 per type by overloading on const-ness), I would not bother. It's about as much work to write the accept by hand, and it's dead simple, and immediately understandable.

like image 122
Matthieu M. Avatar answered Jan 23 '26 06:01

Matthieu M.



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!