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?
CodePudding user response:
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.
CodePudding user response:
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;
};