Annotate noexcept and constexpr with HEDLEY, and add interval containment, ChaCha, and the dyadic range tables.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Ryan Henry 2026-09-24 20:44:07 -06:00
parent 875f09fec1
commit 0d8a5a8131
97 changed files with 9212 additions and 1159 deletions

View file

@ -0,0 +1,62 @@
#include <cstdint>
#include <cstring>
#include <iostream>
#include "dpf.hpp"
/// `buffered_prg` is a forward cursor, one stream per value type.
/// `at<I>(index)` reads by absolute index and does not move the cursor.
/// `lane_table` is the seekable form: value and mask streams per role.
int main()
{
//! [buffered-prg]
dpf::randomness::aes_buffered_prg<std::uint64_t, std::uint32_t> prg(
/*per stream*/ 64);
std::uint64_t first = prg.get<0>();
std::uint64_t second = prg.get<0>();
// Absolute index 0 is `first` again. The cursor stays at 2.
std::uint64_t replay = prg.at<0>(0);
std::uint32_t other_stream = prg.get<1>();
std::uint64_t batch[4];
prg.fill<0>(batch, 4);
//! [buffered-prg]
if (std::memcmp(&replay, &first, sizeof(first)) != 0)
{
std::cerr << "buffered_prg at(0)\n";
return 1;
}
if (prg.sampled<0>() != 6)
{
std::cerr << "buffered_prg cursor\n";
return 1;
}
// Stream 1 has its own cursor.
if (prg.sampled<1>() != 1)
{
std::cerr << "buffered_prg stream 1\n";
return 1;
}
(void)second;
(void)other_stream;
(void)batch;
//! [lane-table]
dpf::randomness::lane_table<std::uint64_t> lanes(/*window*/ 32);
// Order does not matter. Masks are a separate stream from values.
auto v_late = lanes.value_at(/*role*/ 3, /*index*/ 10);
auto v_early = lanes.value_at(3, 10);
auto mask = lanes.mask_at(3, 10);
//! [lane-table]
if (std::memcmp(&v_late, &v_early, sizeof(v_late)) != 0)
{
std::cerr << "lane_table replay\n";
return 1;
}
(void)mask;
std::cout << "ok\n";
return 0;
}

View file

@ -1,31 +1,44 @@
#include <cstdint>
#include <iostream>
#include <limits>
#include "dpf.hpp"
int main(int arc, char * argv[])
/// Every input of the domain, from `numeric_limits<Input>::min()` through
/// `max()`. Same shape as `eval_interval`.
int main()
{
uint16_t x = 42; // Input value
using prg = dpf::prg::counter_wrapper<dpf::prg::dummy>; // This is just to count the number of PRG invocations
auto before = prg::count(); // In order to show how much this program cost
auto [dpf0, dpf1] = dpf::make_dpf<prg>(x);
auto after = prg::count();
std::cout << "dpf::make_dpf used " << (after-before) << "\n";
const std::uint8_t alpha = 42;
const std::uint64_t beta = 7;
auto [k0, k1] = dpf::make_dpf(alpha, beta);
before = prg::count();
auto [buf0, iter0] = dpf::eval_full(dpf0);
after = prg::count();
std::cout << "dpf::eval_full(dpf0) used " << (after-before) << "\n";
before = prg::count();
auto [buf1, iter1] = dpf::eval_full(dpf1);
after = prg::count();
std::cout << "dpf::eval_full(dpf1) used " << (after-before) << "\n";
// Retrieve the original input by iterating over the two buffers
for (size_t i = 0; i < buf0.size(); ++i) {
bool item1 = buf0[i];
bool item2 = buf1[i];
if (item1 ^ item2) std::cout << "The original input is: " << i << std::endl;
//! [eval-full]
auto [buf0, iter0] = dpf::eval_full(k0);
auto [buf1, iter1] = dpf::eval_full(k1);
auto it0 = std::begin(iter0);
auto it1 = std::begin(iter1);
for (int x = std::numeric_limits<std::uint8_t>::min();
x <= std::numeric_limits<std::uint8_t>::max();
++x, ++it0, ++it1)
{
std::uint64_t got = dpf::reconstruct(*it0, *it1);
std::uint64_t expect = (static_cast<std::uint8_t>(x) == alpha) ? beta : 0;
if (got != expect)
{
std::cerr << "eval_full\n";
return 1;
}
}
//! [eval-full]
if (it0 != std::end(iter0) || it1 != std::end(iter1))
{
std::cerr << "eval_full length\n";
return 1;
}
std::cout << "Total PRG invocation: " << prg::count() << "\n";
std::cout << beta << "\n";
(void)buf0;
(void)buf1;
return 0;
}
}

