---
title: 'LibFpp: Persistent STL-like C++ Library'
url: https://www.emergentmind.com/topics/libfpp
type: topic
---

# LibFpp: Persistent STL-like C++ Library

Searching arXiv for the LibFPP paper and closely related terminology.
LibFpp is a C++ container library that implements **persistent iterators with value semantics** and provides **persistent counterparts to the Standard Template Library (STL)**, including vectors, maps, sets, strings, and related abstractions [2604.14072]. Its central objective is to reconcile two programming styles that are ordinarily separated: STL-style iterator-centric imperative programming over mutable containers, and persistent data structures in which updates create new versions while previous versions remain accessible. In LibFpp, a persistent iterator snapshots the version of its underlying container at creation, and iterator operations act on the iterator-local copy of the container. The result is an STL-like programming interface intended to eliminate iterator invalidation and aliasing hazards while retaining familiar cursor-based programming patterns [2604.14072].

## 1. Concept and scope

LibFpp is presented as a library for **persistent vectors, maps, sets, strings, and other abstractions as persistent counterparts to the Standard Template Library (STL)** [2604.14072]. The design hierarchy described for the library treats these abstractions uniformly: a vector is a sequence; a multiset is a sorted vector; a set is a multiset with no duplicate elements; a multimap is a multiset of key-value pairs ordered by key; a map is a multimap with unique keys; and a string is a vector of characters under UTF-8.

The problem addressed is the tension between iterator expressiveness and mutation hazards. Ordinary STL iterators are tied to mutable containers and therefore inherit reference semantics. The paper emphasizes hazards including **iterator invalidation**, **aliasing**, **data races / concurrent modification hazards**, and subtle **side effects** from in-place mutation [2604.14072]. LibFpp replaces this model with persistent values. Variables may still be rebound, but operations create new versions rather than mutating shared state.

The library is intentionally STL-like in naming, general API shape, iterator idioms, and asymptotic complexity targets, but it is not positioned as a raw-performance substitute for array-backed mutable containers. The paper states that **“LibFpp is not intended as a drop-in replacement for STL containers in performance-critical code.”** This suggests a design point centered on safety, persistence, structural sharing, and expressiveness rather than maximal locality or pointer-arithmetic efficiency.

## 2. Persistent iterators and value semantics

The core abstraction is the **persistent iterator**. A persistent iterator is created from a container, for example through `begin()`, but unlike an STL iterator it is not merely a pointer-like reference into a mutable object. Instead, it is a value that includes both a cursor position and a snapshot of the referenced container version [2604.14072].

This yields the library’s defining semantic property: iterator operations such as `++`, `erase`, `insert`, and assignment-through-iterator act on the iterator-local persistent version. Mutating through one iterator does not mutate the original source container, and mutating through one iterator does not affect any other iterator. Previous versions remain accessible. The paper characterizes this explicitly as **true value semantics**.

The contrast with mutable STL code is central. In standard C++, erase-while-iterating requires careful reassignment because `erase` may invalidate the iterator. LibFpp instead permits update-capable traversal in a style that resembles STL but produces a new persistent value. A representative example is:

```cpp
string filterAscii(string s) {
    string::iterator i;
    for (i = s.begin(); i != s.end(); )
        if (*i >= 0x7F) i.erase();
        else            ++i;
    return i.value();
}
```

Here the iterator evolves through successive persistent versions; `s` remains unmodified, and `i.value()` materializes the iterator’s current logical container [2604.14072].

This design is also meant to address aliasing. In a mutable-reference model, two names may denote the same logical object, so updates through one name are visible through the other. In LibFpp, logical values are versioned. Physical structure may still be shared, but semantically visible mutation is absent. A plausible implication is that reasoning about local program state becomes closer to reasoning about immutable values, while preserving an imperative iterator idiom.

## 3. Data-structure foundation

LibFpp uses **finger trees** as the common implementation substrate for its containers [2604.14072]. The paper describes a finger tree as composed of recursive spine nodes, each with left and right finger nodes; fingers store 1–4 digits; digits are balanced 2–3 trees; and digit depth increases down the spine. The same representation underlies vectors, strings, sets, maps, multisets, and multimaps.

This uniformity is refined through invariants and subtree summaries. The paper states that each container is realized as **a finger tree equipped with a specialized monoid summarizing subtree information**. For sets and maps, the monoid is the **minimum element of the left subtree**; for strings, the monoid is the **total Unicode length under the UTF-8 encoding** [2604.14072]. This supports a single persistent substrate with specialized behavior for ordered lookup, sequence access, and UTF-8-aware string operations.

