C++ Visitor Pattern with variable return types

Viewed 206

I have the following setup:

class IVisitor{
public:
    virtual SomeType visit(IVisitable& visitable) = 0;
};

class IVisitable{
public:
    virtual SomeType accept(IVisitor& visitor) = 0;
};

class ConcreteVisitor : public IVisitor{
public:
    SomeType visit(IVisitable& visitable) override {
        return calculateSomeStuff();
    }
};

class ConcertVisitable : public IVisitable{
public:
    SomeType accept(IVisitor& visitor) override {
        return visitor.visit(*this);
    }
};

But I want different Visitors to return different types. For this to work, IVisitable::accept() should also return different types.

This would result in the following (incorrect) code:

template <typename R>
class IVisitor{
public:
    virtual R visit(IVisitable& visitable) = 0;
};

class IVisitable{
public:
    template <typename R>
    virtual R accept(IVisitor<R>& visitor) = 0;
};

class ConcreteVisitorOne : public IVisitor<int>{
public:
    int visit(IVisitable& visitable) override {
        return calculateSomeStuff();
    }
};

class ConcreteVisitorTwo : public IVisitor<bool>{
public:
    bool visit(IVisitable& visitable) override {
        return calculateSomeStuff();
    }
};

class ConcertVisitable : public IVisitable{
public:
    int accept<int>(IVisitor<int>& visitor) override {
        return visitor.visit(*this);
    }
};

This, unfortunately, is not possible in C++, as the virtual function IVistitable::accept() can't be templated.

Does anybody know a way around this limitation?

2 Answers

Several options:

  • Don't return anything, handle the value inside the visitor. The visitor is completely free to store or handle any return value and type.

  • Return a variadic type (std::any or std::variant). Note that using them requires you to find out the actual type, which is kind-of similar to your original problem perhaps.

  • Inherit multiple IVisitor<> interfaces. This might be the easiest way even, although it's not necessary elegant.

Another option is to switch to std::variant (C++17) instead of double dispatch:

int i = std::visit([](auto& v){ return 42;}, some_variant);
bool b = std::visit([](auto& v){ return false;}, some_variant);
Related