View file

@ -1,42 +1,43 @@
#include <cstdint>
#include <iostream>
#include "dpf.hpp"
int main(int arc, char * argv[])
/// Inclusive range. The returned iterable yields one share per input in
/// `[from, to]`, in that order.
int main()
{
uint16_t x = 42, y;
using prg = dpf::prg::counter_wrapper<dpf::prg::dummy>;
const std::uint8_t alpha = 42;
const std::uint64_t beta = 7;
const std::uint8_t from = 40;
const std::uint8_t to = 50;
auto [k0, k1] = dpf::make_dpf(alpha, beta);
// Make the DPF
auto before = prg::count();
auto [dpf0, dpf1] = dpf::make_dpf<prg>(x);
auto after = prg::count();
std::cout << "dpf::make_dpf prg invocation: " << (after-before) << "\n";
//! [eval-interval]
auto [buf0, iter0] = dpf::eval_interval(k0, from, to);
auto [buf1, iter1] = dpf::eval_interval(k1, from, to);
// Evaluate the DPF by interval
before = prg::count();
int from = 0, to = 49;
auto [buf0, iter0] = dpf::eval_interval(dpf0, from, to);
auto [buf1, iter1] = dpf::eval_interval(dpf1, from, to);
after = prg::count();
std::cout << "dpf::eval_interval prg invocation: " << (after-before) << "\n";
// Retrieve the original input by iterating over the two buffers
std::vector<bool> result;
for (size_t i = from; i < to+1; ++i) {
bool item1 = buf0[i];
bool item2 = buf1[i];
result.push_back(item1 ^ item2);
if (item1 ^ item2) y=i;
auto it0 = std::begin(iter0);
auto it1 = std::begin(iter1);
for (std::uint8_t x = from; x <= to; ++x, ++it0, ++it1)
{
std::uint64_t got = dpf::reconstruct(*it0, *it1);
std::uint64_t expect = (x == alpha) ? beta : 0;
if (got != expect)
{
std::cerr << "eval_interval\n";
return 1;
}
}
// Print out the XOR interval
for (const auto& item : result) {
std::cout << static_cast<bool>(item);
//! [eval-interval]
if (it0 != std::end(iter0) || it1 != std::end(iter1))
{
std::cerr << "eval_interval length\n";
return 1;
}
std::cout << std::endl;
if (y == x) std::cout << "The orginal value is: " << x << std::endl;
else std::cout << "The evaluated inputs did not match the original value." << std::endl;
std::cout << "Total PRG invocation: " << prg::count() << std::endl;
std::cout << dpf::reconstruct(*std::begin(iter0), *std::begin(iter1)) << "\n";
(void)buf0;
(void)buf1;
return 0;
}
}

View file

@ -1,17 +1,54 @@
#include <cstdint>
#include <iostream>
#include "dpf.hpp"
int main(int arc, char * argv[])
/// One input. `*eval_point` is that party's share of output 0.
/// Reconstruct with `dpf::reconstruct` (leaf outputs are subtractive).
int main()
{
uint16_t x = 42;
auto [dpf0, dpf1] = dpf::make_dpf(x);
const std::uint8_t alpha = 42;
const std::uint64_t beta = 7;
auto [k0, k1] = dpf::make_dpf(alpha, beta);
using key_t = dpf::unwrap_party_key_t<std::decay_t<decltype(k0)>>;
auto res = dpf::eval_point(dpf0, x);
//! [eval-point]
auto y0 = *dpf::eval_point(k0, alpha);
auto y1 = *dpf::eval_point(k1, alpha);
std::uint64_t opened = dpf::reconstruct(y0, y1);
//! [eval-point]
if (opened != beta)
{
std::cerr << "eval_point at the programmed input\n";
return 1;
}
std::cout << *dpf::eval_point(dpf0, 41) << " ^ " << *dpf::eval_point(dpf1, 41) << " = " << (*dpf::eval_point(dpf0, 41) ^ *dpf::eval_point(dpf1, 41)) << "\n"; // = 0
std::cout << *dpf::eval_point(dpf0, x) << " ^ " << *dpf::eval_point(dpf1, x) << " = " << (*dpf::eval_point(dpf0, x) ^ *dpf::eval_point(dpf1, x)) << "\n"; // = 1
std::cout << *dpf::eval_point(dpf0, 43) << " ^ " << *dpf::eval_point(dpf1, 43) << " = " << (*dpf::eval_point(dpf0, 43) ^ *dpf::eval_point(dpf1, 43)) << "\n"; // = 0
auto off0 = *dpf::eval_point(k0, std::uint8_t{41});
auto off1 = *dpf::eval_point(k1, std::uint8_t{41});
if (dpf::reconstruct(off0, off1) != 0)
{
std::cerr << "eval_point off the programmed input\n";
return 1;
}
//! [eval-point-memo]
// One mutable memoizer per key. Nearby points reuse the common prefix.
auto path0 = dpf::make_basic_path_memoizer<key_t>();
auto path1 = dpf::make_basic_path_memoizer<key_t>();
for (int x = 40; x <= 44; ++x)
{
auto s0 = *dpf::eval_point(k0, static_cast<std::uint8_t>(x), path0);
auto s1 = *dpf::eval_point(k1, static_cast<std::uint8_t>(x), path1);
std::uint64_t got = dpf::reconstruct(s0, s1);
std::uint64_t expect = (static_cast<std::uint8_t>(x) == alpha) ? beta : 0;
if (got != expect)
{
std::cerr << "path memoizer\n";
return 1;
}
}
//! [eval-point-memo]
std::cout << opened << "\n";
return 0;
}
}

