samedi 4 avril 2015

Traversing a tree during compile time, with visiting actions

To make a preorder traversal of a nested pack of types (i.e. a tree of types), and then carry out an action at each leaf, I've worked out the algorithm already (and tested to work correctly):



template <typename, typename> struct Visit;
template <typename, typename, bool> struct VisitHelper;

template <template <typename...> class P1, template <typename...> class P2, typename First, typename... Rest, typename... Visited>
struct Visit<P1<First, Rest...>, P2<Visited...>> :
VisitHelper<P1<First, Rest...>, P2<Visited...>, HasChildren<First>::value> {};

template <template <typename...> class P1, template <typename...> class P2, typename... Visited>
struct Visit<P1<>, P2<Visited...>> { // End of recursion. Every leaf in the tree visited.
using type = P2<Visited...>;
};

// Here First has children, so visit its children, after which visit Rest...
template <template <typename...> class P1, template <typename...> class P2, typename First, typename... Rest, typename... Visited>
struct VisitHelper<P1<First, Rest...>, P2<Visited...>, true> :
Visit<typename Merge<First, P2<Rest...>>::type, P1<Visited...>> {};

// Here First has no children, so the "leaf action" is carried out. In this case, it is appended to P<Visited...> as a simple example.
template <template <typename...> class P1, template <typename...> class P2, typename First, typename... Rest, typename... Visited>
struct VisitHelper<P1<First, Rest...>, P2<Visited...>, false> :
Visit<P1<Rest...>, typename Append<First, P2<Visited...>>::type> {};


The helper functions are self-explanatory. For completeness, here they are:



template <typename T>
struct HasChildren : std::false_type {};

template <template <typename...> class P, typename... Types>
struct HasChildren<P<Types...>> : std::true_type {};

template <typename, typename> struct Merge;

template <template <typename...> class P1, template <typename...> class P2, typename... Ts, typename... Us>
struct Merge<P1<Ts...>, P2<Us...>> {
using type = P1<Ts..., Us...>;
};

template <typename, typename> struct Append;

template <typename T, template <typename...> class P, typename... Ts>
struct Append<T, P<Ts...>> {
using type = P<Ts..., T>;
};


My "leaf action" here is simply storing the type of each node visited, as my test shows:



template <typename...> struct Pack {};
template <typename...> struct Group {};
template <typename...> struct Wrap {};
struct Object {};

int main() {
std::cout << std::boolalpha << std::is_same<
Visit<Pack<Wrap<int, Object, double>, bool, Group<char, Pack<int, double, Pack<char, Wrap<char, long, short>, int, Object>, char>, double>, long>, Pack<>>::type,
Pack<int, Object, double, bool, char, int, double, char, char, long, short, int, Object, char, double, long>
>::value << std::endl; // true
}


But my problem now is: What if there is to be an action carried out at each non-leaf, and such "non-leaf action" is carried out AFTER the children are visited? How to remember the non-leaf?


Here is my motivation for this. Consider this loop (which is carried out during run-time):



template <int N>
void Graph<N>::topologicalSortHelper (int v, std::array<bool, N>& visited, std::stack<int>& stack) {
visited[v] = true; // Mark the current node as visited.
for (int x : adjacentVertices[v]) // Repeat for all the vertices adjacent to this vertex.
if (!visited[x])
topologicalSortHelper (x, visited, stack);
stack.push(v); // Push current vertex to 'stack' which stores the result.
}


There are 2 "non-leaf actions" here. visited[v] = true; is carried out before visiting the children, so that is no problem (just make the change in the inheritance), but the real problem is stack.push(v);. How to remember v after all the children have been visited?


So more generally, how to modify my code above so that an action at a non-leaf can be carried out after the children have been visited (all done during compile-time).


Aucun commentaire:

Enregistrer un commentaire