2

This is a follow up on No user defined conversion when using standard variants and visitor pattern

I need to implement a templated version of the visitor pattern as shown below, however it looks like the accept function has to be virtual which is not possible. Could you please help me?

#include <variant>
#include <iostream>

class Visitable //I need this to be non-templated (no template for Visitable!!): Otherwise I could use CRTP to solve this issue.
{
public:
    virtual ~Visitable() = default;

    template<typename Visitor> 
    /*virtual*/ double accept(Visitor* visitor) //I can't do virtual here.
    {
        throw("I don't want to end up here");
    };

protected:
    Visitable() = default;
};

struct DoubleVisitable : public Visitable
{
    template<typename Visitor> 
    double accept(Visitor* visitor) 
    {
        return visitor->visit(*this);
    };

    double m_val = 1.0;
};

struct StringVisitable : public Visitable
{
    template<typename Visitor> 
    double accept(Visitor* visitor) 
    {
        return visitor->visit(*this);
    };
    double m_val = 0.0;
};

template<typename... args>
class Visitor
{
public:
    virtual ~Visitor() = default;

    virtual double visit(typename std::variant<args...> visitable)
    {
        auto op = [this](typename std::variant<args...> visitable) -> double { return this->apply(visitable); };
        return std::visit(std::ref(op), visitable);
    }

    virtual double apply(typename std::variant<args...> visitable) = 0;

    Visitor() = default;
};

class SubVisitor : public Visitor<DoubleVisitable, StringVisitable>
{
public:
    virtual ~SubVisitor() = default;
    SubVisitor() : Visitor<DoubleVisitable, StringVisitable>() {};
    
    virtual double apply(std::variant<DoubleVisitable, StringVisitable> visitable) override
    {
        return std::visit(            
            [this](auto&& v){return process(v);},
            visitable
        );
    };

    virtual double process(const StringVisitable& visitable)
    {
        std::cout << "STRING HANDLED" << std::endl;
        return 0.0;
    }

    virtual double process(const DoubleVisitable& visitable)
    {
        std::cout << "DOUBLE HANDLED" << std::endl;
        return 1.0;
    }
};



int main(int argc, char* argv[])
{
    SubVisitor visitor;
    DoubleVisitable visitable;
    visitable.accept(&visitor);

    //I want to be doing this:
    Visitable* doubleV = new DoubleVisitable();
    doubleV->accept(&visitor);
    delete doubleV;
    return 1;
}

The code is here Link. Could you please help me make this not throw but collapses to the right child class DoubleVisitable or StringVisitable. It looks like I need virtual templated member function which is not possible as mentioned here Can a class member function template be virtual?

