604 lines
21 KiB
C++
604 lines
21 KiB
C++
/// @file dpf/placement.hpp
|
|
/// @brief Prefix placement (`at<N>`), phantom cmp tag, and slot-meta machinery.
|
|
/// @details Shared by `dpf_key` (unified key) and `incremental.hpp` (gen/eval).
|
|
/// Holds the pure type-level pieces so `dpf_key.hpp` can build a
|
|
/// `slot_meta` table for multi-level / cmp keys without depending on
|
|
/// the generation / evaluation code that lives in `incremental.hpp`.
|
|
/// @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_PLACEMENT_HPP__
|
|
#define LIBDPF_INCLUDE_DPF_PLACEMENT_HPP__
|
|
|
|
#include <cstddef>
|
|
#include <array>
|
|
#include <tuple>
|
|
#include <type_traits>
|
|
#include <utility>
|
|
#include <algorithm>
|
|
|
|
#include "dpf/utils.hpp"
|
|
#include "dpf/leaf_node.hpp"
|
|
#include "dpf/wildcard.hpp"
|
|
|
|
namespace dpf
|
|
{
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Public placement sugar: `at<N>(y, ys...)`
|
|
// ---------------------------------------------------------------------------
|
|
|
|
template <std::size_t N, typename OutputT, typename ...OutputTs>
|
|
struct at_pack
|
|
{
|
|
static constexpr std::size_t prefix = N;
|
|
using outputs_tuple = std::tuple<OutputT, OutputTs...>;
|
|
outputs_tuple values;
|
|
|
|
at_pack() = delete;
|
|
explicit at_pack(OutputT y, OutputTs ...ys)
|
|
: values{std::move(y), std::move(ys)...} { }
|
|
};
|
|
|
|
template <std::size_t N>
|
|
struct at_fn
|
|
{
|
|
template <typename OutputT, typename ...OutputTs>
|
|
constexpr auto operator()(OutputT y, OutputTs ...ys) const
|
|
{
|
|
return at_pack<N, OutputT, OutputTs...>(std::move(y), std::move(ys)...);
|
|
}
|
|
};
|
|
|
|
template <std::size_t N>
|
|
inline constexpr at_fn<N> at{};
|
|
|
|
template <typename T> struct is_at : std::false_type {};
|
|
template <std::size_t N, typename O, typename ...Os>
|
|
struct is_at<at_pack<N, O, Os...>> : std::true_type {};
|
|
template <typename T> inline constexpr bool is_at_v = is_at<T>::value;
|
|
|
|
/// @brief Phantom pack element for a key's comparison (DCF) channel. Not a leaf: it
|
|
/// only records the cmp prefix depth in the key's type. `Depth` is the number
|
|
/// of tree levels the comparison walks (0 is reserved for "no cmp").
|
|
/// @details `OutBits` is the comparison output group width (bits of the β payload),
|
|
/// so the value CWs / addend can be stored at group width instead of a full
|
|
/// padded `uint64_t` per level.
|
|
/// @tparam Depth depth
|
|
/// @tparam OutBits out bits
|
|
/// @tparam Wild whether the payload is a wildcard
|
|
/// @tparam BlockWidth checkpoint spacing, in levels
|
|
/// @tparam Incremental whether a final correction is stored at every depth
|
|
template <std::size_t Depth, std::size_t OutBits = 0, bool Wild = false,
|
|
std::size_t BlockWidth = 0, bool Incremental = false>
|
|
struct cmp_channel_tag
|
|
{
|
|
static constexpr std::size_t depth = Depth;
|
|
static constexpr std::size_t out_bits = OutBits;
|
|
/// @brief True when the comparison payload (β) is a wildcard to be assigned
|
|
/// after keygen. Concrete (non-wildcard) cmp keys keep `Wild == false`
|
|
/// so their layout / type name is unchanged.
|
|
static constexpr bool wild = Wild;
|
|
/// @brief 0 keeps the per-level path-sum. `B >= 1` selects blocked checkpoints
|
|
/// of target width `B`.
|
|
static constexpr std::size_t block_width = BlockWidth;
|
|
/// @brief Save a final correction at every depth (`idcf`).
|
|
static constexpr bool incremental = Incremental;
|
|
};
|
|
|
|
template <typename T> struct is_cmp_channel_tag : std::false_type {};
|
|
template <std::size_t Depth, std::size_t OutBits, bool Wild, std::size_t Block,
|
|
bool Incremental>
|
|
struct is_cmp_channel_tag<cmp_channel_tag<Depth, OutBits, Wild, Block, Incremental>>
|
|
: std::true_type {};
|
|
template <typename T>
|
|
inline constexpr bool is_cmp_channel_tag_v =
|
|
is_cmp_channel_tag<std::decay_t<T>>::value;
|
|
|
|
/// Phantom pack element: key carries per-level VDPF correction seeds.
|
|
struct verifiable
|
|
{
|
|
static constexpr bool is_verifiable_tag = true;
|
|
};
|
|
|
|
/// Phantom pack element: ROM leaf stretch + extractability checks.
|
|
struct extractable
|
|
{
|
|
static constexpr bool is_extractable_tag = true;
|
|
};
|
|
|
|
template <typename T>
|
|
struct is_verifiable_tag : std::false_type
|
|
{ };
|
|
template <>
|
|
struct is_verifiable_tag<verifiable> : std::true_type
|
|
{ };
|
|
template <typename T>
|
|
inline constexpr bool is_verifiable_tag_v =
|
|
is_verifiable_tag<std::decay_t<T>>::value;
|
|
|
|
template <typename T>
|
|
struct is_extractable_tag : std::false_type
|
|
{ };
|
|
template <>
|
|
struct is_extractable_tag<extractable> : std::true_type
|
|
{ };
|
|
template <typename T>
|
|
inline constexpr bool is_extractable_tag_v =
|
|
is_extractable_tag<std::decay_t<T>>::value;
|
|
|
|
namespace detail
|
|
{
|
|
namespace incr
|
|
{
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// A concrete placed output: an output type `OutputT` planted at prefix `N`.
|
|
// ---------------------------------------------------------------------------
|
|
|
|
/// @brief Default: placed payload type is the stored type. Specialized for
|
|
/// `arith_beta<T>` in `doerner_shelat.hpp` so the key leaf type is `T`.
|
|
/// @tparam T value type
|
|
template <typename T>
|
|
struct unwrap_placed_output
|
|
{
|
|
using type = T;
|
|
};
|
|
|
|
template <std::size_t N, typename OutputT>
|
|
struct placed
|
|
{
|
|
static constexpr std::size_t prefix = N;
|
|
/// @brief Stored argument type (may be `arith_beta<T>`).
|
|
using stored_type = OutputT;
|
|
/// @brief Key / leaf payload type (`T` when stored is `arith_beta<T>`).
|
|
using output_type = typename unwrap_placed_output<OutputT>::type;
|
|
OutputT value;
|
|
output_type addend{}; // public if_false for eq(...); party 0 absorbs at eval
|
|
};
|
|
|
|
template <typename T> struct is_placed : std::false_type {};
|
|
template <std::size_t N, typename O>
|
|
struct is_placed<placed<N, O>> : std::true_type {};
|
|
template <typename T>
|
|
inline constexpr bool is_placed_v = is_placed<std::decay_t<T>>::value;
|
|
|
|
/// @brief Heavy-hitters incremental point function: one payload per prefix length.
|
|
/// @details `levels[i]` is the bit length of slot `i`.
|
|
/// @tparam LevelSeq level seq
|
|
/// @tparam Betas betas
|
|
template <typename LevelSeq, typename ...Betas>
|
|
struct idpf_pack;
|
|
|
|
template <std::size_t ...Levels, typename ...Betas>
|
|
struct idpf_pack<std::index_sequence<Levels...>, Betas...>
|
|
{
|
|
static constexpr bool is_idpf = true;
|
|
static constexpr std::size_t n = sizeof...(Levels);
|
|
static constexpr std::array<std::size_t, n == 0 ? 1 : n> levels{
|
|
Levels...};
|
|
std::tuple<Betas...> values;
|
|
};
|
|
|
|
template <typename T> struct is_idpf : std::false_type {};
|
|
template <std::size_t ...Levels, typename ...Betas>
|
|
struct is_idpf<idpf_pack<std::index_sequence<Levels...>, Betas...>>
|
|
: std::true_type {};
|
|
template <typename T>
|
|
inline constexpr bool is_idpf_v = is_idpf<std::decay_t<T>>::value;
|
|
|
|
template <typename NodeT, typename OutputT>
|
|
inline constexpr std::size_t out_bits_v =
|
|
utils::bitlength_of_output_v<concrete_type_t<OutputT>, NodeT>;
|
|
|
|
template <typename NodeT, typename OutputT>
|
|
inline constexpr std::size_t lg_opl_v =
|
|
dpf::lg_outputs_per_leaf_v<concrete_type_t<OutputT>, NodeT>;
|
|
|
|
template <typename NodeT, std::size_t N, typename OutputT>
|
|
inline constexpr std::size_t level_of_v = N - lg_opl_v<NodeT, OutputT>;
|
|
|
|
template <typename NodeT, std::size_t N, typename OutputT>
|
|
inline constexpr bool prefix_ok_v = (N >= lg_opl_v<NodeT, OutputT>);
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Per-output slot metadata (packing / tree-level table).
|
|
// ---------------------------------------------------------------------------
|
|
|
|
struct slot_meta
|
|
{
|
|
std::size_t prefix;
|
|
std::size_t tree_level;
|
|
std::size_t pos_base;
|
|
std::size_t group_id;
|
|
std::size_t index_in_group;
|
|
std::size_t out_bits;
|
|
std::size_t lg_opl;
|
|
std::size_t block_len;
|
|
};
|
|
|
|
template <typename NodeT, typename PlacedTuple, std::size_t ...Is>
|
|
constexpr std::size_t max_tree_level_impl(std::index_sequence<Is...>)
|
|
{
|
|
std::size_t m = 0;
|
|
((m = std::max(m, level_of_v<NodeT,
|
|
std::tuple_element_t<Is, PlacedTuple>::prefix,
|
|
typename std::tuple_element_t<Is, PlacedTuple>::output_type>)), ...);
|
|
return m;
|
|
}
|
|
|
|
template <typename NodeT, typename PlacedTuple>
|
|
inline constexpr std::size_t max_tree_level_v =
|
|
max_tree_level_impl<NodeT, PlacedTuple>(
|
|
std::make_index_sequence<std::tuple_size_v<PlacedTuple>>{});
|
|
|
|
template <typename NodeT, typename PlacedTuple, std::size_t ...Is>
|
|
constexpr bool all_prefixes_ok_impl(std::index_sequence<Is...>)
|
|
{
|
|
return (prefix_ok_v<NodeT,
|
|
std::tuple_element_t<Is, PlacedTuple>::prefix,
|
|
typename std::tuple_element_t<Is, PlacedTuple>::output_type> && ...);
|
|
}
|
|
|
|
template <typename NodeT, typename PlacedTuple>
|
|
inline constexpr bool all_prefixes_ok_v =
|
|
all_prefixes_ok_impl<NodeT, PlacedTuple>(
|
|
std::make_index_sequence<std::tuple_size_v<PlacedTuple>>{});
|
|
|
|
template <typename NodeT, typename PlacedTuple, std::size_t I>
|
|
constexpr void fill_slot_basics(
|
|
std::array<slot_meta, std::tuple_size_v<PlacedTuple>> & meta)
|
|
{
|
|
using P = std::tuple_element_t<I, PlacedTuple>;
|
|
using O = typename P::output_type;
|
|
meta[I].prefix = P::prefix;
|
|
meta[I].lg_opl = lg_opl_v<NodeT, O>;
|
|
meta[I].out_bits = out_bits_v<NodeT, O>;
|
|
meta[I].tree_level = P::prefix - meta[I].lg_opl;
|
|
meta[I].block_len =
|
|
dpf::block_length_of_leaf_v<concrete_type_t<O>, NodeT>;
|
|
meta[I].pos_base = 0;
|
|
meta[I].group_id = 0;
|
|
meta[I].index_in_group = 0;
|
|
}
|
|
|
|
template <typename NodeT, typename PlacedTuple, std::size_t ...Is>
|
|
constexpr void fill_all_basics(
|
|
std::array<slot_meta, std::tuple_size_v<PlacedTuple>> & meta,
|
|
std::index_sequence<Is...>)
|
|
{
|
|
(fill_slot_basics<NodeT, PlacedTuple, Is>(meta), ...);
|
|
}
|
|
|
|
template <typename NodeT, typename PlacedTuple>
|
|
constexpr auto build_meta()
|
|
{
|
|
constexpr std::size_t n = std::tuple_size_v<PlacedTuple>;
|
|
std::array<slot_meta, n> meta{};
|
|
fill_all_basics<NodeT, PlacedTuple>(meta, std::make_index_sequence<n>{});
|
|
|
|
std::size_t next_gid = 0;
|
|
for (std::size_t i = 0; i < n; ++i)
|
|
{
|
|
std::size_t gid = static_cast<std::size_t>(-1);
|
|
for (std::size_t j = 0; j < i; ++j)
|
|
{
|
|
if (meta[j].prefix == meta[i].prefix
|
|
&& meta[j].out_bits == meta[i].out_bits)
|
|
{
|
|
gid = meta[j].group_id;
|
|
break;
|
|
}
|
|
}
|
|
if (gid == static_cast<std::size_t>(-1))
|
|
gid = next_gid++;
|
|
meta[i].group_id = gid;
|
|
}
|
|
|
|
const std::size_t ngroups = next_gid;
|
|
std::array<std::size_t, n> group_count{};
|
|
for (std::size_t i = 0; i < n; ++i)
|
|
group_count[i] = 0;
|
|
for (std::size_t i = 0; i < n; ++i)
|
|
meta[i].index_in_group = group_count[meta[i].group_id]++;
|
|
|
|
std::array<std::size_t, n> g_level{};
|
|
std::array<std::size_t, n> g_blocks{};
|
|
std::array<std::size_t, n> g_first{};
|
|
for (std::size_t g = 0; g < ngroups; ++g)
|
|
{
|
|
g_blocks[g] = 0;
|
|
g_first[g] = n;
|
|
g_level[g] = 0;
|
|
}
|
|
for (std::size_t i = 0; i < n; ++i)
|
|
{
|
|
const auto g = meta[i].group_id;
|
|
g_level[g] = meta[i].tree_level;
|
|
g_blocks[g] += meta[i].block_len;
|
|
if (i < g_first[g])
|
|
g_first[g] = i;
|
|
}
|
|
|
|
constexpr std::size_t depth = max_tree_level_v<NodeT, PlacedTuple>;
|
|
std::array<bool, n> g_done{};
|
|
for (std::size_t g = 0; g < ngroups; ++g)
|
|
g_done[g] = false;
|
|
|
|
for (std::size_t level = 0; level <= depth; ++level)
|
|
{
|
|
std::size_t cursor = (level == depth) ? 0 : 2;
|
|
for (;;)
|
|
{
|
|
std::size_t best_g = n;
|
|
std::size_t best_first = n;
|
|
for (std::size_t g = 0; g < ngroups; ++g)
|
|
{
|
|
if (g_done[g] || g_level[g] != level)
|
|
continue;
|
|
if (g_first[g] < best_first)
|
|
{
|
|
best_first = g_first[g];
|
|
best_g = g;
|
|
}
|
|
}
|
|
if (best_g == n)
|
|
break;
|
|
for (std::size_t i = 0; i < n; ++i)
|
|
{
|
|
if (meta[i].group_id == best_g)
|
|
meta[i].pos_base = cursor;
|
|
}
|
|
cursor += g_blocks[best_g];
|
|
g_done[best_g] = true;
|
|
}
|
|
}
|
|
return meta;
|
|
}
|
|
|
|
template <typename MetaArray, std::size_t NGroups>
|
|
constexpr auto build_group_order(const MetaArray & meta, std::size_t n)
|
|
{
|
|
std::array<std::size_t, NGroups == 0 ? 1 : NGroups> order{};
|
|
std::array<bool, NGroups == 0 ? 1 : NGroups> used{};
|
|
for (std::size_t g = 0; g < NGroups; ++g)
|
|
used[g] = false;
|
|
for (std::size_t k = 0; k < NGroups; ++k)
|
|
{
|
|
std::size_t best = NGroups;
|
|
std::size_t best_lvl = static_cast<std::size_t>(-1);
|
|
std::size_t best_first = n;
|
|
for (std::size_t g = 0; g < NGroups; ++g)
|
|
{
|
|
if (used[g])
|
|
continue;
|
|
std::size_t lvl = 0, first = n;
|
|
for (std::size_t i = 0; i < n; ++i)
|
|
{
|
|
if (meta[i].group_id == g)
|
|
{
|
|
lvl = meta[i].tree_level;
|
|
if (i < first)
|
|
first = i;
|
|
}
|
|
}
|
|
if (lvl < best_lvl || (lvl == best_lvl && first < best_first))
|
|
{
|
|
best_lvl = lvl;
|
|
best_first = first;
|
|
best = g;
|
|
}
|
|
}
|
|
order[k] = best;
|
|
used[best] = true;
|
|
}
|
|
return order;
|
|
}
|
|
|
|
template <typename InputT>
|
|
constexpr InputT lane_input(InputT x, std::size_t prefix, std::size_t bitlen)
|
|
{
|
|
if (prefix >= bitlen)
|
|
return x;
|
|
// Shift on the integral representation so this works for `modint`,
|
|
// `keyword` (whose `>>` yields a parent `modint`, not the keyword), and
|
|
// signed/bitstring inputs. Reconstruct the input type from the shifted
|
|
// integral value via `make_from_integral_value` (a friend of `keyword`).
|
|
constexpr auto to_int = utils::to_integral_type<InputT>{};
|
|
using FromI = typename utils::make_from_integral_value<InputT>::integral_type;
|
|
const auto shifted = static_cast<FromI>(to_int(x) >> (bitlen - prefix));
|
|
return utils::make_from_integral_value<InputT>{}(shifted);
|
|
}
|
|
|
|
// Concatenate index sequences
|
|
template <typename... Seqs> struct cat_seq;
|
|
template <> struct cat_seq<> { using type = std::index_sequence<>; };
|
|
template <std::size_t... Is>
|
|
struct cat_seq<std::index_sequence<Is...>>
|
|
{
|
|
using type = std::index_sequence<Is...>;
|
|
};
|
|
template <std::size_t... Is, std::size_t... Js, typename... Rest>
|
|
struct cat_seq<std::index_sequence<Is...>, std::index_sequence<Js...>, Rest...>
|
|
{
|
|
using type = typename cat_seq<std::index_sequence<Is..., Js...>, Rest...>::type;
|
|
};
|
|
template <typename... Seqs>
|
|
using cat_seq_t = typename cat_seq<Seqs...>::type;
|
|
|
|
template <std::size_t G, typename MetaHolder, std::size_t I>
|
|
using keep_if_group = std::conditional_t<
|
|
MetaHolder::value[I].group_id == G,
|
|
std::index_sequence<I>,
|
|
std::index_sequence<>>;
|
|
|
|
template <std::size_t G, typename MetaHolder, typename Seq>
|
|
struct filter_group;
|
|
template <std::size_t G, typename MetaHolder, std::size_t... Is>
|
|
struct filter_group<G, MetaHolder, std::index_sequence<Is...>>
|
|
{
|
|
using type = cat_seq_t<keep_if_group<G, MetaHolder, Is>...>;
|
|
};
|
|
template <std::size_t G, typename MetaHolder, std::size_t N>
|
|
using filter_group_t = typename filter_group<G, MetaHolder,
|
|
std::make_index_sequence<N>>::type;
|
|
|
|
template <typename KeyT>
|
|
struct meta_holder
|
|
{
|
|
static constexpr auto value = KeyT::meta;
|
|
};
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Normalize a `dpf_key` output pack into (PlacedTuple, CmpDepth).
|
|
//
|
|
// Each pack element is one of:
|
|
// - a bare output `T` -> placed<BitLen, T>
|
|
// - a `placed<N, T>` -> placed<N, T> (from `at<N>`)
|
|
// - a `cmp_channel_tag<Depth>` -> not a leaf; contributes Depth to CmpDepth
|
|
// ---------------------------------------------------------------------------
|
|
|
|
template <std::size_t BitLen, typename Elem>
|
|
struct normalize_one
|
|
{
|
|
using placed_tuple = std::tuple<placed<BitLen, Elem>>;
|
|
static constexpr std::size_t cmp_depth = 0;
|
|
static constexpr std::size_t cmp_out_bits = 0;
|
|
static constexpr bool cmp_wild = false;
|
|
static constexpr std::size_t cmp_block = 0;
|
|
static constexpr bool cmp_idcf = false;
|
|
static constexpr bool is_verifiable = false;
|
|
static constexpr bool is_extractable = false;
|
|
};
|
|
template <std::size_t BitLen, std::size_t N, typename T>
|
|
struct normalize_one<BitLen, placed<N, T>>
|
|
{
|
|
using placed_tuple = std::tuple<placed<N, T>>;
|
|
static constexpr std::size_t cmp_depth = 0;
|
|
static constexpr std::size_t cmp_out_bits = 0;
|
|
static constexpr bool cmp_wild = false;
|
|
static constexpr std::size_t cmp_block = 0;
|
|
static constexpr bool cmp_idcf = false;
|
|
static constexpr bool is_verifiable = false;
|
|
static constexpr bool is_extractable = false;
|
|
};
|
|
template <std::size_t BitLen, std::size_t Depth, std::size_t OutBits, bool Wild,
|
|
std::size_t Block, bool Incremental>
|
|
struct normalize_one<BitLen,
|
|
cmp_channel_tag<Depth, OutBits, Wild, Block, Incremental>>
|
|
{
|
|
using placed_tuple = std::tuple<>;
|
|
static constexpr std::size_t cmp_depth = Depth;
|
|
static constexpr std::size_t cmp_out_bits = OutBits;
|
|
static constexpr bool cmp_wild = Wild;
|
|
static constexpr std::size_t cmp_block = Block;
|
|
static constexpr bool cmp_idcf = Incremental;
|
|
static constexpr bool is_verifiable = false;
|
|
static constexpr bool is_extractable = false;
|
|
};
|
|
template <std::size_t BitLen>
|
|
struct normalize_one<BitLen, verifiable>
|
|
{
|
|
using placed_tuple = std::tuple<>;
|
|
static constexpr std::size_t cmp_depth = 0;
|
|
static constexpr std::size_t cmp_out_bits = 0;
|
|
static constexpr bool cmp_wild = false;
|
|
static constexpr std::size_t cmp_block = 0;
|
|
static constexpr bool cmp_idcf = false;
|
|
static constexpr bool is_verifiable = true;
|
|
static constexpr bool is_extractable = false;
|
|
};
|
|
template <std::size_t BitLen>
|
|
struct normalize_one<BitLen, extractable>
|
|
{
|
|
using placed_tuple = std::tuple<>;
|
|
static constexpr std::size_t cmp_depth = 0;
|
|
static constexpr std::size_t cmp_out_bits = 0;
|
|
static constexpr bool cmp_wild = false;
|
|
static constexpr std::size_t cmp_block = 0;
|
|
static constexpr bool cmp_idcf = false;
|
|
static constexpr bool is_verifiable = false;
|
|
static constexpr bool is_extractable = true;
|
|
};
|
|
|
|
template <std::size_t BitLen, typename ...Elems>
|
|
struct normalize_pack
|
|
{
|
|
using placed_tuple =
|
|
decltype(std::tuple_cat(
|
|
std::declval<typename normalize_one<BitLen, Elems>::placed_tuple>()...));
|
|
static constexpr std::size_t cmp_depth =
|
|
(std::size_t{0} + ... + normalize_one<BitLen, Elems>::cmp_depth);
|
|
static constexpr std::size_t cmp_out_bits =
|
|
(std::size_t{0} + ... + normalize_one<BitLen, Elems>::cmp_out_bits);
|
|
static constexpr bool cmp_wild =
|
|
(false || ... || normalize_one<BitLen, Elems>::cmp_wild);
|
|
static constexpr std::size_t cmp_block =
|
|
(std::size_t{0} + ... + normalize_one<BitLen, Elems>::cmp_block);
|
|
static constexpr bool cmp_idcf =
|
|
(false || ... || normalize_one<BitLen, Elems>::cmp_idcf);
|
|
static constexpr bool is_verifiable =
|
|
(false || ... || normalize_one<BitLen, Elems>::is_verifiable);
|
|
static constexpr bool is_extractable =
|
|
(false || ... || normalize_one<BitLen, Elems>::is_extractable);
|
|
};
|
|
|
|
template <std::size_t BitLen>
|
|
struct normalize_pack<BitLen>
|
|
{
|
|
using placed_tuple = std::tuple<>;
|
|
static constexpr std::size_t cmp_depth = 0;
|
|
static constexpr std::size_t cmp_out_bits = 0;
|
|
static constexpr bool cmp_wild = false;
|
|
static constexpr std::size_t cmp_block = 0;
|
|
static constexpr bool cmp_idcf = false;
|
|
static constexpr bool is_verifiable = false;
|
|
static constexpr bool is_extractable = false;
|
|
};
|
|
|
|
/// @brief True iff the pack is "classic-shaped": every element is a bare output (no
|
|
/// `placed<>` from `at<>`, no `cmp_channel_tag<>`, and no verifiable/extractable).
|
|
template <typename ...Elems>
|
|
inline constexpr bool is_classic_pack_v =
|
|
!((is_placed_v<Elems> || is_cmp_channel_tag_v<Elems>
|
|
|| is_verifiable_tag_v<Elems> || is_extractable_tag_v<Elems>) || ...);
|
|
|
|
} // namespace incr
|
|
} // namespace detail
|
|
|
|
/// @brief Sparse heavy-hitters IDPF. Slot `i` is the point function on prefix
|
|
/// `Levels[i]`, evaluated with `out<i>`.
|
|
/// @tparam Levels levels
|
|
/// @tparam Betas betas
|
|
/// @param betas the `betas`
|
|
/// @return Sparse heavy-hitters IDPF
|
|
template <std::size_t ...Levels, typename ...Betas>
|
|
auto idpf_at(Betas ...betas)
|
|
{
|
|
static_assert(sizeof...(Levels) == sizeof...(Betas),
|
|
"idpf_at: one payload per prefix length");
|
|
return detail::incr::idpf_pack<std::index_sequence<Levels...>,
|
|
std::decay_t<Betas>...>{
|
|
std::tuple<std::decay_t<Betas>...>{std::move(betas)...}};
|
|
}
|
|
|
|
template <std::size_t ...I, typename ...Betas>
|
|
auto idpf_from_seq(std::index_sequence<I...>, Betas ...betas)
|
|
{
|
|
return idpf_at<(I + 1)...>(std::move(betas)...);
|
|
}
|
|
|
|
/// @brief Consecutive prefixes of length 1, 2, …, `sizeof...(Betas)`.
|
|
/// @tparam Betas betas
|
|
/// @param betas the `betas`
|
|
/// @return Consecutive prefixes of length 1, 2, …, `sizeof...(Betas)`
|
|
template <typename ...Betas>
|
|
auto idpf(Betas ...betas)
|
|
{
|
|
return idpf_from_seq(std::make_index_sequence<sizeof...(Betas)>{},
|
|
std::move(betas)...);
|
|
}
|
|
|
|
} // namespace dpf
|
|
|
|
#endif // LIBDPF_INCLUDE_DPF_PLACEMENT_HPP__
|