#pragma once #include #include namespace kpn { // ── Primary template — not defined; only specialisations match ──────────────── template struct function_traits; // Free function template struct function_traits { using return_t = R; using args = std::tuple; static constexpr std::size_t arity = sizeof...(Args); }; // Function pointer template struct function_traits : function_traits {}; // Member function pointer (const) template struct function_traits : function_traits {}; // Member function pointer (non-const) template struct function_traits : function_traits {}; // Callable (lambda / std::function) — delegate to operator() template struct function_traits : function_traits {}; // ── Helpers ─────────────────────────────────────────────────────────────────── template using return_t = typename function_traits>::return_t; template using args_t = typename function_traits>::args; template inline constexpr std::size_t arity_v = function_traits>::arity; // ── Tuple detection ─────────────────────────────────────────────────────────── template struct is_tuple : std::false_type {}; template struct is_tuple> : std::true_type {}; template inline constexpr bool is_tuple_v = is_tuple::value; // ── Normalise return type to always be a tuple ──────────────────────────────── // void → std::tuple<> // T (non-tup) → std::tuple // tuple<...> → tuple<...> (unchanged) template struct normalise_return { using type = std::tuple; }; template<> struct normalise_return { using type = std::tuple<>; }; template struct normalise_return> { using type = std::tuple; }; template using normalised_return_t = typename normalise_return::type; // ── Output count from a function type ──────────────────────────────────────── template inline constexpr std::size_t output_count_v = std::tuple_size_v>>; // ── repeat_tuple: std::tuple with N repetitions ──────────────── template> struct repeat_tuple; template struct repeat_tuple> { template using always_T = T; using type = std::tuple...>; }; template using repeat_tuple_t = typename repeat_tuple::type; // ── Sentinel detection ──────────────────────────────────────────────────────── // A value is a "sentinel" (must-deliver control token, e.g. EOF) if its type // carries a bool-convertible eof flag — either directly (`v.eof`, as on a raw // source Frame) or nested one level under a `.source` member (`v.source.eof`, // as on message types that wrap the originating Frame). Sentinels are delivered // losslessly and non-blockingly via Channel::push_sentinel() instead of the // throwing push(), so backpressure can never drop the token that unblocks // downstream teardown. // // Types with neither shape are never treated as sentinels — both traits are // SFINAE-safe and the runtime check compiles away to `false` for them, so this // stays a no-op for pipelines that don't use an eof convention. // // Lives here rather than in pool_node.hpp because every node type that forwards // values needs it, not just the pool-scheduled ones. FilterNode and RouterNode // not having it is what let an EOF token be dropped on a full output. template struct has_eof_field : std::false_type {}; template struct has_eof_field(std::declval().eof))>> : std::true_type {}; template struct has_source_eof_field : std::false_type {}; template struct has_source_eof_field(std::declval().source.eof))>> : std::true_type {}; template constexpr bool is_sentinel_value(const T& v) { if constexpr (has_eof_field::value) return static_cast(v.eof); else if constexpr (has_source_eof_field::value) return static_cast(v.source.eof); else return false; } } // namespace kpn