libdpf/include/dpf/placement.hpp
Ryan Henry 0d22946a0e Checkpoint the party/runtime stack before share-program and malicious-mode work.
Ship the TLS mesh, composer, Beaver/Yao/leaf MPC, prep/online paths, apps, and docs so the tree is pushable before elevating share_expr, security_mode, and prep resume.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-09-28 05:59:19 -06:00

692 lines
24 KiB
C++
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/// @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;
};
/// @brief Phantom pack element: keep a beaver-backed leaf so the payload can
/// be rewritten without regenerating the path to `α`.
struct updatable
{
static constexpr bool is_updatable_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;
template <typename T>
struct is_updatable_tag : std::false_type
{ };
template <>
struct is_updatable_tag<updatable> : std::true_type
{ };
template <typename T>
inline constexpr bool is_updatable_tag_v =
is_updatable_tag<std::decay_t<T>>::value;
/// @brief Compile-time switch for verifiable / extractable / output-MAC checks.
/// @details Default `auth_profile<>` is semi-honest: no correction seeds and
/// no leaf XOF. `verifiable` and `extractable` become phantom tags
/// for `make_dpf_profile`. `output_mac` stays on the profile for
/// Beaver and carry sessions; key generation does not consume it.
template <bool Verifiable = false, bool Extractable = false,
bool OutputMac = false>
struct auth_profile
{
static constexpr bool verifiable = Verifiable;
static constexpr bool extractable = Extractable;
static constexpr bool output_mac = OutputMac;
};
using semi_honest = auth_profile<false, false, false>;
using checked = auth_profile<true, true, true>;
/// @brief Tuple of phantom tags requested by `Profile` (may be empty).
/// @tparam Profile an `auth_profile` specialization
/// @return `verifiable` and/or `extractable`, or an empty tuple.
/// `Profile::output_mac` is not a key tag.
template <typename Profile = semi_honest>
HEDLEY_CONST
HEDLEY_NO_THROW
constexpr auto key_tags_tuple() noexcept
{
if constexpr (Profile::verifiable && Profile::extractable)
return std::tuple{verifiable{}, extractable{}};
else if constexpr (Profile::verifiable)
return std::tuple{verifiable{}};
else if constexpr (Profile::extractable)
return std::tuple{extractable{}};
else
return std::tuple{};
}
/// @brief Alias for `key_tags_tuple` (use with `std::apply` / `make_dpf_profile`).
/// @tparam Profile an `auth_profile` specialization
/// @return the same tuple as `key_tags_tuple<Profile>()`
/// @see key_tags_tuple
template <typename Profile = semi_honest>
HEDLEY_CONST
HEDLEY_NO_THROW
constexpr auto key_tags() noexcept
{
return key_tags_tuple<Profile>();
}
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, bool PublicAddend = false>
struct placed
{
static constexpr std::size_t prefix = N;
/// @brief When true, party 0 absorbs `addend` (eq / eq_at if_false) at eval.
static constexpr bool has_public_addend = PublicAddend;
/// @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, bool A>
struct is_placed<placed<N, O, A>> : std::true_type {};
template <typename T>
inline constexpr bool is_placed_v = is_placed<std::decay_t<T>>::value;
template <typename Tuple>
struct any_public_addend : std::false_type {};
template <typename ...Ps>
struct any_public_addend<std::tuple<Ps...>>
: std::bool_constant<(Ps::has_public_addend || ...)> {};
template <typename Tuple>
inline constexpr bool any_public_addend_v = any_public_addend<Tuple>::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, bool A>
struct normalize_one<BitLen, placed<N, T, A>>
{
using placed_tuple = std::tuple<placed<N, T, A>>;
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>
struct normalize_one<BitLen, updatable>
{
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;
};
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>
|| is_updatable_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__