#pragma once #include "fanout_groups.hpp" #include #include #include // Compile-time topological sort + cycle detection over a TypeList>. // // Nodes are identified by type (not by index), since two different node types // are always distinct vertices. The sort produces a TypeList of node types in // topological order (sources first). // // cycle detection: if DFS revisits a node currently on the stack → static_assert. namespace kpn::tmp { // ── Collect all unique node types from an expanded edge list ────────────────── template> struct all_node_types; template struct all_node_types, Seen> { using type = Seen; }; template struct all_node_types, Seen> { using S1 = std::conditional_t< already_processed::value, Seen, append_t>; using S2 = std::conditional_t< already_processed::value, S1, append_t>; using type = typename all_node_types, S2>::type; }; // ── Collect successors (dst node types) of a given src node type ────────────── template struct successors; template struct successors> { using type = TypeList<>; }; template struct successors> { using rest = typename successors>::type; using type = std::conditional_t< std::is_same_v, std::conditional_t< already_processed::value, rest, append_t>, rest>; }; // ── DFS state ───────────────────────────────────────────────────────────────── // Color: 0=white(unseen), 1=grey(on stack), 2=black(done) // We track grey nodes as a TypeList to detect back-edges. template struct is_grey : already_processed {}; // DFS result: has_cycle flag + topo order (black nodes appended post-visit) template struct DfsResult { static constexpr bool has_cycle = Cycle; using topo = TopoList; }; template struct dfs_node; // Iterate successors template struct dfs_successors; template struct dfs_successors, EdgeList, GreyList, BlackList, TopoList, CycleSoFar> { static constexpr bool has_cycle = CycleSoFar; using black = BlackList; using topo = TopoList; }; template struct dfs_successors, EdgeList, GreyList, BlackList, TopoList, CycleSoFar> { // Visit Head using visit = dfs_node; static constexpr bool cycle1 = CycleSoFar || visit::has_cycle; // Continue with remaining successors using updated black/topo using rest = dfs_successors, EdgeList, GreyList, typename visit::black, typename visit::topo, cycle1>; static constexpr bool has_cycle = rest::has_cycle; using black = typename rest::black; using topo = typename rest::topo; }; template struct dfs_node { // Already black — skip static constexpr bool already_done = already_processed::value; // On stack — cycle static constexpr bool on_stack = is_grey::value; // Visit successors (only if not already done / on stack) using succs = typename successors::type; using new_grey = append_t; using visit_succs = dfs_successors; static constexpr bool has_cycle = already_done ? false : on_stack ? true : visit_succs::has_cycle; // Append node to topo after all successors (post-order = reverse topo) using topo_after = std::conditional_t< already_done || on_stack, TopoList, append_t>; using black = std::conditional_t< already_done || on_stack, BlackList, append_t>; using topo = topo_after; }; // ── Top-level DFS over all nodes ────────────────────────────────────────────── template struct dfs_all; template struct dfs_all, EdgeList, BlackList, TopoList, CycleSoFar> { static constexpr bool has_cycle = CycleSoFar; using topo = TopoList; }; template struct dfs_all, EdgeList, BlackList, TopoList, CycleSoFar> { using visit = dfs_node, BlackList, TopoList>; static constexpr bool cycle1 = CycleSoFar || visit::has_cycle; using rest = dfs_all, EdgeList, typename visit::black, typename visit::topo, cycle1>; static constexpr bool has_cycle = rest::has_cycle; using topo = typename rest::topo; }; // ── Public interface ─────────────────────────────────────────────────────────── // topo_sort: // ::topo — TypeList of node types, sources first (start order) // ::has_cycle — true if a cycle was detected template struct topo_sort { using Nodes = typename all_node_types::type; using result = dfs_all, TypeList<>, false>; // DFS post-order gives reverse topo; reverse the list for sources-first order. // Reversing a TypeList: template> struct reverse_list; template struct reverse_list, Acc> { using type = Acc; }; template struct reverse_list, Acc> : reverse_list, append_t> {}; // wrong direction intentionally // Post-order appends children before parent, so the list is already // reverse-topo (sinks first). Reverse it to get sources first. template> struct rev; template struct rev, Acc> { using type = Acc; }; template struct rev, Acc> : rev, TypeList> { // prepend H to Acc: TypeList }; // Simpler: just reverse by prepending template> struct rev2 { using type = A; }; template struct rev2, TypeList> : rev2, TypeList> {}; static constexpr bool has_cycle = result::has_cycle; using topo = typename rev2::type; }; // ── Duplicate-tag detection ─────────────────────────────────────────────────── // // Two user nodes share a (Func, UniqueTag) pair iff they have the same type. // has_duplicate_tags_v is true if any two user node types are identical. template struct type_in_list : std::false_type {}; template struct type_in_list> : std::conditional_t, std::true_type, type_in_list>> {}; template struct has_duplicate_node_types : std::false_type {}; template struct has_duplicate_node_types> : std::conditional_t>::value, std::true_type, has_duplicate_node_types>> {}; template inline constexpr bool has_duplicate_tags_v = has_duplicate_node_types< typename all_node_types>::type>::value; } // namespace kpn::tmp