View file

@ -1,54 +1,114 @@
#include <chrono>
#include <array>
#include <cstdint>
#include <iostream>
#include "dpf.hpp"
using std::chrono::high_resolution_clock;
using std::chrono::duration_cast;
using std::chrono::duration;
using std::chrono::milliseconds;
int main(int argc, char * argv[])
/// A sorted point list. `return_output_only_tag_` stores one share per point.
/// A `sequence_recipe` compiled from that list is reusable across keys.
int main()
{
using input_type = uint8_t;
using prg = dpf::prg::counter_wrapper<dpf::prg::dummy>;
const std::uint8_t alpha = 42;
const std::uint64_t beta = 7;
auto [k0, k1] = dpf::make_dpf(alpha, beta);
using key_t = dpf::unwrap_party_key_t<std::decay_t<decltype(k0)>>;
constexpr int N = 50;
std::array<input_type, N> keys{};
for(int i=0; i<N; i++) keys[i] = i; // Create an array of keys
//! [eval-sequence]
// Nondecreasing. An unsorted range throws std::runtime_error.
std::array<std::uint8_t, 5> points{1, 7, 42, 100, 200};
// eval_sequence with recipe
input_type x = 42;
auto [dpf0, dpf1] = dpf::make_dpf<prg>(x); // First DPF to be able to create the recipe
auto t1 = high_resolution_clock::now(); // To measure the time of execution
auto before = prg::count(); // To count the number of PRG invocations
auto recipe0 = dpf::make_sequence_recipe(dpf0, std::begin(keys), std::end(keys)); // Create a recipe
auto recipe1 = dpf::make_sequence_recipe(dpf1, std::begin(keys), std::end(keys)); // Create a recipe
for (int i=0; i<N; i++)
auto [buf0, iter0] = dpf::eval_sequence(k0, points.begin(), points.end(),
dpf::return_output_only_tag_{});
auto [buf1, iter1] = dpf::eval_sequence(k1, points.begin(), points.end(),
dpf::return_output_only_tag_{});
auto it0 = std::begin(iter0);
auto it1 = std::begin(iter1);
for (std::uint8_t x : points)
{
auto [dpf00, dpf11] = dpf::make_dpf<prg>(i); // Make 50 DPFs
dpf::eval_sequence(dpf0, recipe0); // Evaluate the DPFs with the recipe
dpf::eval_sequence(dpf1, recipe0); // Evaluate the DPFs with the recipe
std::uint64_t got = dpf::reconstruct(*it0, *it1);
std::uint64_t expect = (x == alpha) ? beta : 0;
if (got != expect)
{
std::cerr << "eval_sequence\n";
return 1;
}
++it0;
++it1;
}
auto after = prg::count(); // Count the number of PRG invocations
std::cout << "dpf::eval_sequence with recipe " << (after-before) << "\n";
//! [eval-sequence]
// eval_sequence without the recipe
auto t2 = high_resolution_clock::now();
duration<double, std::milli> ms_double = t2 - t1;
std::cout << "Time of execution: " << ms_double.count() << "ms\n";
auto t3 = high_resolution_clock::now();
before = prg::count();
for (int i=0; i<N; i++)
//! [eval-sequence-recipe]
// The recipe is a property of the point list and the key's input type.
// Bind the memoizer to this recipe object and pass that same object back.
auto recipe = dpf::make_sequence_recipe<key_t>(points.begin(), points.end());
auto memo0 = dpf::make_double_space_sequence_memoizer<key_t>(recipe);
auto memo1 = dpf::make_double_space_sequence_memoizer<key_t>(recipe);
auto sbuf0 = dpf::make_output_buffer_for_recipe_subsequence(k0, recipe,
dpf::return_output_only_tag_{});
auto sbuf1 = dpf::make_output_buffer_for_recipe_subsequence(k1, recipe,
dpf::return_output_only_tag_{});
auto seq0 = dpf::eval_sequence(k0, recipe, sbuf0, memo0,
dpf::return_output_only_tag_{});
auto seq1 = dpf::eval_sequence(k1, recipe, sbuf1, memo1,
dpf::return_output_only_tag_{});
//! [eval-sequence-recipe]
// Omitting the memoizer allocates a double-space workspace for that call.
seq0 = dpf::eval_sequence(k0, recipe, sbuf0, dpf::return_output_only_tag_{});
seq1 = dpf::eval_sequence(k1, recipe, sbuf1, dpf::return_output_only_tag_{});
it0 = std::begin(seq0);
it1 = std::begin(seq1);
for (std::uint8_t x : points)
{
auto [dpf00, dpf11] = dpf::make_dpf<prg>(i);
dpf::eval_sequence(dpf00, std::begin(keys), std::end(keys));
dpf::eval_sequence(dpf11, std::begin(keys), std::end(keys));
if (dpf::reconstruct(*it0, *it1) != ((x == alpha) ? beta : 0))
{
std::cerr << "eval_sequence recipe, default memoizer\n";
return 1;
}
++it0;
++it1;
}
after = prg::count();
std::cout << "dpf::eval_sequence used " << (after-before) << "\n";
auto t4 = high_resolution_clock::now();
duration<double, std::milli> ms_double2 = t4 - t3;
std::cout << "Time of execution with the memoizers: " << ms_double2.count() << "ms\n";
seq0 = dpf::eval_sequence(k0, recipe, sbuf0, memo0, dpf::return_output_only_tag_{});
seq1 = dpf::eval_sequence(k1, recipe, sbuf1, memo1, dpf::return_output_only_tag_{});
it0 = std::begin(seq0);
it1 = std::begin(seq1);
for (std::uint8_t x : points)
{
std::uint64_t got = dpf::reconstruct(*it0, *it1);
std::uint64_t expect = (x == alpha) ? beta : 0;
if (got != expect)
{
std::cerr << "eval_sequence recipe\n";
return 1;
}
++it0;
++it1;
}
// Same recipe, same memoizers, same buffers: a second key overwrites them.
auto [k0b, k1b] = dpf::make_dpf(std::uint8_t{100}, std::uint64_t{9});
seq0 = dpf::eval_sequence(k0b, recipe, sbuf0, memo0, dpf::return_output_only_tag_{});
seq1 = dpf::eval_sequence(k1b, recipe, sbuf1, memo1, dpf::return_output_only_tag_{});
it0 = std::begin(seq0);
it1 = std::begin(seq1);
for (std::uint8_t x : points)
{
std::uint64_t got = dpf::reconstruct(*it0, *it1);
std::uint64_t expect = (x == 100) ? 9 : 0;
if (got != expect)
{
std::cerr << "eval_sequence recipe reuse\n";
return 1;
}
++it0;
++it1;
}
std::cout << beta << "\n";
(void)buf0;
(void)buf1;
return 0;
}
}

