`make_dpf(x, y)` returns one key per party. Evaluation of a key yields that party's share. Leaf outputs are subtractive shares: open them with `dpf::reconstruct`, which computes `share0 - share1`. Comparison outputs are additive shares: `reconstruct` computes `share0 + share1`. A single-output `eval_point` returns a small handle; `*handle` is the share. An unassigned `dpf::wildcard` output throws `std::runtime_error`. `[from, to]` is inclusive. The points passed to `eval_sequence` are a nondecreasing range; an unsorted range throws `std::runtime_error`. Memoizers hold interior nodes between calls. Output buffers hold the shares a multi-point evaluation writes. Pass both as mutable named objects when a later call should reuse them. The factories `make_basic_path_memoizer`, `make_basic_interval_memoizer`, and `make_*_sequence_memoizer` unwrap `party_key`, so a workspace built from either party's type accepts both parties. Name that type with `dpf::unwrap_party_key_t>`. # Memoizers {#memoizers} ## Path memoizers {#path_memoizers} `eval_point` walks one root-to-leaf path. `make_basic_path_memoizer()` keeps every node of the previous point, and the next point recomputes only the suffix after the common prefix. `make_nonmemoizing_path_memoizer()` keeps one node and starts from the root on every call. A one-off `eval_point(key, x)` uses the nonmemoizing memoizer. Pass the memoizer as a mutable lvalue. The default argument is a new temporary, so it has no previous point to resume from. Keep a separate memoizer for each key you are in the middle of evaluating. A different root restarts the path. **Code samples**\n
- memoizers.cpp \include{cpp} evaluation/memoizers.cpp
## Interval memoizers {#interval_memoizers} `eval_interval` and `eval_full` expand every leaf in a range. `make_basic_interval_memoizer(from, to)` stores two levels of that range. That is the workspace the convenience overloads allocate. `make_full_tree_interval_memoizer(from, to)` keeps every level. `make_basic_full_memoizer()` and `make_full_tree_full_memoizer()` are the same workspaces sized for the whole domain. Size the memoizer for the widest interval you will pass to it. A wider interval throws `std::length_error`. The same key and the same endpoints leave the final interior level in place. A different key or a different interval rebuilds into the same allocation. Passing only the memoizer still allocates a fresh output buffer and returns `std::pair(buffer, iterable)`. ## Sequence memoizers {#sequence_memoizers} `make_sequence_recipe(begin, end)` compiles a sorted point list into a traversal. The recipe depends on the input type, and one recipe serves every key of that type. A sequence memoizer stores a reference to the recipe object it was built from and checks later calls by address. Pass that same object, and keep the recipe alive for as long as the memoizer is used. A copy of the recipe throws `std::logic_error`. `make_double_space_sequence_memoizer(recipe)` keeps two levels. It is what `eval_sequence(key, recipe, buffer)` allocates when you omit the memoizer. `make_inplace_reversing_sequence_memoizer(recipe)` keeps one level and reverses direction as it descends. `make_full_tree_sequence_memoizer(recipe)` retains every level. A key whose depth differs from the recipe throws `std::logic_error`. # Output buffers {#output_buffers} `output_buffer` is move-only storage with `size`, iterators, `data`, and `operator[]`. Build it with the factory that matches the evaluation: - `make_output_buffer_for_interval(key, from, to)` - `make_output_buffer_for_full(key)` - `make_output_buffer_for_subsequence(key, begin, end, tag)` - `make_output_buffer_for_recipe_subsequence(key, recipe, tag)` On a `party_key`, leaf slots are `subtractive_share`s and comparison slots are `additive_share`s. `dpf::bit`, `dpf::twobit`, and `dpf::nyble` slots are packed. Trivially default-constructible slot types are left uninitialized; the evaluation overwrites every slot it is responsible for. `eval_interval` and recipe `eval_sequence` take the buffer as a non-const reference, so the argument is a named object. The returned iterable refers into that buffer. Read it while the buffer is alive, and only over the points the iterable covers. The next evaluation overwrites those slots. For one output, the convenience overload returns the buffer itself as the first element of the pair. For several output indices it returns a tuple of buffers. `make_output_buffer(dpf::out, key, from, to)` and `make_output_buffer(dpf::cmp, key, n)` size a buffer for one channel of a multi-output or comparison key. The slot types follow the same party-share rule. **Code samples**\n
- output_buffers.cpp \include{cpp} evaluation/output_buffers.cpp
# dpf::eval_point {#eval_point} `eval_point(key, x)` evaluates output 0 at one input. `eval_point(key, x)` selects another output. Two or more indices, `eval_point<0, 1>(key, x)`, return a tuple of shares rather than handles. `eval_point(key, x, path)` continues a path memoizer. `eval_point(dpf::out, key, x, path)` and `eval_point(dpf::cmp, key, x, path)` are the same walk with an explicit channel. Comparison results are additive shares. **Code samples**\n
- eval_point.cpp \include{cpp} evaluation/eval_point.cpp
# dpf::eval_interval {#eval_interval} `eval_interval(key, from, to)` evaluates every input from `from` through `to`. The iterable yields one share per input, in that order. Optional arguments are an output buffer and then an interval memoizer. An output index pack, `eval_interval<0, 1>(key, from, to, buffers, memo)`, writes each selected output. **Code samples**\n
- eval_interval.cpp \include{cpp} evaluation/eval_interval.cpp
# dpf::eval_full {#eval_full} `eval_full(key)` is the closed interval from `std::numeric_limits::min()` through `max()`. The buffer and full-domain memoizer overloads match `eval_interval`. `make_output_buffer_for_full(key)` and `make_basic_full_memoizer()` size both for that domain. **Code samples**\n
- eval_full.cpp \include{cpp} evaluation/eval_full.cpp
# dpf::eval_sequence {#eval_sequence} `eval_sequence(key, begin, end, tag)` evaluates a sorted list. `dpf::return_output_only_tag_` stores one share per listed point. `dpf::return_entire_node_tag_` stores whole leaves; it is the default. The iterable still yields one share per listed point, in list order. `eval_sequence(key, recipe, buffer, memo, tag)` repeats that list. `memo` is a sequence memoizer bound to `recipe`. Omit `memo` to allocate a `double_space` workspace for that call. **Code samples**\n
- eval_sequence.cpp \include{cpp} evaluation/eval_sequence.cpp
# Buffered PRG {#buffered_prg} `dpf::randomness::buffered_prg` (alias `dpf::randomness::aes_buffered_prg`) is a forward cursor with one PRG stream per value type. `get()` and `fill(out, n)` consume the cursor. `at(index)` reads an absolute index and leaves the cursor where it is. `sampled()` is how far `get` and `fill` have advanced. `per_stream_buffer_elems` is at least 1. `dpf::randomness::lane_table` is the seekable form for a runtime set of roles. `value_at(role, index)` and `mask_at(role, index)` are independent streams, and a repeated index returns the same element. **Code samples**\n
- buffered_prg.cpp \include{cpp} evaluation/buffered_prg.cpp