@@ -0,0 +1,358 @@
|
||||
#pragma once
|
||||
#include <cstddef>
|
||||
#include <tuple>
|
||||
#include <type_traits>
|
||||
#include <utility>
|
||||
|
||||
// Metafunctions that scan a pack of Edge<> types, group edges by source port,
|
||||
// auto-insert FanoutNode<T,N> where N>1, and produce an expanded edge list
|
||||
// plus a tuple type for the owned fanout nodes.
|
||||
//
|
||||
// Key types produced:
|
||||
// expanded_edges_t<Edges...> — type list of SimpleEdge after fanout insertion
|
||||
// fanout_storage_t<Edges...> — std::tuple<FanoutNode<T0,N0>, ...> to own
|
||||
|
||||
namespace kpn::tmp {
|
||||
|
||||
// ── Type list ─────────────────────────────────────────────────────────────────
|
||||
|
||||
template<typename... Ts>
|
||||
struct TypeList {};
|
||||
|
||||
template<typename List, typename T>
|
||||
struct append;
|
||||
template<typename... Ts, typename T>
|
||||
struct append<TypeList<Ts...>, T> { using type = TypeList<Ts..., T>; };
|
||||
template<typename List, typename T>
|
||||
using append_t = typename append<List, T>::type;
|
||||
|
||||
template<typename A, typename B>
|
||||
struct concat;
|
||||
template<typename... As, typename... Bs>
|
||||
struct concat<TypeList<As...>, TypeList<Bs...>> { using type = TypeList<As..., Bs...>; };
|
||||
template<typename A, typename B>
|
||||
using concat_t = typename concat<A, B>::type;
|
||||
|
||||
// ── Source port identity (used as compile-time map key) ───────────────────────
|
||||
|
||||
// Two edges share a source port when their SrcNode type AND SrcIdx are identical.
|
||||
template<typename SrcNode, std::size_t SrcIdx>
|
||||
struct SrcPort {};
|
||||
|
||||
template<typename Edge>
|
||||
using src_port_of = SrcPort<typename Edge::src_node_t, Edge::src_idx>;
|
||||
|
||||
// ── Count occurrences of a SrcPort in an edge list ───────────────────────────
|
||||
|
||||
template<typename Port, typename EdgeList>
|
||||
struct count_port;
|
||||
|
||||
template<typename Port>
|
||||
struct count_port<Port, TypeList<>> : std::integral_constant<std::size_t, 0> {};
|
||||
|
||||
template<typename Port, typename Head, typename... Tail>
|
||||
struct count_port<Port, TypeList<Head, Tail...>>
|
||||
: std::integral_constant<std::size_t,
|
||||
(std::is_same_v<Port, src_port_of<Head>> ? 1 : 0)
|
||||
+ count_port<Port, TypeList<Tail...>>::value> {};
|
||||
|
||||
// ── Collect all destination (DstNode&, DstIdx) for a given SrcPort ────────────
|
||||
|
||||
// A destination descriptor — just type tags, no references (references go in
|
||||
// the runtime SimpleEdge structs produced after wiring).
|
||||
template<typename DstNode, std::size_t DstIdx>
|
||||
struct DstDesc {};
|
||||
|
||||
template<typename Port, typename EdgeList>
|
||||
struct collect_dsts;
|
||||
|
||||
template<typename Port>
|
||||
struct collect_dsts<Port, TypeList<>> { using type = TypeList<>; };
|
||||
|
||||
template<typename Port, typename Head, typename... Tail>
|
||||
struct collect_dsts<Port, TypeList<Head, Tail...>> {
|
||||
using rest = typename collect_dsts<Port, TypeList<Tail...>>::type;
|
||||
using type = std::conditional_t<
|
||||
std::is_same_v<Port, src_port_of<Head>>,
|
||||
append_t<rest, DstDesc<typename Head::dst_node_t, Head::dst_idx>>,
|
||||
rest>;
|
||||
};
|
||||
|
||||
// ── SimpleEdge: a resolved edge after fanout expansion ────────────────────────
|
||||
//
|
||||
// At runtime, StaticNetwork wires edges using SimpleEdge descriptors.
|
||||
// Each SimpleEdge is just a pair of (SrcNode&, SrcIdx, DstNode&, DstIdx) stored
|
||||
// as a type — the actual references come from the node tuple at wire time.
|
||||
|
||||
template<typename SrcNode, std::size_t SrcIdx,
|
||||
typename DstNode, std::size_t DstIdx>
|
||||
struct SimpleEdge {
|
||||
using src_node_t = SrcNode;
|
||||
using dst_node_t = DstNode;
|
||||
static constexpr std::size_t src_idx = SrcIdx;
|
||||
static constexpr std::size_t dst_idx = DstIdx;
|
||||
};
|
||||
|
||||
// ── FanoutPlaceholder: a fanout node that will be owned by StaticNetwork ───────
|
||||
|
||||
template<typename T, std::size_t N, std::size_t FanoutId>
|
||||
struct FanoutPlaceholder {
|
||||
using value_type = T;
|
||||
static constexpr std::size_t fan_n = N;
|
||||
static constexpr std::size_t fan_id = FanoutId;
|
||||
};
|
||||
|
||||
// ── expand_edge: for one original edge, produce the replacement SimpleEdge(s) ──
|
||||
//
|
||||
// If the source port has N>1 consumers: the edge from src→fanout and the
|
||||
// fanout→dst edges are synthesised elsewhere (see expand_all). Here we only
|
||||
// need to emit the fanout→dst edge for this particular destination.
|
||||
//
|
||||
// For N==1 edges we emit the edge unchanged.
|
||||
//
|
||||
// This is called after the fanout node type has already been determined.
|
||||
|
||||
} // namespace kpn::tmp
|
||||
|
||||
namespace kpn {
|
||||
// Forward declaration — FanoutNode is defined in fanout.hpp.
|
||||
template<typename T, std::size_t N> class FanoutNode;
|
||||
} // namespace kpn
|
||||
|
||||
namespace kpn::tmp {
|
||||
|
||||
// ── Master expansion: iterate edges, build expanded list + storage tuple ───────
|
||||
//
|
||||
// Strategy:
|
||||
// 1. First pass: for each unique SrcPort with N>1, record a FanoutPlaceholder.
|
||||
// 2. Second pass: rewrite each edge.
|
||||
// - N==1 edges become a single SimpleEdge unchanged.
|
||||
// - N>1 edges: on first encounter emit src→fanout SimpleEdge + N fanout→dst
|
||||
// SimpleEdges; on subsequent encounters for the same src port emit nothing
|
||||
// (already handled).
|
||||
//
|
||||
// To implement "first encounter" tracking we carry a list of already-processed
|
||||
// SrcPorts through the fold.
|
||||
|
||||
template<typename ProcessedPorts, std::size_t NextFanoutId,
|
||||
typename FanoutList, // TypeList<FanoutPlaceholder<...>>
|
||||
typename EdgeList, // TypeList<SimpleEdge<...>> — accumulated output
|
||||
typename RemainingEdges> // TypeList<original edges> still to process
|
||||
struct expand_impl;
|
||||
|
||||
// Base case — no more edges
|
||||
template<typename ProcessedPorts, std::size_t NextFanoutId,
|
||||
typename FanoutList, typename EdgeList>
|
||||
struct expand_impl<ProcessedPorts, NextFanoutId, FanoutList, EdgeList, TypeList<>> {
|
||||
using fanouts = FanoutList;
|
||||
using edges = EdgeList;
|
||||
};
|
||||
|
||||
// Helper: is Port in ProcessedPorts?
|
||||
template<typename Port, typename Processed>
|
||||
struct already_processed : std::false_type {};
|
||||
template<typename Port, typename Head, typename... Tail>
|
||||
struct already_processed<Port, TypeList<Head, Tail...>>
|
||||
: std::conditional_t<std::is_same_v<Port, Head>,
|
||||
std::true_type,
|
||||
already_processed<Port, TypeList<Tail...>>> {};
|
||||
|
||||
// Helper: given DstDesc list + FanoutPlaceholder id, produce SimpleEdge list
|
||||
// FanoutNode<T,N>::output<I> → DstNode::input<DstIdx>
|
||||
template<std::size_t FanoutId, typename T, std::size_t N,
|
||||
typename DstDescList, std::size_t I = 0>
|
||||
struct fanout_to_dst_edges;
|
||||
|
||||
template<std::size_t FanoutId, typename T, std::size_t N, std::size_t I>
|
||||
struct fanout_to_dst_edges<FanoutId, T, N, TypeList<>, I> {
|
||||
using type = TypeList<>;
|
||||
};
|
||||
|
||||
template<std::size_t FanoutId, typename T, std::size_t N,
|
||||
typename DstNodeT, std::size_t DstI, typename... DstTail, std::size_t I>
|
||||
struct fanout_to_dst_edges<FanoutId, T, N,
|
||||
TypeList<DstDesc<DstNodeT, DstI>, DstTail...>, I> {
|
||||
using head_edge = SimpleEdge<kpn::FanoutNode<T, N>, I, DstNodeT, DstI>;
|
||||
using rest = typename fanout_to_dst_edges<FanoutId, T, N,
|
||||
TypeList<DstTail...>, I+1>::type;
|
||||
using type = append_t<rest, head_edge>;
|
||||
};
|
||||
|
||||
// Recursive case — process head edge
|
||||
template<typename ProcessedPorts, std::size_t NextFanoutId,
|
||||
typename FanoutList, typename EdgeList,
|
||||
typename Head, typename... Tail>
|
||||
struct expand_impl<ProcessedPorts, NextFanoutId, FanoutList, EdgeList,
|
||||
TypeList<Head, Tail...>> {
|
||||
|
||||
using AllEdges = TypeList<Head, Tail...>;
|
||||
using Port = src_port_of<Head>;
|
||||
using SrcNode = typename Head::src_node_t;
|
||||
using T = std::tuple_element_t<Head::src_idx,
|
||||
typename SrcNode::return_tuple>;
|
||||
static constexpr std::size_t N = count_port<Port, AllEdges>::value
|
||||
+ count_port<Port, EdgeList>::value
|
||||
// recount against full original list approximation:
|
||||
// simpler: recount in remaining + already done
|
||||
;
|
||||
|
||||
// Recount properly against the complete original edge list is not possible here
|
||||
// without passing it along. Instead we pre-compute N before entering the fold.
|
||||
// See expand_all below which pre-computes per-port counts.
|
||||
//
|
||||
// This struct is not used directly — expand_all drives the logic with pre-computed N.
|
||||
};
|
||||
|
||||
// ── expand_all: top-level entry point ─────────────────────────────────────────
|
||||
//
|
||||
// Pre-computes per-source-port counts, then runs a fold that processes edges
|
||||
// one by one.
|
||||
|
||||
// Port count map entry
|
||||
template<typename Port, std::size_t Count>
|
||||
struct PortCount {};
|
||||
|
||||
// Build port count list from full edge list
|
||||
template<typename AllEdges, typename UniquePortsSeen>
|
||||
struct build_port_counts;
|
||||
|
||||
template<typename AllEdges>
|
||||
struct build_port_counts<AllEdges, TypeList<>> {
|
||||
using type = TypeList<>;
|
||||
};
|
||||
|
||||
template<typename AllEdges, typename HeadPort, typename... TailPorts>
|
||||
struct build_port_counts<AllEdges, TypeList<HeadPort, TailPorts...>> {
|
||||
static constexpr std::size_t cnt = count_port<HeadPort, AllEdges>::value;
|
||||
using rest = typename build_port_counts<AllEdges, TypeList<TailPorts...>>::type;
|
||||
using type = append_t<rest, PortCount<HeadPort, cnt>>;
|
||||
};
|
||||
|
||||
// Collect unique source ports from edge list
|
||||
template<typename EdgeList, typename SeenSoFar = TypeList<>>
|
||||
struct unique_src_ports;
|
||||
|
||||
template<typename SeenSoFar>
|
||||
struct unique_src_ports<TypeList<>, SeenSoFar> { using type = SeenSoFar; };
|
||||
|
||||
template<typename Head, typename... Tail, typename SeenSoFar>
|
||||
struct unique_src_ports<TypeList<Head, Tail...>, SeenSoFar> {
|
||||
using Port = src_port_of<Head>;
|
||||
using next_seen = std::conditional_t<
|
||||
already_processed<Port, SeenSoFar>::value,
|
||||
SeenSoFar,
|
||||
append_t<SeenSoFar, Port>>;
|
||||
using type = typename unique_src_ports<TypeList<Tail...>, next_seen>::type;
|
||||
};
|
||||
|
||||
// Look up count for a port
|
||||
template<typename Port, typename CountList>
|
||||
struct lookup_count : std::integral_constant<std::size_t, 1> {};
|
||||
template<typename Port, std::size_t N, typename... Rest>
|
||||
struct lookup_count<Port, TypeList<PortCount<Port, N>, Rest...>>
|
||||
: std::integral_constant<std::size_t, N> {};
|
||||
template<typename Port, typename Head, typename... Rest>
|
||||
struct lookup_count<Port, TypeList<Head, Rest...>>
|
||||
: lookup_count<Port, TypeList<Rest...>> {};
|
||||
|
||||
// Fold state for the wiring pass
|
||||
template<typename ProcessedPorts, std::size_t NextFanoutId,
|
||||
typename FanoutList, typename EdgeList>
|
||||
struct FoldState {
|
||||
using processed = ProcessedPorts;
|
||||
static constexpr std::size_t next_id = NextFanoutId;
|
||||
using fanouts = FanoutList;
|
||||
using edges = EdgeList;
|
||||
};
|
||||
|
||||
// Process one edge given pre-computed port counts
|
||||
template<typename State, typename Edge, typename CountList, typename AllEdges>
|
||||
struct process_edge {
|
||||
using Port = src_port_of<Edge>;
|
||||
using SrcNode = typename Edge::src_node_t;
|
||||
using T = std::tuple_element_t<Edge::src_idx, typename SrcNode::return_tuple>;
|
||||
static constexpr std::size_t N = lookup_count<Port, CountList>::value;
|
||||
|
||||
// N==1: pass through unchanged
|
||||
using passthrough_edges = append_t<typename State::edges,
|
||||
SimpleEdge<SrcNode, Edge::src_idx,
|
||||
typename Edge::dst_node_t, Edge::dst_idx>>;
|
||||
|
||||
// N>1, first encounter: emit src→fanout + all fanout→dst edges
|
||||
using DstDescs = typename collect_dsts<Port, AllEdges>::type;
|
||||
static constexpr std::size_t fid = State::next_id;
|
||||
using fanout_type = FanoutPlaceholder<T, N, fid>;
|
||||
using src_to_fan = SimpleEdge<SrcNode, Edge::src_idx, FanoutNode<T, N>, 0>;
|
||||
using fan_to_dsts = typename fanout_to_dst_edges<fid, T, N, DstDescs>::type;
|
||||
using fanout_edges = concat_t<append_t<typename State::edges, src_to_fan>, fan_to_dsts>;
|
||||
using new_fanouts = append_t<typename State::fanouts, fanout_type>;
|
||||
|
||||
static constexpr bool seen = already_processed<Port, typename State::processed>::value;
|
||||
|
||||
using type = std::conditional_t<
|
||||
(N == 1),
|
||||
FoldState<typename State::processed, State::next_id,
|
||||
typename State::fanouts, passthrough_edges>,
|
||||
std::conditional_t<
|
||||
!seen,
|
||||
FoldState<append_t<typename State::processed, Port>,
|
||||
State::next_id + 1,
|
||||
new_fanouts, fanout_edges>,
|
||||
// already processed — skip (fanout edges already emitted)
|
||||
State>>;
|
||||
};
|
||||
|
||||
// Fold over all edges
|
||||
template<typename State, typename EdgeList, typename CountList, typename AllEdges>
|
||||
struct fold_edges;
|
||||
|
||||
template<typename State, typename CountList, typename AllEdges>
|
||||
struct fold_edges<State, TypeList<>, CountList, AllEdges> { using type = State; };
|
||||
|
||||
template<typename State, typename Head, typename... Tail,
|
||||
typename CountList, typename AllEdges>
|
||||
struct fold_edges<State, TypeList<Head, Tail...>, CountList, AllEdges> {
|
||||
using next = typename process_edge<State, Head, CountList, AllEdges>::type;
|
||||
using type = typename fold_edges<next, TypeList<Tail...>, CountList, AllEdges>::type;
|
||||
};
|
||||
|
||||
// ── Public interface ───────────────────────────────────────────────────────────
|
||||
|
||||
template<typename... Edges>
|
||||
struct expand_all {
|
||||
using AllEdges = TypeList<Edges...>;
|
||||
using UniquePorts = typename unique_src_ports<AllEdges>::type;
|
||||
using CountList = typename build_port_counts<AllEdges, UniquePorts>::type;
|
||||
using InitState = FoldState<TypeList<>, 0, TypeList<>, TypeList<>>;
|
||||
using FinalState = typename fold_edges<InitState, AllEdges, CountList, AllEdges>::type;
|
||||
|
||||
using fanout_placeholders = typename FinalState::fanouts; // TypeList<FanoutPlaceholder<...>>
|
||||
using expanded_edges = typename FinalState::edges; // TypeList<SimpleEdge<...>>
|
||||
};
|
||||
|
||||
// Convert TypeList<FanoutPlaceholder<T,N,Id>...> to std::tuple<FanoutNode<T,N>...>
|
||||
template<typename PlaceholderList>
|
||||
struct to_fanout_tuple;
|
||||
|
||||
template<>
|
||||
struct to_fanout_tuple<TypeList<>> { using type = std::tuple<>; };
|
||||
|
||||
template<typename T, std::size_t N, std::size_t Id, typename... Rest>
|
||||
struct to_fanout_tuple<TypeList<FanoutPlaceholder<T, N, Id>, Rest...>> {
|
||||
using rest = typename to_fanout_tuple<TypeList<Rest...>>::type;
|
||||
// prepend FanoutNode<T,N>
|
||||
template<typename Tuple> struct prepend;
|
||||
template<typename... Ts> struct prepend<std::tuple<Ts...>> {
|
||||
using type = std::tuple<kpn::FanoutNode<T, N>, Ts...>;
|
||||
};
|
||||
using type = typename prepend<rest>::type;
|
||||
};
|
||||
|
||||
template<typename... Edges>
|
||||
using fanout_storage_t = typename to_fanout_tuple<
|
||||
typename expand_all<Edges...>::fanout_placeholders>::type;
|
||||
|
||||
template<typename... Edges>
|
||||
using expanded_edges_t = typename expand_all<Edges...>::expanded_edges;
|
||||
|
||||
} // namespace kpn::tmp
|
||||
@@ -0,0 +1,23 @@
|
||||
#pragma once
|
||||
#include <cstddef>
|
||||
#include <tuple>
|
||||
#include <utility>
|
||||
|
||||
namespace kpn::tmp {
|
||||
|
||||
// repeat_tuple_t<T, N> — std::tuple<T, T, ..., T> with N elements.
|
||||
// Used by FanoutNode and fanout_groups to express N homogeneous outputs.
|
||||
|
||||
template<typename T, std::size_t N, typename Seq = std::make_index_sequence<N>>
|
||||
struct repeat_tuple;
|
||||
|
||||
template<typename T, std::size_t N, std::size_t... Is>
|
||||
struct repeat_tuple<T, N, std::index_sequence<Is...>> {
|
||||
template<std::size_t> using always_T = T;
|
||||
using type = std::tuple<always_T<Is>...>;
|
||||
};
|
||||
|
||||
template<typename T, std::size_t N>
|
||||
using repeat_tuple_t = typename repeat_tuple<T, N>::type;
|
||||
|
||||
} // namespace kpn::tmp
|
||||
@@ -0,0 +1,223 @@
|
||||
#pragma once
|
||||
#include "fanout_groups.hpp"
|
||||
#include <cstddef>
|
||||
#include <tuple>
|
||||
#include <type_traits>
|
||||
|
||||
// Compile-time topological sort + cycle detection over a TypeList<SimpleEdge<...>>.
|
||||
//
|
||||
// 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<typename EdgeList, typename Seen = TypeList<>>
|
||||
struct all_node_types;
|
||||
|
||||
template<typename Seen>
|
||||
struct all_node_types<TypeList<>, Seen> { using type = Seen; };
|
||||
|
||||
template<typename Head, typename... Tail, typename Seen>
|
||||
struct all_node_types<TypeList<Head, Tail...>, Seen> {
|
||||
using S1 = std::conditional_t<
|
||||
already_processed<typename Head::src_node_t, Seen>::value,
|
||||
Seen, append_t<Seen, typename Head::src_node_t>>;
|
||||
using S2 = std::conditional_t<
|
||||
already_processed<typename Head::dst_node_t, S1>::value,
|
||||
S1, append_t<S1, typename Head::dst_node_t>>;
|
||||
using type = typename all_node_types<TypeList<Tail...>, S2>::type;
|
||||
};
|
||||
|
||||
// ── Collect successors (dst node types) of a given src node type ──────────────
|
||||
|
||||
template<typename NodeType, typename EdgeList>
|
||||
struct successors;
|
||||
|
||||
template<typename NodeType>
|
||||
struct successors<NodeType, TypeList<>> { using type = TypeList<>; };
|
||||
|
||||
template<typename NodeType, typename Head, typename... Tail>
|
||||
struct successors<NodeType, TypeList<Head, Tail...>> {
|
||||
using rest = typename successors<NodeType, TypeList<Tail...>>::type;
|
||||
using type = std::conditional_t<
|
||||
std::is_same_v<typename Head::src_node_t, NodeType>,
|
||||
std::conditional_t<
|
||||
already_processed<typename Head::dst_node_t, rest>::value,
|
||||
rest,
|
||||
append_t<rest, typename Head::dst_node_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<typename Node, typename GreyList>
|
||||
struct is_grey : already_processed<Node, GreyList> {};
|
||||
|
||||
// DFS result: has_cycle flag + topo order (black nodes appended post-visit)
|
||||
template<bool Cycle, typename TopoList>
|
||||
struct DfsResult { static constexpr bool has_cycle = Cycle; using topo = TopoList; };
|
||||
|
||||
template<typename Node, typename EdgeList,
|
||||
typename GreyList, typename BlackList, typename TopoList>
|
||||
struct dfs_node;
|
||||
|
||||
// Iterate successors
|
||||
template<typename SuccList, typename EdgeList,
|
||||
typename GreyList, typename BlackList, typename TopoList, bool CycleSoFar>
|
||||
struct dfs_successors;
|
||||
|
||||
template<typename EdgeList, typename GreyList, typename BlackList,
|
||||
typename TopoList, bool CycleSoFar>
|
||||
struct dfs_successors<TypeList<>, EdgeList, GreyList, BlackList, TopoList, CycleSoFar> {
|
||||
static constexpr bool has_cycle = CycleSoFar;
|
||||
using black = BlackList;
|
||||
using topo = TopoList;
|
||||
};
|
||||
|
||||
template<typename Head, typename... Tail, typename EdgeList,
|
||||
typename GreyList, typename BlackList, typename TopoList, bool CycleSoFar>
|
||||
struct dfs_successors<TypeList<Head, Tail...>, EdgeList,
|
||||
GreyList, BlackList, TopoList, CycleSoFar> {
|
||||
// Visit Head
|
||||
using visit = dfs_node<Head, EdgeList, GreyList, BlackList, TopoList>;
|
||||
static constexpr bool cycle1 = CycleSoFar || visit::has_cycle;
|
||||
// Continue with remaining successors using updated black/topo
|
||||
using rest = dfs_successors<TypeList<Tail...>, 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<typename Node, typename EdgeList,
|
||||
typename GreyList, typename BlackList, typename TopoList>
|
||||
struct dfs_node {
|
||||
// Already black — skip
|
||||
static constexpr bool already_done = already_processed<Node, BlackList>::value;
|
||||
// On stack — cycle
|
||||
static constexpr bool on_stack = is_grey<Node, GreyList>::value;
|
||||
|
||||
// Visit successors (only if not already done / on stack)
|
||||
using succs = typename successors<Node, EdgeList>::type;
|
||||
using new_grey = append_t<GreyList, Node>;
|
||||
|
||||
using visit_succs = dfs_successors<succs, EdgeList, new_grey, BlackList, TopoList,
|
||||
on_stack>;
|
||||
|
||||
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<typename visit_succs::topo, Node>>;
|
||||
|
||||
using black = std::conditional_t<
|
||||
already_done || on_stack,
|
||||
BlackList,
|
||||
append_t<typename visit_succs::black, Node>>;
|
||||
|
||||
using topo = topo_after;
|
||||
};
|
||||
|
||||
// ── Top-level DFS over all nodes ──────────────────────────────────────────────
|
||||
|
||||
template<typename NodeList, typename EdgeList,
|
||||
typename BlackList, typename TopoList, bool CycleSoFar>
|
||||
struct dfs_all;
|
||||
|
||||
template<typename EdgeList, typename BlackList, typename TopoList, bool CycleSoFar>
|
||||
struct dfs_all<TypeList<>, EdgeList, BlackList, TopoList, CycleSoFar> {
|
||||
static constexpr bool has_cycle = CycleSoFar;
|
||||
using topo = TopoList;
|
||||
};
|
||||
|
||||
template<typename Head, typename... Tail, typename EdgeList,
|
||||
typename BlackList, typename TopoList, bool CycleSoFar>
|
||||
struct dfs_all<TypeList<Head, Tail...>, EdgeList, BlackList, TopoList, CycleSoFar> {
|
||||
using visit = dfs_node<Head, EdgeList, TypeList<>, BlackList, TopoList>;
|
||||
static constexpr bool cycle1 = CycleSoFar || visit::has_cycle;
|
||||
using rest = dfs_all<TypeList<Tail...>, 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<EdgeList>:
|
||||
// ::topo — TypeList of node types, sources first (start order)
|
||||
// ::has_cycle — true if a cycle was detected
|
||||
template<typename EdgeList>
|
||||
struct topo_sort {
|
||||
using Nodes = typename all_node_types<EdgeList>::type;
|
||||
using result = dfs_all<Nodes, EdgeList, TypeList<>, TypeList<>, false>;
|
||||
// DFS post-order gives reverse topo; reverse the list for sources-first order.
|
||||
// Reversing a TypeList:
|
||||
template<typename List, typename Acc = TypeList<>>
|
||||
struct reverse_list;
|
||||
template<typename Acc>
|
||||
struct reverse_list<TypeList<>, Acc> { using type = Acc; };
|
||||
template<typename H, typename... T, typename Acc>
|
||||
struct reverse_list<TypeList<H, T...>, Acc>
|
||||
: reverse_list<TypeList<T...>, append_t<Acc, H>> {}; // 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<typename List, typename Acc = TypeList<>>
|
||||
struct rev;
|
||||
template<typename Acc>
|
||||
struct rev<TypeList<>, Acc> { using type = Acc; };
|
||||
template<typename H, typename... T, typename Acc>
|
||||
struct rev<TypeList<H, T...>, Acc> : rev<TypeList<T...>, TypeList<H, Acc>> {
|
||||
// prepend H to Acc: TypeList<H, Acc...>
|
||||
};
|
||||
// Simpler: just reverse by prepending
|
||||
template<typename L, typename A = TypeList<>>
|
||||
struct rev2 { using type = A; };
|
||||
template<typename H, typename... T, typename... As>
|
||||
struct rev2<TypeList<H, T...>, TypeList<As...>>
|
||||
: rev2<TypeList<T...>, TypeList<H, As...>> {};
|
||||
|
||||
static constexpr bool has_cycle = result::has_cycle;
|
||||
using topo = typename rev2<typename result::topo>::type;
|
||||
};
|
||||
|
||||
// ── Duplicate-tag detection ───────────────────────────────────────────────────
|
||||
//
|
||||
// Two user nodes share a (Func, UniqueTag) pair iff they have the same type.
|
||||
// has_duplicate_tags_v<Edges...> is true if any two user node types are identical.
|
||||
|
||||
template<typename T, typename List>
|
||||
struct type_in_list : std::false_type {};
|
||||
template<typename T, typename H, typename... Tail>
|
||||
struct type_in_list<T, TypeList<H, Tail...>>
|
||||
: std::conditional_t<std::is_same_v<T, H>,
|
||||
std::true_type,
|
||||
type_in_list<T, TypeList<Tail...>>> {};
|
||||
|
||||
template<typename NodeList>
|
||||
struct has_duplicate_node_types : std::false_type {};
|
||||
template<typename H, typename... Tail>
|
||||
struct has_duplicate_node_types<TypeList<H, Tail...>>
|
||||
: std::conditional_t<type_in_list<H, TypeList<Tail...>>::value,
|
||||
std::true_type,
|
||||
has_duplicate_node_types<TypeList<Tail...>>> {};
|
||||
|
||||
template<typename... Edges>
|
||||
inline constexpr bool has_duplicate_tags_v =
|
||||
has_duplicate_node_types<
|
||||
typename all_node_types<TypeList<Edges...>>::type>::value;
|
||||
|
||||
} // namespace kpn::tmp
|
||||
Reference in New Issue
Block a user