Vero
  • 313
  • 2
  • 9
  • What is the use case? – Rulle Dec 03 '22 at 20:56
  • Visitable is an event (there are many of them and I don't want it to be a template) in a game engine that needs to be processed by the visitor and return another event which I am representing as a double – Vero Dec 03 '22 at 21:04

2 Answers2

2

In C++, there are no template virtual functions. This does not exist. What you can do is either:

  • have an accept method for each class you'd like to visit (each descendant)
  • have a std::variant<> of implementations instead of inheritance.
lorro
  • 10,687
  • 23
  • 36
  • How can I do the first one, I thought this is what I am doing and you can see it failing? I missed something maybe. For the second point I don't really see what you mean sorry :) Thanks very much for looking anyways! – Vero Dec 03 '22 at 17:21
  • @Vero : don't templetize, add an implementation (literally) for each callable. As for variants, that's perhaps easier: look up `std::variant` and `std::visit` on e.g. cpprreference. – lorro Dec 03 '22 at 17:37
  • Sorry, fail to manage. Could you please post more details? or code? – Vero Dec 03 '22 at 20:52
  • 1
    @Vero https://en.cppreference.com/w/cpp/utility/variant/visit https://en.cppreference.com/w/cpp/utility/variant Check the examples and the doc as well – lorro Dec 03 '22 at 20:55
  • The problem I couldn't solve is that the visit takes a arg which is a predefined type, in my case I am taking a Visitable, which is a base class, so I can't do the constexpr ifs as I know the type, I need the child type and I can't use CRTP as mentioned in the question. Also I can't handle all types in the base class Visitor, I need inheritance so it' dynamic to add processing other events i.e. Vistables. – Vero Dec 03 '22 at 21:07
  • @Vero The variant-based solution assumes that, instead of base class, you store a variant (of all possible 'descendants', or rather, implementations). Then you can visit it and use static polymorphism via visit. – lorro Dec 03 '22 at 21:59
  • Thanks again, I didn't want to ask but could you please put a response with code example please? – Vero Dec 03 '22 at 22:17
1

It says in the question that Visitable cannot be a template. But is it allowed to inherit from a template class? And do you know all the possible visitors? If so, you could add a new template class that Visitable inherits from and that declares virtual methods for all the visitors:

template <typename ... T> class AcceptMethods {};
template <> class AcceptMethods<> {};
template <typename First, typename ... Rest>
class AcceptMethods<First, Rest...> : public AcceptMethods<Rest...> {
public:
  virtual double accept(First* ) = 0;
  virtual ~AcceptMethods() {}
};

typedef AcceptMethods<SubVisitor> AllAcceptMethods;

class Visitable : public AllAcceptMethods
{
public:
    virtual ~Visitable() = default;
};

In the above code, we are just listing SubVisitor, but AcceptMethods is variadic so it could be typedef AcceptMethods<A, B, C, D, AndSoOn> AllAcceptMethods;.

Then we add another template class WithGenericAcceptMethod whose purpose is to implement the accept methods declared by AcceptMethods by calling a template method acceptT:

template <typename This, typename ... T> class WithGenericAcceptMethod {};
template <typename This> class WithGenericAcceptMethod<This, AcceptMethods<>> : public Visitable {};
template <typename This, typename First, typename ... Rest>
class WithGenericAcceptMethod<This, AcceptMethods<First, Rest...>> : public WithGenericAcceptMethod<This, AcceptMethods<Rest...>> {
public:
  double accept(First* visitor) override {
    return ((This*)this)->template acceptT<First>(visitor);
  }
  virtual ~WithGenericAcceptMethod() {}
};

This class takes as first argument a This parameter in the spirit of CRTP. Then we can now let the specific visitable classes inherit from WithGenericAcceptMethod and implement the template acceptT method:

struct DoubleVisitable : public WithGenericAcceptMethod<DoubleVisitable, AllAcceptMethods>
{
    template<typename Visitor> 
    double acceptT(Visitor* visitor) 
    {
        return visitor->visit(*this);
    };

    double m_val = 1.0;
};

struct StringVisitable : public WithGenericAcceptMethod<StringVisitable, AllAcceptMethods>
{
    template<typename Visitor> 
    double acceptT(Visitor* visitor) 
    {
        return visitor->visit(*this);
    };
    double m_val = 0.0;
};
Rulle
  • 4,496
  • 1
  • 15
  • 21
  • 1
    Thanks so much, I am trying to understand, I think all type linked errors will be catched at compile time I hope. Still reading. Thanks so much! I see the recursive inheritance, I think that has no impact on performance as there is no overiding of the virtual function defined. – Vero Dec 03 '22 at 21:42
  • Thanks again, only issue I have is ```typedef AcceptMethods AllAcceptMethods;``` meaning I have always to add ```SubVisitor``` everytime I need to add a new one and there a loot of them, I will go with this if there is no other way. Thanks so much for taking the time really appreciated!! – Vero Dec 03 '22 at 22:15
  • 1
    Unfortunately I think you have to declare all the different visitor types, no matter if you go with this approach or the one from the other answer based on `std::variant`. The compiler needs to know the different Visitor types in order to generate code for each type. Actually, the approach with `std::variant` might be a bit more concise but it really depends on what you want to do... – Rulle Dec 03 '22 at 22:21
  • I want to have visitables inherting from a base visitable that are visited by visitors, and the latter visitors can inherit from a base visitable class, each child of the base visitor only expects and only implements the visit function for a subset of the base visitable children. I know this is not precise but just wanted to say it in words. Thanks so much, I am still trying to get the code using std::variant from @lorro. Thanks so much Rulle!! – Vero Dec 03 '22 at 22:26