Let say I have a visitor in Java.
interface Visitor<T> {
T visitA(VisitableA a);
T visitB(VisitableB b);
}
abstract class Visitable {
abstract <T> T accept(Visitor<T> visitor);
}
class VisitableA extends Visitable {
@Override <T> T accept(Visitor<T> visitor) {
return visitor.visitA(this);
}
}
class VisitableB extends Visitable {
@Override <T> T accept(Visitor<T> visitor) {
return visitor.visitB(this);
}
}
Now, if I wanted to take that and write it in C , I could do it like this
class VisitableA;
class VisitableB;
template <typename T> class Visitor {
virtual T visitA(VisitableA& a) = 0;
virtual T visitB(VisitableB& b) = 0;
};
class MyVisitor : public Visitor<int> {
int visitA(VisitableA& a) override { /* do something */ return 42; }
int visitB(VisitableB& b) override { /* do something */ return 1337; }
};
class Visitable {
template <typename T> virtual T accept(Visitor<T>& visitor) = 0;
};
class VisitableA : public Visitable {
template <typename T> T accept(Visitor<T>& visitor) override { return visitor.visitA(*this); }
};
class VisitableB : public Visitable {
template <typename T> T accept(Visitor<T>& visitor) override { return visitor.visitB(*this); }
};
But this does not compile, since I cannot have a virtual templated method.
I could use std::any
(or something similar) but I was wondering if there was a way do implement this without it.
It must be noted that the accept
return type is not known at the construction of the Visitable
s.
C 20 is fine if required.
CodePudding user response:
Do what Java does under the hood.
Replace T
with std::any
.
For Visitable
make a base class that returns any
to both methods. Then make a template class that implements the any
returning as final
, and creates a T
returning Visit
as pure for implementors to implement.
Visitable
has two methods; a pure virtual private one taking a Visitable
, and a public template taking a VitiableT
template. It calls the pure virtual one and casts the any
to a T
.
Java generics are basically just writing that glue for you.
You may have to do casting under the hood, or look at typeids, depending on how you use this.
struct VistableA; struct VistableB;
struct Visitor{
virtual std::any visitA(VisitableA&)=0;
virtual std::any visitB(VisitableB&)=0;
};
template<class T>
struct VisitorT:Visitor{
std::any visitA(VisitableA& a) final{ return visitTA(a); }
T visitTA(VisitableA& a) = 0;
std::any visitB(VisitableB& b) final{ return visitTB(b); }
T visitTB(VisitableB& b) = 0;
};
class MyVisitor : public VisitorT<int> {
int visitTA(VisitableA& a) override { /* do something */ return 42; }
int visitTB(VisitableB& b) override { /* do something */ return 1337; }
};
class Visitable {
template <typename T> T accept(VisitorT<T>& visitor) {
return std::any_cast<T>(accept(static_cast<Visitor&>(visitor)));
}
virtual std::any accept(Visitor& visitor)=0;
};
class VisitableA : public Visitable {
std::any accept(Visitor& visitor) override { return visitor.visitA(*this); }
};
class VisitableB : public Visitable {
std::any accept(Visitor& visitor) override { return visitor.visitB(*this); }
};
now this being C there are another half dozen ways to solve your problem that do not emulate Java. But this is the Java esque way.
Note because we are writing the glue code Java writes for you, if we make a mistake in the glue we aren't type safe.
And in VisitableA
Java checks that the same "generic type" is used for all of the T
s; in C I used a std::any
, which could be mixed up with other "generics" and break some type safety. You could imagine a complex system of tagged std::any
to avoid this.