The stated performance properties of the finger-tree basis are: amortized `O(1)` adding and removing elements at the front or back; `O(\log N)` access, insertion, and deletion at arbitrary positions; and `O(\log N)` append and split [2604.14072]. Because the representation is persistent, updates only copy modified paths while structurally sharing unaffected subtrees.

The paper presents this as more than an implementation convenience. It enables “zero-cost upcasts” between related abstractions within the hierarchy. This suggests that the library’s container taxonomy is not a collection of unrelated wrappers, but a single persistent sequence framework specialized by ordering and summarization constraints.

## 4. Iterator implementation as optimized zippers

Persistent iterators are implemented as **optimized zippers over finger trees** [2604.14072]. A zipper stores a focused node together with the reversed path to the root. In LibFpp, the zipper node is described by the tuple

$$
\langle tree, child, dirty, parent \rangle
$$

where `tree` is the finger-tree node in the path, `child` indicates which child is taken, `dirty` indicates whether that subtree has been modified relative to the parent, and `parent` is the next zipper node up the reversed path or null at the root [2604.14072].

The iterator head points at the current element node, so dereference is direct. Navigation ascends to the lowest common ancestor of source and destination and then descends again. The paper states that local `++it` and `--it` are amortized `O(1)`, while larger jumps such as `it += N` are `O(\log N)`.

A key implementation device is the `dirty` flag. Assignment through an iterator is described by allocating a new leaf node `n`, creating a new zipper head, and marking that head dirty. The paper gives the resulting transformation as

$$
j = \langle n,~i.child,~\mathit{true},~i.parent \rangle
$$

which yields assignment in `O(1)` [2604.14072]. Full reconstruction of enclosing tree nodes is deferred. When upward traversal or value extraction forces rebuilding, dirty flags propagate and affected nodes are rebuilt lazily.

Insertion and deletion follow the same general strategy: unwind to a nearby ancestor where balancing can be restored, rebuild a local balanced subtree, mark the relevant zipper node dirty, and defer full root reconstruction. The paper states that the unwinding depth is amortized `O(1)` [2604.14072].

The operation `value()` materializes the iterator’s current container version by walking from the current zipper node to the root and rebuilding dirty nodes as needed. Its worst-case complexity is stated as `O(\log N)`, and it is described as pure: it does not invalidate or mutate the iterator [2604.14072].

## 5. API and operational behavior

LibFpp aims to support **all of the standard C++ STL iterator operations (increment, decrement, dereference, etc.)** with similar asymptotic complexity goals, subject to the constraints of a tree-based persistent representation [2604.14072]. The following operations are explicitly listed in the paper for representative container families.

| Container family | Representative operations | Reported character |
|---|---|---|
| Vector | `size`, `operator[]`, `front`, `back`, `push_back`, `push_front`, `pop_back`, `pop_front`, `begin`, iterator `insert`, `erase`, `assign`, `++`, `--`, `*`, `+= n` | Sequence-style persistent API |
| Set / Map | `size`, keyed access, indexed access, `contains`, `insert`, `erase`, `begin`, `find`, iterator `erase`, `++`, `--`, `*`, `+= n` | Ordered persistent API |
| String | `size`, `operator[]`, concatenation, `push_front`, `substr`, `begin`, iterator `insert`, `erase`, `assign`, `++`, `--`, `*`, `+= n` | UTF-8 persistent sequence |

The most visible semantic divergence from STL is that iterator-local updates do not mutate the original container. Instead, programs often end by extracting the iterator’s current version via `value()`. This is also what makes persistent iterators suitable for snapshot-based workflows. The paper’s text-editor example uses a cursor type alias

```cpp
using Cursor = string::iterator;
```

and stores undo/redo history as vectors of cursors. Since a cursor simultaneously represents a container version and a position, snapshots become cheap shallow copies through structural sharing [2604.14072].

The paper also uses a stateful filtering example to argue that persistent iterators support imperative, cursor-oriented patterns that are awkward with higher-order combinators. This suggests that LibFpp is aimed not only at persistence as a storage discipline, but at preserving a particular style of algorithm expression.

## 6. Complexity profile, performance, and engineering tradeoffs

