/// @file dpf/dcf.hpp /// @brief Comparison-channel specs and GGM path-sum helpers for libdpf. /// @details `lt`/`leq`/`gt`/`geq` (+ `_at`) take `(if_true, if_false=0)`. /// Eval walks the same GGM tree as the DPF (per-level value CWs). /// `eq` / `eq_at` are synonyms for ordinary point placements. /// @copyright Copyright (c) 2019-2026 Ryan Henry and [others](@ref authors) /// @license Released under a GNU General Public v2.0 (GPLv2) license. #ifndef LIBDPF_INCLUDE_DPF_DCF_HPP__ #define LIBDPF_INCLUDE_DPF_DCF_HPP__ #include #include #include #include #include #include "hedley/hedley.h" #include "simde/simde/x86/avx2.h" #include "dpf/utils.hpp" #include "dpf/bit.hpp" #include "dpf/xor_wrapper.hpp" #include "dpf/twiddle.hpp" #include "dpf/cmp_group.hpp" namespace dpf { template struct ic_pack; template struct is_ic_pack : std::false_type {}; template struct is_ic_pack> : std::true_type {}; template inline constexpr bool no_ic_pack_v = (!is_ic_pack>::value && ...); /// @brief Comparison kind for the optional DCF channel on a key. /// @details `lt`/`leq`/`gt`/`geq` are the comparison predicates. The later kinds are /// path paints: one constant on each sibling subtree of the secret point, /// evaluated by the same value-correction walk. enum class cmp_kind : uint8_t { lt = 0, leq = 1, gt = 2, geq = 3, lcp = 4, // common-prefix length prefix = 5, // matched prefix, in the lane's high bits mask = 6, // high-bit mask of that length one_hot = 7, // 2^{length} (0 when the bit does not fit) break_bit = 8, // secret bit at the first difference prefix_with_length = 9, // (low-aligned prefix << length_bits) | length paint = 10 // caller-supplied unit plant }; /// @brief True for the path-paint kinds. Comparisons stay `lt`/`leq`/`gt`/`geq`. /// @param kind the comparison or paint kind /// @return True for the path-paint kinds HEDLEY_NO_THROW inline constexpr bool is_paint_kind(cmp_kind kind) noexcept { switch (kind) { case cmp_kind::lcp: case cmp_kind::prefix: case cmp_kind::mask: case cmp_kind::one_hot: case cmp_kind::break_bit: case cmp_kind::prefix_with_length: case cmp_kind::paint: return true; default: return false; } } /// @brief Unit plant for `path_paint`. `prefix` is the in-lane matched prefix. using paint_callback = uint64_t (*)(std::size_t matched, uint64_t prefix, bool leaf, const void * ctx); enum class cmp_trivial : uint8_t { none = 0, always_true = 1, always_false = 2 }; namespace detail { namespace dcf_impl { template HEDLEY_NO_THROW Beta default_false() noexcept { if constexpr (std::is_same_v) return dpf::bit::zero; else return Beta{}; } template HEDLEY_NO_THROW uint64_t beta_delta_u64(const Beta & if_true, const Beta & if_false, uint64_t mask) noexcept { if constexpr (std::is_same_v) { const uint64_t t = static_cast(if_true) ? 1ULL : 0ULL; const uint64_t f = static_cast(if_false) ? 1ULL : 0ULL; return (t ^ f) & mask; } else if constexpr (dpf::utils::is_xor_wrapper_v) { return (static_cast(if_true) ^ static_cast(if_false)) & mask; } else if constexpr (detail::has_integral_representation::value) { using raw_type = typename Beta::integral_type; using unsigned_type = utils::make_unsigned_t; const auto t = static_cast( static_cast(if_true.integral_representation())); const auto f = static_cast( static_cast(if_false.integral_representation())); return (t - f) & mask; } else { return (static_cast(if_true) - static_cast(if_false)) & mask; } } template HEDLEY_NO_THROW uint64_t beta_to_u64_simple(const Beta & beta, uint64_t mask) noexcept { if constexpr (std::is_same_v) return (static_cast(beta) ? 1ULL : 0ULL) & mask; else if constexpr (detail::has_integral_representation::value) { using raw_type = typename Beta::integral_type; using unsigned_type = utils::make_unsigned_t; return static_cast(static_cast( beta.integral_representation())) & mask; } else return static_cast(beta) & mask; } template HEDLEY_NO_THROW Beta sub_beta(const Beta & a, const Beta & b) noexcept { if constexpr (std::is_same_v) return dpf::bit{static_cast(a) ^ static_cast(b)}; else if constexpr (dpf::utils::is_xor_wrapper_v) return Beta{static_cast(a) ^ static_cast(b)}; else return static_cast(a - b); } template HEDLEY_NO_THROW Beta u64_to_beta(uint64_t v) noexcept { if constexpr (std::is_same_v) return dpf::bit{static_cast(v & 1u)}; else if constexpr (detail::has_integral_representation::value) return Beta::from_raw(static_cast(v)); else return static_cast(v); } HEDLEY_CONST HEDLEY_NO_THROW HEDLEY_ALWAYS_INLINE constexpr uint64_t default_mask_for_bits(std::size_t out_bits) noexcept { if (out_bits >= 64) return ~0ULL; if (out_bits == 0) return 0ULL; return (1ULL << out_bits) - 1ULL; } HEDLEY_CONST HEDLEY_NO_THROW HEDLEY_ALWAYS_INLINE constexpr uint64_t neg_m(uint64_t x, uint64_t mask) noexcept { return (0ULL - x) & mask; } HEDLEY_CONST HEDLEY_NO_THROW HEDLEY_ALWAYS_INLINE constexpr uint64_t sgn_m(uint8_t t1, uint64_t x, uint64_t mask) noexcept { return t1 ? neg_m(x, mask) : x; } /// @brief Convert a GGM node to a group element (low 64 bits, control bits cleared). /// @param n the `n` /// @param mask the bit mask /// @return the returned `uint64_t` HEDLEY_ALWAYS_INLINE uint64_t convert_node(simde__m128i n, uint64_t mask) noexcept { return static_cast( simde_mm_cvtsi128_si64(dpf::unset_lo_2bits(n))) & mask; } /// @brief Draw the group-width blind `r` used to split the `cmp_addend` share. /// @details `sample` yields one interior block; only `popcount(mask)` live bits are /// kept, so the blind (and thus the addend share) never needs a full padded /// `uint64_t` on the wire. Dealer and Doerner–Shelat gen call this with the /// same block source so their keys stay byte-identical (matched tapes). /// @tparam BlockSampler block sampler /// @param mask the bit mask /// @param sample the `sample` /// @return the returned `uint64_t` template HEDLEY_ALWAYS_INLINE HEDLEY_NO_THROW uint64_t sample_addend_blind(uint64_t mask, BlockSampler && sample) noexcept { return convert_node(dpf::unset_lo_2bits(sample()), mask); } /// @brief One level of value CW on GGM children. Updates running `Va`. /// @details `ai` is the keep-path bit of the (effective) threshold. /// @param c0L the `c0L` /// @param c0R the `c0R` /// @param c1L the `c1L` /// @param c1R the `c1R` /// @param t0 the `t0` /// @param t1 the `t1` /// @param ai the `ai` /// @param Va the `Va` /// @param beta the payload /// @param mask the bit mask /// @return One level of value CW on GGM children HEDLEY_NO_THROW inline uint64_t make_value_cw(simde__m128i c0L, simde__m128i c0R, simde__m128i c1L, simde__m128i c1R, uint8_t t0, uint8_t t1, int ai, uint64_t & Va, uint64_t beta, uint64_t mask) noexcept { (void)t0; uint64_t v0K, v1K, v0Lo, v1Lo; if (ai == 0) { v0K = convert_node(c0L, mask); v1K = convert_node(c1L, mask); v0Lo = convert_node(c0R, mask); v1Lo = convert_node(c1R, mask); } else { v0K = convert_node(c0R, mask); v1K = convert_node(c1R, mask); v0Lo = convert_node(c0L, mask); v1Lo = convert_node(c1L, mask); } uint64_t vcw = sgn_m(t1, (v1Lo + neg_m(v0Lo, mask) + neg_m(Va, mask)) & mask, mask); // Lose-left (ai==1) is the x<α diverge: plant β there. if (ai == 1) vcw = (vcw + sgn_m(t1, beta, mask)) & mask; Va = (Va + neg_m(v1K, mask) + v0K + sgn_m(t1, vcw, mask)) & mask; return vcw; } /// @brief Same recurrence as `make_value_cw`, planting `plant` on the lose child /// in both directions. `plant == 0` leaves the correction unchanged. /// @param c0L the `c0L` /// @param c0R the `c0R` /// @param c1L the `c1L` /// @param c1R the `c1R` /// @param t0 the `t0` /// @param t1 the `t1` /// @param ai the `ai` /// @param Va the `Va` /// @param plant the unit plant /// @param mask the bit mask /// @return Same recurrence as `make_value_cw`, planting `plant` on the lose child in both /// directions HEDLEY_NO_THROW inline uint64_t make_value_cw_planted(simde__m128i c0L, simde__m128i c0R, simde__m128i c1L, simde__m128i c1R, uint8_t t0, uint8_t t1, int ai, uint64_t & Va, uint64_t plant, uint64_t mask) noexcept { (void)t0; uint64_t v0K, v1K, v0Lo, v1Lo; if (ai == 0) { v0K = convert_node(c0L, mask); v1K = convert_node(c1L, mask); v0Lo = convert_node(c0R, mask); v1Lo = convert_node(c1R, mask); } else { v0K = convert_node(c0R, mask); v1K = convert_node(c1R, mask); v0Lo = convert_node(c0L, mask); v1Lo = convert_node(c1L, mask); } uint64_t vcw = sgn_m(t1, (v1Lo + neg_m(v0Lo, mask) + neg_m(Va, mask)) & mask, mask); vcw = (vcw + sgn_m(t1, plant, mask)) & mask; Va = (Va + neg_m(v1K, mask) + v0K + sgn_m(t1, vcw, mask)) & mask; return vcw; } HEDLEY_NO_THROW inline unsigned __int128 paint_lane_mask(std::size_t nbits) noexcept { using u128 = unsigned __int128; if (nbits == 0) return 0; if (nbits >= 128) return ~u128{0}; return (u128{1} << nbits) - 1; } /// @brief High `d` bits of an `nbits`-wide lane, in that lane's own positions. /// @param alpha the secret input point /// @param nbits the width in bits /// @param d the `d` /// @return High `d` bits of an `nbits`-wide lane, in that lane's own positions HEDLEY_NO_THROW inline unsigned __int128 paint_high_bits(unsigned __int128 alpha, std::size_t nbits, std::size_t d) noexcept { alpha &= paint_lane_mask(nbits); if (d == 0 || nbits == 0) return 0; if (d >= nbits) return alpha; const std::size_t drop = nbits - d; return (alpha >> drop) << drop; } HEDLEY_NO_THROW inline unsigned __int128 paint_low_aligned(unsigned __int128 alpha, std::size_t nbits, std::size_t d) noexcept { alpha &= paint_lane_mask(nbits); if (d == 0 || nbits == 0) return 0; if (d >= nbits) return alpha; return alpha >> (nbits - d); } /// @brief Unit (β = 1) lose-subtree or leaf plant. The caller scales by δ. /// @details `matched` is the number of leading bits already shared with α. A lose /// subtree at that depth reconstructs to this value; `leaf` is the full match. /// @param kind the comparison or paint kind /// @param matched the number of leading bits shared with the secret point /// @param alpha the secret input point /// @param nbits the width in bits /// @param length_bits the bits used to store the prefix length /// @param leaf the leaf value /// @param fn the `fn` /// @param ctx the `ctx` /// @return Unit (β = 1) lose-subtree or leaf plant inline uint64_t paint_unit(cmp_kind kind, std::size_t matched, unsigned __int128 alpha, std::size_t nbits, std::size_t length_bits, bool leaf, paint_callback fn, const void * ctx) { if (nbits == 0) return 0; const std::size_t d = leaf ? nbits : matched; switch (kind) { case cmp_kind::lcp: return static_cast(d); case cmp_kind::prefix: return static_cast(paint_high_bits(alpha, nbits, d)); case cmp_kind::mask: return static_cast( paint_high_bits(paint_lane_mask(nbits), nbits, d)); case cmp_kind::one_hot: return d >= 64 ? 0ULL : (1ULL << d); case cmp_kind::break_bit: if (leaf || matched >= nbits) return 0; return static_cast( (alpha >> (nbits - 1 - matched)) & 1); case cmp_kind::prefix_with_length: { if (length_bits >= 128) return static_cast(d); unsigned __int128 packed = paint_low_aligned(alpha, nbits, d) << length_bits; packed |= static_cast(d); return static_cast(packed); } case cmp_kind::paint: if (fn == nullptr) return 0; return fn(d, static_cast(paint_high_bits(alpha, nbits, d)), leaf, ctx); default: return 0; } } HEDLEY_NO_THROW inline uint64_t scale_plant(uint64_t unit, uint64_t scale, uint64_t mask) noexcept { return (unit * scale) & mask; } /// @brief Final leaf value CW. `on_path` is the payload reconstructed when the query /// stays on α's path through all levels (0 for strict lt/geq; β for leq/gt). /// @param s0 the `s0` /// @param s1 the `s1` /// @param t1 the `t1` /// @param Va the `Va` /// @param mask the bit mask /// @param on_path the value reconstructed on the secret path /// @return Final leaf value CW HEDLEY_NO_THROW inline uint64_t make_final_cw(simde__m128i s0, simde__m128i s1, uint8_t t1, uint64_t Va, uint64_t mask, uint64_t on_path = 0) noexcept { uint64_t c0 = convert_node(s0, mask); uint64_t c1 = convert_node(s1, mask); return sgn_m(t1, (c1 + neg_m(c0, mask) + neg_m(Va, mask) + on_path) & mask, mask); } } // namespace dcf_impl /// @brief Comparison metadata on an incremental key (value CWs live on the key). /// @details Payload δ = if_true − if_false is dealer-known and baked into `value_cw` / /// `cw_last` only — never stored clear on the key (traditional DPF hiding). /// The second output value (`if_false`) is held as a per-party additive share /// on the key (`cmp_addend`), not as a public constant. struct cmp_meta { int nbits = 0; // comparison prefix length uint64_t mask = 0; cmp_kind kind = cmp_kind::lt; cmp_trivial trivial = cmp_trivial::none; bool eval_as_ge = false; // invert path-sum (geq / gt) bool include_eq = false; // plant δ on the α-path leaf (leq / gt) bool active = false; bool incremental = false; // final correction saved at every depth int block_width = 0; // 0 = per-level path-sum int tail_bits = 0; // residual q; 0 when the tree covers every bit HEDLEY_NO_THROW bool empty() const noexcept { return !active; } }; /// @brief Backward-compatible alias while call sites migrate. using cmp_channel = cmp_meta; } // namespace detail // --------------------------------------------------------------------------- // Comparison specs: lt/leq/gt/geq (+ _at) // --------------------------------------------------------------------------- template struct cmp_pack { static constexpr bool is_cmp = true; static constexpr cmp_kind kind = Kind; static constexpr std::size_t prefix = 0; static constexpr std::size_t block_width = 0; using beta_type = Beta; Beta if_true; Beta if_false; explicit cmp_pack(Beta t, Beta f = detail::dcf_impl::default_false()) : if_true{std::move(t)}, if_false{std::move(f)} { } }; template struct cmp_at_pack { static constexpr bool is_cmp = true; static constexpr cmp_kind kind = Kind; static constexpr std::size_t prefix = N; static constexpr std::size_t block_width = 0; using beta_type = Beta; Beta if_true; Beta if_false; explicit cmp_at_pack(Beta t, Beta f = detail::dcf_impl::default_false()) : if_true{std::move(t)}, if_false{std::move(f)} { } }; template inline auto lt(Beta t, Beta f = detail::dcf_impl::default_false>()) { return cmp_pack>(std::move(t), std::move(f)); } template inline auto leq(Beta t, Beta f = detail::dcf_impl::default_false>()) { return cmp_pack>(std::move(t), std::move(f)); } template inline auto gt(Beta t, Beta f = detail::dcf_impl::default_false>()) { return cmp_pack>(std::move(t), std::move(f)); } template inline auto geq(Beta t, Beta f = detail::dcf_impl::default_false>()) { return cmp_pack>(std::move(t), std::move(f)); } template inline auto lt_at(Beta t, Beta f = detail::dcf_impl::default_false>()) { return cmp_at_pack>(std::move(t), std::move(f)); } template inline auto leq_at(Beta t, Beta f = detail::dcf_impl::default_false>()) { return cmp_at_pack>(std::move(t), std::move(f)); } template inline auto gt_at(Beta t, Beta f = detail::dcf_impl::default_false>()) { return cmp_at_pack>(std::move(t), std::move(f)); } template inline auto geq_at(Beta t, Beta f = detail::dcf_impl::default_false>()) { return cmp_at_pack>(std::move(t), std::move(f)); } // --------------------------------------------------------------------------- // Path paints. Same channel and same (if_true, if_false) scale as a comparison: // the reconstructed value is if_false + (if_true − if_false) · unit(x). // `unit` is the common-prefix length, the matched prefix, a mask, and so on. // --------------------------------------------------------------------------- template struct spec_is_incremental : std::false_type {}; template struct spec_is_incremental> : std::bool_constant {}; template struct spec_length_bits : std::integral_constant {}; template struct spec_length_bits> : std::integral_constant {}; template struct paint_pack { static constexpr bool is_cmp = true; static constexpr cmp_kind kind = Kind; static constexpr std::size_t prefix = 0; static constexpr std::size_t block_width = 0; static constexpr std::size_t length_bits = LengthBits; static constexpr bool incremental = false; using beta_type = Beta; Beta if_true; Beta if_false; explicit paint_pack(Beta t, Beta f = detail::dcf_impl::default_false()) : if_true{std::move(t)}, if_false{std::move(f)} { } }; template struct paint_at_pack { static constexpr bool is_cmp = true; static constexpr cmp_kind kind = Kind; static constexpr std::size_t prefix = N; static constexpr std::size_t block_width = 0; static constexpr std::size_t length_bits = LengthBits; static constexpr bool incremental = false; using beta_type = Beta; Beta if_true; Beta if_false; explicit paint_at_pack(Beta t, Beta f = detail::dcf_impl::default_false()) : if_true{std::move(t)}, if_false{std::move(f)} { } }; template inline auto lcp(Beta t = Beta{1}, Beta f = detail::dcf_impl::default_false()) { return paint_pack(std::move(t), std::move(f)); } template inline auto lcp_at(Beta t = Beta{1}, Beta f = detail::dcf_impl::default_false()) { return paint_at_pack(std::move(t), std::move(f)); } template inline auto common_prefix(Beta t = Beta{1}, Beta f = detail::dcf_impl::default_false()) { return paint_pack(std::move(t), std::move(f)); } template inline auto common_prefix_at(Beta t = Beta{1}, Beta f = detail::dcf_impl::default_false()) { return paint_at_pack(std::move(t), std::move(f)); } template inline auto prefix_mask(Beta t = Beta{1}, Beta f = detail::dcf_impl::default_false()) { return paint_pack(std::move(t), std::move(f)); } template inline auto prefix_mask_at(Beta t = Beta{1}, Beta f = detail::dcf_impl::default_false()) { return paint_at_pack(std::move(t), std::move(f)); } template inline auto diverge_one_hot(Beta t = Beta{1}, Beta f = detail::dcf_impl::default_false()) { return paint_pack(std::move(t), std::move(f)); } template inline auto diverge_one_hot_at(Beta t = Beta{1}, Beta f = detail::dcf_impl::default_false()) { return paint_at_pack(std::move(t), std::move(f)); } template inline auto break_bit(Beta t = Beta{1}, Beta f = detail::dcf_impl::default_false()) { return paint_pack(std::move(t), std::move(f)); } template inline auto break_bit_at(Beta t = Beta{1}, Beta f = detail::dcf_impl::default_false()) { return paint_at_pack(std::move(t), std::move(f)); } /// @brief Low `LengthBits` hold the common-prefix length. Above them sits the /// matched prefix packed into the low bits of the lane (`α >> (N − d)`). /// @tparam LengthBits length bits /// @tparam Beta payload type /// @param t the `t` /// @param f the `f` /// @return Low `LengthBits` hold the common-prefix length template inline auto prefix_with_length(Beta t = Beta{1}, Beta f = detail::dcf_impl::default_false()) { return paint_pack( std::move(t), std::move(f)); } template inline auto prefix_with_length_at(Beta t = Beta{1}, Beta f = detail::dcf_impl::default_false()) { return paint_at_pack( std::move(t), std::move(f)); } /// @brief Arbitrary unit plant. `fn(matched, in_lane_prefix, leaf)` returns the β = 1 /// value of that sibling subtree (`leaf` is the full match, `matched == N`). /// @details The result is scaled by `if_true − if_false` like the canned recipes. /// @tparam Beta payload type /// @tparam Fn fn template struct paint_fn_pack { static constexpr bool is_cmp = true; static constexpr cmp_kind kind = cmp_kind::paint; static constexpr std::size_t prefix = 0; static constexpr std::size_t block_width = 0; static constexpr std::size_t length_bits = 0; static constexpr bool incremental = false; using beta_type = Beta; Beta if_true; Beta if_false; Fn fn; paint_fn_pack(Beta t, Beta f, Fn g) : if_true{std::move(t)}, if_false{std::move(f)}, fn{std::move(g)} { } }; template struct paint_fn_at_pack { static constexpr bool is_cmp = true; static constexpr cmp_kind kind = cmp_kind::paint; static constexpr std::size_t prefix = N; static constexpr std::size_t block_width = 0; static constexpr std::size_t length_bits = 0; static constexpr bool incremental = false; using beta_type = Beta; Beta if_true; Beta if_false; Fn fn; paint_fn_at_pack(Beta t, Beta f, Fn g) : if_true{std::move(t)}, if_false{std::move(f)}, fn{std::move(g)} { } }; template inline auto path_paint(Fn fn, Beta t, Beta f = detail::dcf_impl::default_false()) { return paint_fn_pack, std::decay_t>( std::move(t), std::move(f), std::move(fn)); } template inline auto path_paint(Fn fn) { return path_paint(std::move(fn), uint64_t{1}); } template inline auto path_paint_at(Fn fn, Beta t, Beta f = detail::dcf_impl::default_false()) { return paint_fn_at_pack, std::decay_t>( std::move(t), std::move(f), std::move(fn)); } /// @brief Incremental comparison: the same predicate, correct at every prefix length. /// @details Evaluate the full point with `cmp`, and a prefix with `cmp_prefix`. /// @tparam Spec comparison or interval specification template struct idcf_pack { static constexpr bool is_cmp = true; static constexpr bool incremental = true; static constexpr cmp_kind kind = Spec::kind; static constexpr std::size_t prefix = Spec::prefix; static constexpr std::size_t block_width = Spec::block_width; static constexpr std::size_t length_bits = spec_length_bits::value; using beta_type = typename Spec::beta_type; beta_type if_true; beta_type if_false; explicit idcf_pack(Spec spec) : if_true{std::move(spec.if_true)}, if_false{std::move(spec.if_false)} { } }; template inline auto idcf(Spec spec) { static_assert(is_paint_kind(Spec::kind) == false, "idcf wraps lt/leq/gt/geq; path paints are already one full-domain value"); static_assert(Spec::block_width == 0, "idcf uses the per-level path, not blocked checkpoints"); return idcf_pack{std::move(spec)}; } // --------------------------------------------------------------------------- // Equality specs: eq / eq_at // --------------------------------------------------------------------------- template struct eq_pack { static constexpr bool is_eq = true; static constexpr std::size_t prefix = 0; using beta_type = Beta; Beta if_true; Beta if_false; explicit eq_pack(Beta t, Beta f = detail::dcf_impl::default_false()) : if_true{std::move(t)}, if_false{std::move(f)} { } }; template struct eq_at_pack { static constexpr bool is_eq = true; static constexpr std::size_t prefix = N; using beta_type = Beta; Beta if_true; Beta if_false; explicit eq_at_pack(Beta t, Beta f = detail::dcf_impl::default_false()) : if_true{std::move(t)}, if_false{std::move(f)} { } }; template inline auto eq(Beta t, Beta f = detail::dcf_impl::default_false>()) { return eq_pack>(std::move(t), std::move(f)); } template inline auto eq_at(Beta t, Beta f = detail::dcf_impl::default_false>()) { return eq_at_pack>(std::move(t), std::move(f)); } template struct block_width_pack { static_assert(BlockWidth >= 1, "block_width needs B >= 1"); static_assert(Spec::block_width == 0, "comparison is already block_width"); static constexpr bool is_cmp = true; static constexpr std::size_t block_width = BlockWidth; static constexpr cmp_kind kind = Spec::kind; static constexpr std::size_t prefix = Spec::prefix; static constexpr bool incremental = spec_is_incremental::value; static constexpr std::size_t length_bits = spec_length_bits::value; using beta_type = typename Spec::beta_type; beta_type if_true; beta_type if_false; explicit block_width_pack(Spec spec) : if_true{std::move(spec.if_true)}, if_false{std::move(spec.if_false)} { } }; template struct block_width_fn { template constexpr auto operator()(Spec spec) const { return block_width_pack{std::move(spec)}; } }; template inline constexpr block_width_fn block_width{}; template struct is_cmp_spec : std::false_type {}; template struct is_cmp_spec> : std::true_type {}; template struct is_cmp_spec> : std::true_type {}; template struct is_cmp_spec> : std::true_type {}; template struct is_cmp_spec> : std::true_type {}; template struct is_cmp_spec> : std::true_type {}; template struct is_cmp_spec> : std::true_type {}; template struct is_cmp_spec> : std::true_type {}; template struct is_cmp_spec> : std::true_type {}; template inline constexpr bool is_cmp_spec_v = is_cmp_spec::value; template struct is_eq_spec : std::false_type {}; template struct is_eq_spec> : std::true_type {}; template struct is_eq_spec> : std::true_type {}; template inline constexpr bool is_eq_spec_v = is_eq_spec::value; template inline constexpr bool is_dcf_spec_v = is_cmp_spec_v; } // namespace dpf #endif // LIBDPF_INCLUDE_DPF_DCF_HPP__