View file

@ -1,50 +1,128 @@
#include <array>
#include <cstdint>
#include <iostream>
#include <chrono>
#include "dpf.hpp"
using std::chrono::high_resolution_clock;
using std::chrono::duration_cast;
using std::chrono::duration;
using std::chrono::milliseconds;
int main(int arc, char * argv[])
/// Memoizers are workspaces of interior nodes. Pass a mutable lvalue.
/// A temporary (including the default argument) cannot remember a prefix.
int main()
{
// Making the DPF with an integer value
uint16_t x = 42;
using prg = dpf::prg::counter_wrapper<dpf::prg::dummy>;
auto [dpf0, dpf1] = dpf::make_dpf<prg>(x);
const std::uint8_t alpha = 42;
const std::uint64_t beta = 7;
auto [k0, k1] = dpf::make_dpf(alpha, beta);
using key_t = dpf::unwrap_party_key_t<std::decay_t<decltype(k0)>>;
// Evaluating the DPF and counting how much it cost without memoizers
auto t1 = high_resolution_clock::now();
auto before = prg::count();
for (int i = 0; i<1024*1024; i++)
//! [path-memoizer]
auto path0 = dpf::make_basic_path_memoizer<key_t>();
auto path1 = dpf::make_basic_path_memoizer<key_t>();
for (int x = 0; x < 256; ++x)
{
dpf::eval_point(dpf0, i);
auto y0 = *dpf::eval_point(k0, static_cast<std::uint8_t>(x), path0);
auto y1 = *dpf::eval_point(k1, static_cast<std::uint8_t>(x), path1);
std::uint64_t got = dpf::reconstruct(y0, y1);
std::uint64_t expect = (static_cast<std::uint8_t>(x) == alpha) ? beta : 0;
if (got != expect)
{
std::cerr << "basic_path_memoizer\n";
return 1;
}
}
// Printing out the results
auto after = prg::count();
std::cout << "Without memoizers: " << "\n";
std::cout << "PRG invocation: " << after-before << "\n";
auto t2 = high_resolution_clock::now();
duration<double, std::milli> ms_double = t2 - t1;
std::cout << "Time of execution: " << ms_double.count() << "ms\n";
//! [path-memoizer]
// Evaluating the DPF and counting how much it cost with memoizers
auto t3 = high_resolution_clock::now();
before = prg::count();
auto path = dpf::make_basic_path_memoizer(dpf0);
for (int i = 0; i<1024*1024; i++)
// One node, no prefix reuse. Correct for a single query.
auto once0 = dpf::make_nonmemoizing_path_memoizer<key_t>();
auto once1 = dpf::make_nonmemoizing_path_memoizer<key_t>();
if (dpf::reconstruct(*dpf::eval_point(k0, alpha, once0),
*dpf::eval_point(k1, alpha, once1)) != beta)
{
dpf::eval_point(dpf0, i, path);
std::cerr << "nonmemoizing_path_memoizer\n";
return 1;
}
// Printing out the results
after = prg::count();
std::cout << "With memoizers: " << "\n";
std::cout << "PRG invocation: " << after-before << "\n";
auto t4 = high_resolution_clock::now();
duration<double, std::milli> ms_double2 = t4 - t3;
std::cout << "Time of execution with the memoizers: " << ms_double2.count() << "ms\n";
//! [interval-memoizer]
const std::uint8_t from = 40;
const std::uint8_t to = 50;
// Sized for [from, to]. A wider interval throws std::length_error.
// `make_full_tree_interval_memoizer` keeps every level instead of two.
auto memo0 = dpf::make_basic_interval_memoizer<key_t>(from, to);
auto memo1 = dpf::make_basic_interval_memoizer<key_t>(from, to);
auto [ibuf0, i0] = dpf::eval_interval(k0, from, to, memo0);
auto [ibuf1, i1] = dpf::eval_interval(k1, from, to, memo1);
//! [interval-memoizer]
auto it0 = std::begin(i0);
auto it1 = std::begin(i1);
for (std::uint8_t x = from; x <= to; ++x, ++it0, ++it1)
{
std::uint64_t got = dpf::reconstruct(*it0, *it1);
std::uint64_t expect = (x == alpha) ? beta : 0;
if (got != expect)
{
std::cerr << "basic_interval_memoizer\n";
return 1;
}
}
// A different key rebuilds into the same memoizer.
auto [k0b, k1b] = dpf::make_dpf(std::uint8_t{44}, std::uint64_t{9});
std::tie(ibuf0, i0) = dpf::eval_interval(k0b, from, to, memo0);
std::tie(ibuf1, i1) = dpf::eval_interval(k1b, from, to, memo1);
it0 = std::begin(i0);
it1 = std::begin(i1);
for (std::uint8_t x = from; x <= to; ++x, ++it0, ++it1)
{
std::uint64_t got = dpf::reconstruct(*it0, *it1);
std::uint64_t expect = (x == 44) ? 9 : 0;
if (got != expect)
{
std::cerr << "interval memoizer reuse\n";
return 1;
}
}
//! [sequence-memoizer]
std::array<std::uint8_t, 5> points{1, 7, 42, 100, 200};
// The memoizer stores a reference to this recipe and checks it by address.
auto recipe = dpf::make_sequence_recipe<key_t>(points.begin(), points.end());
auto seq0 = dpf::make_inplace_reversing_sequence_memoizer<key_t>(recipe);
auto seq1 = dpf::make_inplace_reversing_sequence_memoizer<key_t>(recipe);
auto [sbuf0, s0] = dpf::eval_sequence(k0, recipe, seq0, dpf::return_output_only_tag_{});
auto [sbuf1, s1] = dpf::eval_sequence(k1, recipe, seq1, dpf::return_output_only_tag_{});
//! [sequence-memoizer]
it0 = std::begin(s0);
it1 = std::begin(s1);
for (std::uint8_t x : points)
{
std::uint64_t got = dpf::reconstruct(*it0, *it1);
std::uint64_t expect = (x == alpha) ? beta : 0;
if (got != expect)
{
std::cerr << "sequence memoizer\n";
return 1;
}
++it0;
++it1;
}
std::tie(sbuf0, s0) = dpf::eval_sequence(k0b, recipe, seq0, dpf::return_output_only_tag_{});
std::tie(sbuf1, s1) = dpf::eval_sequence(k1b, recipe, seq1, dpf::return_output_only_tag_{});
it0 = std::begin(s0);
it1 = std::begin(s1);
for (std::uint8_t x : points)
{
std::uint64_t got = dpf::reconstruct(*it0, *it1);
std::uint64_t expect = (x == 44) ? 9 : 0;
if (got != expect)
{
std::cerr << "sequence memoizer reuse\n";
return 1;
}
++it0;
++it1;
}
std::cout << beta << "\n";
return 0;
}
}