The paper states that **the complexity for any standard operation is no more than `O(\log N)`**, and that LibFpp achieves **time and space complexities comparable to the STL across nearly all operations while adding full value semantics without complexity hazards** [2604.14072]. The main asymptotic tradeoff is that tree-based persistent sequences cannot retain `O(1)` random indexing and pointer-like jumps.

For sequence containers, the paper reports `\mathcal{O}(\log N)` for indexed access and `it += n`, whereas STL vector and string provide `\mathcal{O}(1)`. Conversely, several local iterator edits become asymptotically better than mutable array-backed containers: for vectors and strings, iterator-local `insert` and `erase` are reported as `\mathcal{O}(1)` in LibFpp but `\mathcal{O}(N)` in STL vector/string [2604.14072]. String concatenation is reported as `\mathcal{O}(\min(\log N, \log M))` versus `\mathcal{O}(N+M)` for STL strings, and `substr(idx)` as `\mathcal{O}(\log N)` versus `\mathcal{O}(N)`.

The evaluation uses a macOS arm64 M2 Pro system with 32 GB memory and Clang 21.1, comparing LibFpp against STL (`libc++`), Immer, Abseil, and Folly. Reported container-microbenchmark results versus STL include **56.9× slower** vector access, **3.4× slower** vector append, **262.0× slower** vector update, **3.3× slower** set access, **7.6× faster** vector concat, and **1.8× faster** set append [2604.14072]. Iterator microbenchmarks versus STL report **23.6× slower** vector `Access*`, **11.8× slower** vector `Append*`, **45.2× slower** vector `Update*`, **28.2× slower** vector `Erase*`, **3.1× slower** set `Access*`, **2.9× faster** set `Append*`, and **2.9× faster** set `Erase*` [2604.14072].

These measurements reflect the paper’s central tradeoff: zipper movement and tree traversal are substantially more expensive than pointer arithmetic on contiguous arrays, but modification-heavy node-based workloads can be competitive or faster. The paper summarizes the runtime picture by stating that **“LibFpp achieves practical performance comparable to existing persistent libraries, while offering a more general and expressive persistent iterator design.”**

Several engineering techniques are used to narrow the persistence overhead. The implementation uses reference counting for deterministic reclamation, justified by the acyclic nature of immutable trees [2604.14072]. It also introduces a fixed-size **custom memory allocator (CMA)** under the assumption that finger-tree and zipper nodes fit in one 64-byte cache line, with claimed **4 inlined instructions for allocation** and **5 inlined instructions for deallocation**. In addition, the library performs destructive update when safe—specifically, **when all nodes along an update path have a reference count of exactly one**—and packs multiple elements into each leaf node to improve locality and reduce tree depth [2604.14072]. This suggests a copy-on-write-like optimization strategy under uniqueness.

## 7. Relation to functional persistence and prior C++ practice

LibFpp is positioned between two established traditions. On one side are mutable STL containers and iterators, which are expressive but hazard-prone. On the other side are functional persistent data structures, which avoid mutation hazards but commonly rely on recursion and higher-order combinators such as `map`, `filter`, `foldl`, and `traverse` [2604.14072].

The paper argues that these combinator-oriented interfaces are not always natural for stateful iteration, early exit, cursor-style editing, undo/redo, or algorithms best expressed as loops. It contrasts LibFpp’s `filterAscii` example with Haskell `Seq` code using index-based deletion, noting that the latter becomes `O(N \cdot \log N)` while the LibFpp iterator version is `O(N)` [2604.14072]. This suggests that the novelty is not persistence alone, but persistence combined with an efficient cursor abstraction.

Among C++ persistent libraries, the paper identifies Immer as the closest comparison. The stated differences are that Immer uses RRB trees and CHAMPs, exposes const iterators only, and makes persistence/transience explicit, whereas LibFpp uses finger trees uniformly and introduces update-capable persistent iterators with value semantics [2604.14072]. The paper further states: **“As far as we are aware, LibFpp is the first implementation of zippers for finger trees and the first to use zippers to implement an STL-like iterator-like API.”**

The resulting interpretation is that LibFpp is not merely an STL-inspired persistent container set. It is a semantic redesign of iterators themselves. Containers are persistent values implemented by finger trees; iterators are persistent cursor values implemented by zippers; local updates create new versions via structural sharing; and `value()` materializes the iterator’s current snapshot. The library’s contribution is therefore the combination of persistence, iterator-based expressiveness, and explicit elimination of iterator-invalidating mutation patterns [2604.14072].

Source: https://www.emergentmind.com/topics/libfpp