You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
This is an idea to move forward on, not a sanctioned TODO or a commitment. It is a hypothesis to investigate, measure, and discuss. Treat this as a starting point for discussion.
Motivation
This is really the general cure for a cluster of already-tracked symptoms, reframed as one design rather than a new feature:
All four have the same root cause: there is no arena, so teardown is a recursive walk that frees every node individually. With a monotonic arena, teardown is O(1) and allocation-free by construction.
Why PMR is blocked today — a one-line diagnosis
create<T>() at include/nlohmann/json.hpp:411 does AllocatorType<T> alloc; — it default-constructs a fresh allocator on every allocation, and the destroy sites (include/nlohmann/json.hpp:652+, :2526+, :2597+) do the same. That hard-codes the assumption that the allocator is stateless.
std::pmr::polymorphic_allocator is stateful — it holds a memory_resource*. Under the current code every node would default-construct a pmr allocator pointing at the default resource (ignoring the intended arena), and teardown would deallocate through a default-constructed allocator, which for pmr is undefined behavior (deallocating on the wrong resource). No allocator instance is stored anywhere in basic_json, and nothing propagates one to children.
Two ways forward
(a) Full stateful-allocator support — probably not worth it
Store the allocator in each node, honor allocator_traits POCCA/POCMA/select_on_container_copy_construction, propagate on copy/move. "Correct," but deeply invasive and it grows sizeof(basic_json) by a pointer per node (the union is currently ~8 bytes + a type byte; +8 bytes everywhere is a real regression for large trees).
(b) Arena / PMR document mode — the Boost.JSON model (recommended)
Store the memory resource once at the root (a storage_ptr-style handle), not per node.
Thread it through parse() so all of a document's nodes come from one std::pmr::monotonic_buffer_resource.
Crucially, tear the document down by dropping the arena, not by running per-node destructors. This needs a "no individual free" destruction path (skip the deallocate loop when the tree is arena-owned) — the part that's missing today.
This directly resolves #5135 / #3583 / #5239 / #4843: teardown becomes O(1) and cannot allocate or throw.
Sketch
std::pmr::monotonic_buffer_resource arena;
auto doc = json::parse_into(arena, src); // illustrative: all nodes from `arena`// ... use doc ...// destruction: drop `arena`; no per-node frees run
Implementation notes
Add a storage_ptr-like handle (non-owning or ref-counted pointer to a memory_resource) stored once at the document root; children reference it.
Route create<T>() and every destroy site through the document's resource instead of a default-constructed allocator. These sites are load-bearing and must all be updated consistently.
Add an arena-owned flag / teardown path that skips per-node deallocation.
Keep the change opt-in: default behavior and sizeof(basic_json) must be unchanged for existing users.
Open questions / pitfalls
Copying an arena-backed value out of its document must deep-copy into the destination's allocator — arena-owned storage cannot be shared across trees.
select_on_container_copy_construction-correct behavior at the document boundary, even if individual nodes stay allocator-light.
Move semantics across documents with different resources.
How the public API expresses "this document owns/references arena X" without leaking lifetime footguns (the arena must outlive the document).
Status: brainstorming, not a plan
This is an idea to move forward on, not a sanctioned TODO or a commitment. It is a hypothesis to investigate, measure, and discuss. Treat this as a starting point for discussion.
Motivation
This is really the general cure for a cluster of already-tracked symptoms, reframed as one design rather than a new feature:
noexceptsemanticsbasic_jsondestructor is slowdestroy()All four have the same root cause: there is no arena, so teardown is a recursive walk that frees every node individually. With a monotonic arena, teardown is O(1) and allocation-free by construction.
Why PMR is blocked today — a one-line diagnosis
create<T>()atinclude/nlohmann/json.hpp:411doesAllocatorType<T> alloc;— it default-constructs a fresh allocator on every allocation, and the destroy sites (include/nlohmann/json.hpp:652+,:2526+,:2597+) do the same. That hard-codes the assumption that the allocator is stateless.std::pmr::polymorphic_allocatoris stateful — it holds amemory_resource*. Under the current code every node would default-construct a pmr allocator pointing at the default resource (ignoring the intended arena), and teardown woulddeallocatethrough a default-constructed allocator, which for pmr is undefined behavior (deallocating on the wrong resource). No allocator instance is stored anywhere inbasic_json, and nothing propagates one to children.Two ways forward
(a) Full stateful-allocator support — probably not worth it
Store the allocator in each node, honor
allocator_traitsPOCCA/POCMA/select_on_container_copy_construction, propagate on copy/move. "Correct," but deeply invasive and it growssizeof(basic_json)by a pointer per node (the union is currently ~8 bytes + a type byte; +8 bytes everywhere is a real regression for large trees).(b) Arena / PMR document mode — the Boost.JSON model (recommended)
storage_ptr-style handle), not per node.parse()so all of a document's nodes come from onestd::pmr::monotonic_buffer_resource.This directly resolves #5135 / #3583 / #5239 / #4843: teardown becomes O(1) and cannot allocate or throw.
Sketch
Implementation notes
storage_ptr-like handle (non-owning or ref-counted pointer to amemory_resource) stored once at the document root; children reference it.create<T>()and every destroy site through the document's resource instead of a default-constructed allocator. These sites are load-bearing and must all be updated consistently.sizeof(basic_json)must be unchanged for existing users.Open questions / pitfalls
select_on_container_copy_construction-correct behavior at the document boundary, even if individual nodes stay allocator-light.JSON_NO_IO/binary paths and with the borrowed-view idea (Idea: borrowed / zero-copy read-only view over a source buffer #5295), which is a different way to avoid per-node allocation (references source bytes rather than arena-allocating owned nodes).Dependencies / interactions