I am looking for an advice on how to implement a DAG in C++ using templates. The main idea is to design a kind of framework where users can bring their own classes (Nodes) to perform some work on the input provided by other Nodes. Given this relation between the classes a DAG seems like a natural choice. At the same time I would like to avoid relying on virtual abstract interfaces as I think it is clearer for users to implement a work method with a signature explicitly stating all required inputs, e.g. Node::process(const AnotherNodeA&, const AnotherNodeB&)
rather than Node::process(const set<AbstractNode*>&)
.
I think I figured out how to implement an hierarchy of types by using type lists. For example, the following implements a simple graph like this:
strict digraph "" {
NodeY -> Node1;
NodeX -> Node1;
Node1 -> NodeA;
NodeX -> Node2;
Node2 -> NodeB;
NodeY -> NodeB;
}
#include <iostream>
#include <typeinfo>
template<class... T> struct NodeList {};
template<typename... InpNodes>
struct Node
{
using Inputs = NodeList<InpNodes...>;
};
struct NodeX : Node<> {};
struct NodeY : Node<> {};
struct Node1 : Node<NodeX, NodeY> {};
struct Node2 : Node<NodeX> {};
struct NodeA : Node<Node1> {};
struct NodeB : Node<Node2, NodeY> {};
template <size_t D>
void print_list() {}
template <size_t D=0, typename N, typename... Rest>
void print_list(const N&, const Rest&... rest)
{
for (int i=0; i<D; ++i) std::cout << "\t";
std::cout << typeid(N).name() << "\n";
print_list<D+1>(typename N::Inputs());
print_list<D>(rest...);
}
template <size_t D=0, typename... Types>
void print_list(const NodeList<Types...>& lst)
{
print_list<D>(Types()...);
}
int main()
{
using NList = NodeList<NodeA, NodeB>;
print_list(NList());
return 0;
}
The above prints the hierarchy of the defined types:
NodeA
Node1
NodeX
NodeY
NodeB
Node2
NodeX
NodeY
Would std::pair<ChildNode, ParentNode>
be a good choice to implement the "edges" between the nodes, i.e. ChildNode -> ParentNode
? Can a set of such defined pairs be used to verify and/or sort the nodes topologically?
Node::process(const AnotherNodeA&, const AnotherNodeB&)
seems to be a very generic description. It looks like you want something likefor (each node in topological order) { node.process(node's predecessors); }
? – Machado