Repository navigation
Fix destroy() for ObjectTypes without reverse iteration - #5767
Merged
Merged
Conversation
The non-recursive destroy walk from #5762 picked an object's last child via object_t::rbegin() and std::prev(end()). Neither is available for every ObjectType: no_key_compare_map in unit-custom-object-type.cpp has no rbegin(), so develop no longer compiles that test, and hash maps such as std::unordered_map only have forward iterators. The walk can take an object's children in any order, as long as it finds the same child again while the object is not modified in between. So objects with bidirectional iterators keep using their last child (O(1) to remove from vector-based maps like ordered_map), and objects with forward-only iterators use begin() instead. No reverse iteration or rbegin() is needed any more, and the walk stays allocation-free. Adds a forward-only ObjectType to the tests, destroyed both with mixed nesting and 100000 levels deep. Signed-off-by: Niels Lohmann <mail@nlohmann.me>
gregmarr
approved these changes
Oct 6, 2026
This was referenced Oct 6, 2026
2 tasks done
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Since #5762 (commit 0a36586), develop no longer compiles
tests/src/unit-custom-object-type.cpp:The non-recursive, allocation-free destroy walk picked an object's last child via
object_t::rbegin()(inlast_child()) and removed it witherase(std::prev(end()))(inpop_last_child()). Neither is part of theObjectTyperequirements:no_key_compare_map(in that test) has norbegin();std::unordered_map(through the documented adapter) only have forward iterators, sostd::prev(end())does not compile either.iter_implalready states thatobject_t::iteratormay be forward-only as long as reverse iteration andoperator--are not used.Fix
The walk can take a container's children in any order, as long as it finds the same child again while that container is not modified, which holds because a parent is never touched while the walk is below it. The helpers are renamed to
walk_child()/pop_walk_child(). An object's child is chosen by tag dispatch on its iterator category:std::map,ordered_map,boost::container::flat_map, ...): last child,erase(std::prev(end())). This is what the code did before, now withoutrbegin(), so vector-based maps still remove each child in O(1) and destruction stays linear.erase(begin()). For node-based hash maps such asstd::unordered_map,begin()anderaseare O(1).Arrays are unchanged (
back()/pop_back()). Nothing allocates, and the comments indestroy_container()now describe the "walk child" instead of the "last child".One caveat for forward-only maps: open-addressing hash maps whose
begin()scans for the first occupied slot (e.g.absl::flat_hash_map) can make repeatederase(begin())superlinear for very wide objects. On develop such maps do not compile at all. Compared with 3.12.0, whose destroy() moved children onto a heap-allocated stack in linear time, destroying very wide objects of such maps can be slower. Making the walk independent ofbegin()'s cost would need an iterator stored per level, which the allocation-free design has no room for.Tests
unit-custom-object-type.cppcompiles again (no_key_compare_map).forward_only_mapObjectTypewhose iterator isstd::forward_iterator_tag(norbegin(), nooperator--). It is destroyed with mixed nested objects/arrays, througherase()and assignment, and 100000 levels deep. Against develop's header it fails to compile (rbeginand thestd::prevstatic assertion), and it passes with this change (C++11/C++20, and with ASan/UBSan +JSON_DIAGNOSTICS).-DJSON_BuildTests=ON, json_test_data) passes locally: 100% tests passed, 0 tests failed out of 139 (Debug, AppleClang, macOS).Public API
No breaking changes. The changed helpers are private members of
basic_json::json_value. ForObjectTypes with bidirectional iterators the behavior is identical.ObjectTypes with forward-only iterators or withoutrbegin()compile again, as they did before #5762.This PR was written by Claude Code.
🤖 Generated with Claude Code