Is there a trick to decide which derived class is used in a templated class hierarchy, or at least a way to call the correct function? I've put together a simple snippet, which describes the issue.
class Visitable {
public:
template <typename Visitor> void accept(Visitor &visitor) {
std::cout << "Base";
}
};
class Base : public Visitable {};
class DerivedA : public Base {
public:
template <typename Visitor> void accept(Visitor &visitor) {
std::cout << "DerivedA";
}
};
class DerivedB : public Base {
public:
template <typename Visitor> void accept(Visitor &visitor) {
std::cout << "DerivedB";
}
};
template <class T> class Visitor {
public:
virtual void visit(T &) = 0;
};
template <typename... Ts> class Visitor_N : public Visitor<Ts>... {
public:
using Visitor<Ts>::visit...;
};
// this is a smiplified Visitor, just to have one at place for the example.
// Real visitors are more complex and do different stuff for different types.
class SampleCounter : public Visitor_N<Base, DerivedA, DerivedB> {
public:
int i = 0;
void visit(Base &t) override { i++; }
void visit(DerivedA &t) override { i++; }
void visit(DerivedB &t) override { i++; }
};
int main() {
/**
* The following few lines off course work.
*/
SampleCounter counter;
DerivedA derivedA;
derivedA.accept(counter); // It knows the correct type as I call it from
// DerivedA directly
/**
* Here it calls the accept function of Base, as it should be according to c++ rules.
*/
Base *base = new DerivedA();
base->accept(counter);
delete base;
}
I've tried to implement Base
in a kind of CRTP form, so the base
knows all classes implementing it. Then maybe find the type by using covariant return types (described here) and call accept
by explicitly calling the right accept
function. With this approach I ran into different kinds of Incomplete Type
issues.
Another idea was to apply a kind of pointer (type of derived class) in the base class to decide which class I'm working with. Couldn't get this to work either.
Other ideas I'm not sure are possible:
Type map
is there a way to implement a compile time type map (maybe short -> typename
) and automatically add entries and associated classes? Maybe like the following (in the style of cp_map):
template <typename ... Ps>
class cp_map { /* code like described in https://stackoverflow.com/a/19503045/1893976 */}
template<typename Derived>
class base : public Visitor, public Derived {
template <typename Visitor> void accept(Visitor &visitor) {
// not real cpp code:
auto type = cp_map::getValue(this->getType())
// call accept on right type somehow
}
}
Enum
Following the above type map, would there be a way to implement an enum in a variadic template way, basically doing the same stuff as the type map? Kind of...:
template <typename ... Ts>
enum type_map {
Ts...
}
I guess it is somehow possible to find the derived type (as other SO-answers and questions suggest). But I need a little help in this more template-centric code.
Edit: Why (complex) template classes
I began with a default visitor pattern: Visitor than had a method for each node type:
class ExampleVisitor : public Vistor {
virtual void visitDerivedA(){ /*... */}
virtual void visitDerivedB(){ /*... */}
// ...
}
This pattern was good and sufficient as long as I only had one AST tree with limited AST-Node types and vistors. As the application was getting bigger with multiple trees of different types I've splitted the tree in very general (templated) node classes. Even a "handler" for parent-functionality was outsourced as it does always the same thing, but for different types in different trees.
This led to Node-Definitions like the following:
class TypeDefinitionNode
: public AstNode<TypeDefinitionNode> // inherits base class
, public CompositeType<
TypeDefinitionNode,
BlockNode // type of child nodes
>
, public Parent<
TypeDefinitionNode,
DocumentNode // what type has the parent of this node
>
{ /** ... */ };
The complexity of defining a node type isn't that complex and it's very clear what functionality the class has (through inheritance). The main benefit of it all was the easier access of traversing the tree as all types are clear at compile time. This allows eg. the following (one example out of many):
auto p = f.getParentOfType<Document>();
For many such functions, there's no longer a visitor required. But it still got the visitor pattern for existing algorithms.