mirror of
https://github.com/nlohmann/json.git
synced 2026-08-29 04:17:32 +00:00
Relax the ArrayType and ObjectType requirements
Two requirements forced users of otherwise suitable containers to write a wrapper, and neither was load-bearing. array_t::capacity() was read in push_back(), emplace_back(), operator+=(), and operator[](size_type), but set_parent() only looks at the value under JSON_DIAGNOSTICS; without diagnostics it was computed and discarded. Read it through array_capacity(), which reports unknown_size() when diagnostics are off or when the array type has no capacity() at all, and treat an unknown capacity as "the elements may have moved" so the parent pointers are refreshed conservatively. std::deque now works as ArrayType, in both builds, and capacity() is no longer named at all in a default build. Since the capacity is now only meaningful for array insertions, it moves out of set_parent() into set_parent_after_array_insert(). basic_json::erase(iterator) assigned the object's erase() return value, which requires the container to return the following iterator. Abseil's hash maps return void to avoid computing a successor the caller may not need. Detect that and compute the successor before erasing; containers that return an iterator, including the vector-backed ordered_map where a precomputed successor would be wrong, keep the existing path. Together these leave an Abseil hash map needing only an alias that restores the template argument order, and no adapter at all for std::deque. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018hxZxz8svM54c6ATEvXp5E Signed-off-by: Niels Lohmann <mail@nlohmann.me>
This commit is contained in:
co-authored by
Claude Opus 5
parent
96806af2dc
commit
5d93f35463
@@ -14,9 +14,9 @@ To store objects in C++, a type is defined by the template parameters explained
|
||||
## Template parameters
|
||||
|
||||
`ArrayType`
|
||||
: container type to store arrays. It must be a vector-like container: the library uses `operator[]`, `at()`,
|
||||
`resize()`, and `capacity()`, and requires random-access iterators. `#!cpp std::deque` and `#!cpp std::list` do
|
||||
not provide `capacity()` and therefore cannot be used as-is -- see
|
||||
: container type to store arrays. It must be a vector-like container: the library uses `operator[]`, `at()`, and
|
||||
`resize()`, and requires random-access iterators. `#!cpp std::vector` and `#!cpp std::deque` qualify;
|
||||
`#!cpp std::list` does not. See
|
||||
[Template Parameter Requirements](../../features/types/template_parameters.md#arraytype) for the full list of
|
||||
requirements.
|
||||
|
||||
@@ -70,3 +70,4 @@ Arrays are stored as pointers in a `basic_json` type. That is, for any access to
|
||||
## Version history
|
||||
|
||||
- Added in version 1.0.0.
|
||||
- Made `capacity()` optional, so that array types such as `#!cpp std::deque` can be used, in version 3.13.0.
|
||||
|
||||
@@ -126,3 +126,4 @@ the object is silently converted as an array of key-value pairs, which is incorr
|
||||
## Version history
|
||||
|
||||
- Added in version 1.0.0.
|
||||
- Allowed object types whose `erase(iterator)` returns `#!cpp void` in version 3.13.0.
|
||||
|
||||
@@ -29,7 +29,7 @@ Requirements are split into two groups:
|
||||
| Template parameter | Default | Notable substitutes |
|
||||
|-------------------------------------------------------------------|-----------------------------------|------------------------------------------------------------------------|
|
||||
| [`ObjectType`](#objecttype) | `std::map` | [`nlohmann::ordered_map`](../../api/ordered_map.md), Abseil hash maps |
|
||||
| [`ArrayType`](#arraytype) | `std::vector` | vector-like containers only |
|
||||
| [`ArrayType`](#arraytype) | `std::vector` | `#!cpp std::deque` |
|
||||
| [`StringType`](#stringtype) | `std::string` | `std::string`-like types over `char` |
|
||||
| [`BooleanType`](#booleantype) | `bool` | none worth using |
|
||||
| [`NumberIntegerType`](#numberintegertype-and-numberunsignedtype) | `std::int64_t` | any signed integer type |
|
||||
@@ -79,7 +79,8 @@ i.e., the template arguments follow the order and meaning of `std::map`.
|
||||
- Constructors: default, copy, move, and from an iterator range `(first, last)`.
|
||||
- Member functions `begin()`, `end()`, `cbegin()`, `cend()`, `empty()`, `size()`, `max_size()`, `clear()`,
|
||||
`find(key)`, `count(key)`, `emplace(key, value)`, `insert(value_type)`, `insert(first, last)`, `operator[](key)`,
|
||||
`erase(iterator)`, `erase(first, last)`, and `erase(key)`.
|
||||
`erase(iterator)`, `erase(first, last)`, and `erase(key)`. `erase(iterator)` may return the following iterator or
|
||||
`#!cpp void`; in the latter case the library computes the successor itself, before erasing.
|
||||
- `emplace` and `insert(value_type)` must return `#!cpp std::pair<iterator, bool>` and must have **unique-key**
|
||||
semantics; multimaps cannot be used.
|
||||
- The type must be swappable (via `std::swap` or an ADL `swap`).
|
||||
@@ -123,33 +124,17 @@ similar containers are integrated; see [Object Order](../object_order.md).
|
||||
#### Abseil hash maps
|
||||
|
||||
`absl::flat_hash_map` and `absl::node_hash_map` tolerate an incomplete value type, but they take a hash function as
|
||||
their third template argument and their `erase(iterator)` returns `#!cpp void` rather than the following iterator. An
|
||||
adapter that fixes both makes them usable:
|
||||
their third template argument. The same adapter as for `#!cpp std::unordered_map` makes them usable:
|
||||
|
||||
```cpp
|
||||
template<template<class, class, class, class, class> class Map>
|
||||
struct absl_object
|
||||
template<class Key, class T, class IgnoredCompare, class Allocator>
|
||||
struct flat_hash_object
|
||||
: absl::flat_hash_map<Key, T, absl::Hash<Key>, std::equal_to<Key>, Allocator>
|
||||
{
|
||||
template<class Key, class T, class IgnoredCompare, class Allocator>
|
||||
struct type : Map<Key, T, absl::Hash<Key>, std::equal_to<Key>, Allocator>
|
||||
{
|
||||
using base_t = Map<Key, T, absl::Hash<Key>, std::equal_to<Key>, Allocator>;
|
||||
using base_t::base_t;
|
||||
using iterator = typename base_t::iterator;
|
||||
using base_t::erase;
|
||||
|
||||
iterator erase(iterator pos)
|
||||
{
|
||||
iterator next = std::next(pos);
|
||||
base_t::erase(pos);
|
||||
return next;
|
||||
}
|
||||
};
|
||||
using base_t = absl::flat_hash_map<Key, T, absl::Hash<Key>, std::equal_to<Key>, Allocator>;
|
||||
using base_t::base_t;
|
||||
};
|
||||
|
||||
template<class Key, class T, class Compare, class Allocator>
|
||||
using flat_hash_object = typename absl_object<absl::flat_hash_map>::template type<Key, T, Compare, Allocator>;
|
||||
|
||||
using flat_hash_json = nlohmann::basic_json<flat_hash_object>;
|
||||
```
|
||||
|
||||
@@ -204,8 +189,7 @@ using array_t = ArrayType<basic_json, AllocatorType<basic_json>>;
|
||||
- Constructors: default, copy, move, from an iterator range `(first, last)`, and from `(count, value)`.
|
||||
- Member functions `begin()`, `end()`, `cbegin()`, `cend()`, `empty()`, `size()`, `max_size()`, `clear()`,
|
||||
`operator[](size_type)`, `at(size_type)`, `back()`, `push_back()`, `emplace_back()`, `pop_back()`, `resize()`,
|
||||
`insert()` (single element, count, range, and initializer list), `erase(pos)`, `erase(first, last)`, and
|
||||
**`capacity()`**.
|
||||
`insert()` (single element, count, range, and initializer list), `erase(pos)`, and `erase(first, last)`.
|
||||
- `iterator` must be default-constructible, and it as well as the type returned by `cbegin()`/`cend()` must satisfy
|
||||
[LegacyRandomAccessIterator](https://en.cppreference.com/w/cpp/named_req/RandomAccessIterator).
|
||||
A `#!cpp static_assert` only checks for
|
||||
@@ -215,23 +199,21 @@ using array_t = ArrayType<basic_json, AllocatorType<basic_json>>;
|
||||
[`basic_json::iterator`](../../api/basic_json/begin.md) require random access.
|
||||
- The type must be swappable and provide the comparison operators `==`, `!=`, `<`, `<=`, `>`, `>=` (or `<=>`).
|
||||
|
||||
!!! note "`capacity()` is required unconditionally"
|
||||
!!! note "`capacity()` is optional"
|
||||
|
||||
[`push_back`](../../api/basic_json/push_back.md), [`emplace_back`](../../api/basic_json/emplace_back.md),
|
||||
[`operator+=`](../../api/basic_json/operator+=.md), and
|
||||
[`operator[]`](../../api/basic_json/operator%5B%5D.md) with an array index read `array_t::capacity()` to
|
||||
detect reallocations, regardless of whether [`JSON_DIAGNOSTICS`](../../api/macros/json_diagnostics.md) is enabled.
|
||||
Consequently `#!cpp std::deque` and `#!cpp std::list` cannot be used as `ArrayType` as-is. A `std::deque` becomes
|
||||
usable when wrapped in a type that adds a `capacity()` member function; `#!cpp std::list` additionally lacks
|
||||
`operator[]` and random-access iterators and cannot be used at all.
|
||||
With [`JSON_DIAGNOSTICS`](../../api/macros/json_diagnostics.md) enabled, the library reads `array_t::capacity()`
|
||||
to find out whether adding an element reallocated the array and moved its elements, which would invalidate the
|
||||
parent pointers. An array type without a `capacity()` member function is handled conservatively: the parent
|
||||
pointers of all elements are refreshed after every insertion, which makes adding *n* elements cost O(*n*²). Only
|
||||
diagnostics builds pay this; without them `capacity()` is never called.
|
||||
|
||||
### Compatible containers
|
||||
|
||||
| Container | Support |
|
||||
|-------------------------------|----------------------------------------------------------------------------------|
|
||||
| `#!cpp std::vector` (default) | full |
|
||||
| `#!cpp std::deque` | only when wrapped in a type that adds a `capacity()` member function |
|
||||
| `#!cpp std::list` | not usable; no `operator[]`, no `capacity()`, and no random-access iterators |
|
||||
| `#!cpp std::deque` | full; keeps references valid while the array grows, but see the note on `capacity()` above |
|
||||
| `#!cpp std::list` | not usable; no `operator[]` and no random-access iterators |
|
||||
| `absl::InlinedVector` | not usable; requires a complete value type |
|
||||
| `absl::FixedArray` | not usable; the size is fixed at construction |
|
||||
|
||||
|
||||
Reference in New Issue
Block a user