View file

@ -1,6 +1,82 @@
#include <cstdint>
#include <iostream>
#include "dpf.hpp"
int main(int argc, char * argv[])
/// Output buffers are move-only. `eval_interval` takes the buffer by
/// non-const reference, so name it. The iterable points into that buffer;
/// read it only while the buffer is still alive, and only over the points
/// the iterable covers.
int main()
{
const std::uint8_t alpha = 42;
const std::uint64_t beta = 7;
const std::uint8_t from = 40;
const std::uint8_t to = 50;
auto [k0, k1] = dpf::make_dpf(alpha, beta);
using key_t = dpf::unwrap_party_key_t<std::decay_t<decltype(k0)>>;
//! [output-buffer]
auto buf0 = dpf::make_output_buffer_for_interval(k0, from, to);
auto buf1 = dpf::make_output_buffer_for_interval(k1, from, to);
auto memo0 = dpf::make_basic_interval_memoizer<key_t>(from, to);
auto memo1 = dpf::make_basic_interval_memoizer<key_t>(from, to);
auto iter0 = dpf::eval_interval(k0, from, to, buf0, memo0);
auto iter1 = dpf::eval_interval(k1, from, to, buf1, memo1);
auto it0 = std::begin(iter0);
auto it1 = std::begin(iter1);
for (std::uint8_t x = from; x <= to; ++x, ++it0, ++it1)
{
std::uint64_t got = dpf::reconstruct(*it0, *it1);
std::uint64_t expect = (x == alpha) ? beta : 0;
if (got != expect)
{
std::cerr << "output buffer\n";
return 1;
}
}
//! [output-buffer]
// The next evaluation overwrites the same slots.
auto [k0b, k1b] = dpf::make_dpf(std::uint8_t{44}, std::uint64_t{9});
iter0 = dpf::eval_interval(k0b, from, to, buf0, memo0);
iter1 = dpf::eval_interval(k1b, from, to, buf1, memo1);
it0 = std::begin(iter0);
it1 = std::begin(iter1);
for (std::uint8_t x = from; x <= to; ++x, ++it0, ++it1)
{
std::uint64_t got = dpf::reconstruct(*it0, *it1);
std::uint64_t expect = (x == 44) ? 9 : 0;
if (got != expect)
{
std::cerr << "output buffer reuse\n";
return 1;
}
}
//! [output-buffer-full]
auto full0 = dpf::make_output_buffer_for_full(k0);
auto full1 = dpf::make_output_buffer_for_full(k1);
auto fmemo0 = dpf::make_basic_full_memoizer<key_t>();
auto fmemo1 = dpf::make_basic_full_memoizer<key_t>();
auto f0 = dpf::eval_full(k0, full0, fmemo0);
auto f1 = dpf::eval_full(k1, full1, fmemo1);
//! [output-buffer-full]
it0 = std::begin(f0);
it1 = std::begin(f1);
for (int x = 0; x < 256; ++x, ++it0, ++it1)
{
std::uint64_t got = dpf::reconstruct(*it0, *it1);
std::uint64_t expect = (static_cast<std::uint8_t>(x) == alpha) ? beta : 0;
if (got != expect)
{
std::cerr << "full output buffer\n";
return 1;
}
}
std::cout << beta << "\n";
return 0;
}
}