171 lines
7.4 KiB
Markdown
171 lines
7.4 KiB
Markdown
# nlohmann::ordered_map
|
|
|
|
```
|
|
template<class Key, class T, class IgnoredLess = std::less<Key>,
|
|
class Allocator = std::allocator<std::pair<const Key, T>>>
|
|
struct ordered_map : std::vector<std::pair<const Key, T>, Allocator>;
|
|
```
|
|
|
|
A minimal map-like container that preserves insertion order for use within [`nlohmann::ordered_json`](https://json.nlohmann.me/api/ordered_json/index.md) (`nlohmann::basic_json<ordered_map>`).
|
|
|
|
## Template parameters
|
|
|
|
`Key` : key type
|
|
|
|
`T` : mapped type
|
|
|
|
`IgnoredLess` : comparison function (ignored and only added to ensure compatibility with `std::map`)
|
|
|
|
`Allocator` : allocator type
|
|
|
|
## Iterator invalidation
|
|
|
|
The type uses a `std::vector` to store object elements. Therefore, adding elements can yield a reallocation in which case all iterators (including the `end()` iterator) and all references to the elements are invalidated.
|
|
|
|
When the storage grows, the keys are copied and the mapped values are moved to the new storage. A plain `std::vector` would copy the whole elements instead, because their `const` keys make them not nothrow move constructible; for [`ordered_json`](https://json.nlohmann.me/api/ordered_json/index.md), this would be a deep copy of every nested value. The values are only copied if `T` is not default constructible or not nothrow move assignable.
|
|
|
|
## Member types
|
|
|
|
- **key_type** - key type (`Key`)
|
|
|
|
- **mapped_type** - mapped type (`T`)
|
|
|
|
- **Container** - base container type (`std::vector<std::pair<const Key, T>, Allocator>`)
|
|
|
|
- **iterator**
|
|
|
|
- **const_iterator**
|
|
|
|
- **size_type**
|
|
|
|
- **value_type**
|
|
|
|
- **key_compare** - key comparison function
|
|
|
|
```
|
|
std::equal_to<Key> // until C++14
|
|
|
|
std::equal_to<> // since C++14
|
|
```
|
|
|
|
## Member functions
|
|
|
|
- (constructor)
|
|
- (destructor)
|
|
- **emplace**
|
|
- **operator[]**
|
|
- **at**
|
|
- **erase**
|
|
- **count**
|
|
- **find**
|
|
- **insert**
|
|
|
|
## Exception safety
|
|
|
|
**emplace**, **operator[]**, and **insert(value)** have the strong exception guarantee: if an exception is thrown (for instance, because copying a key or allocating memory fails), the contents of the container are unchanged.
|
|
|
|
## Complexity
|
|
|
|
Because the elements are stored in a `std::vector` in insertion order, there is no index to look a key up by. Every key-based operation performs a **linear scan** over the stored elements. With `n` denoting the number of elements in the container:
|
|
|
|
| Operation | Complexity | Note |
|
|
| -------------------------------------- | -------------- | -------------------------------------------------------- |
|
|
| **emplace** | O(n) | scans for an existing key, then appends (amortized O(1)) |
|
|
| **operator[]** | O(n) | delegates to **emplace** (non-const) or **at** (const) |
|
|
| **at** | O(n) | throws `std::out_of_range` if the key is not found |
|
|
| **find** | O(n) | |
|
|
| **count** | O(n) | the result is always 0 or 1 |
|
|
| **erase(key)** | O(n) | scan, then move the remaining elements one position down |
|
|
| **erase(pos)**, **erase(first, last)** | O(n) | moves all elements after the erased range |
|
|
| **insert(value)** | O(n) | equivalent to **emplace** |
|
|
| **insert(first, last)** | O((n + m) * m) | for `m` inserted elements |
|
|
|
|
This differs from `std::map`, where the same operations are O(log n).
|
|
|
|
Quadratic cost of building large objects
|
|
|
|
Because every insertion scans all elements inserted so far, building an object of `n` distinct keys costs **O(n²)** in total. This applies to filling an [`ordered_json`](https://json.nlohmann.me/api/ordered_json/index.md) object key by key (with `operator[]`, `emplace`, or `insert`) and, for now, to reading one with a binary reader such as [`from_cbor`](https://json.nlohmann.me/api/basic_json/from_cbor/index.md) ([#6104](https://github.com/nlohmann/json/issues/6104)). Parsing JSON text is not affected: the parser finds the keys of an object with many members in a hash table and takes linear time.
|
|
|
|
The cost is negligible for the object sizes typically found in configuration files or API payloads, but it grows steeply for machine-generated objects with many thousands of keys. Measured with Apple clang 21, `-O2 -DNDEBUG`, arm64, for a flat object of `n` keys (best of seven runs):
|
|
|
|
| `n` | `json::parse` | `ordered_json::parse` | `ordered_json`, key by key | `ordered_json::from_cbor` |
|
|
| ------ | ------------- | --------------------- | -------------------------- | ------------------------- |
|
|
| 2000 | 0.3 ms | 0.1 ms | 4.5 ms | 4.4 ms |
|
|
| 4000 | 0.5 ms | 0.2 ms | 17.1 ms | 16.8 ms |
|
|
| 8000 | 0.9 ms | 0.3 ms | 67.0 ms | 66.8 ms |
|
|
| 16 000 | 2.0 ms | 0.7 ms | 282.4 ms | 282.4 ms |
|
|
|
|
If key order matters for objects of that size, consider a container with a lookup index, such as [`nlohmann::fifo_map`](https://github.com/nlohmann/fifo_map) ([integration](https://github.com/nlohmann/json/issues/485#issuecomment-333652309)), as the object type — see [object order](https://json.nlohmann.me/features/object_order/index.md).
|
|
|
|
## Examples
|
|
|
|
Example
|
|
|
|
The example shows the different behavior of `std::map` and `nlohmann::ordered_map`.
|
|
|
|
```
|
|
#include <iostream>
|
|
#include <nlohmann/json.hpp>
|
|
|
|
// simple output function
|
|
template<typename Map>
|
|
void output(const char* prefix, const Map& m)
|
|
{
|
|
std::cout << prefix << " = { ";
|
|
for (auto& element : m)
|
|
{
|
|
std::cout << element.first << ":" << element.second << ' ';
|
|
}
|
|
std::cout << "}" << '\n';
|
|
}
|
|
|
|
int main()
|
|
{
|
|
// create and fill two maps
|
|
nlohmann::ordered_map<std::string, std::string> m_ordered;
|
|
m_ordered["one"] = "eins";
|
|
m_ordered["two"] = "zwei";
|
|
m_ordered["three"] = "drei";
|
|
|
|
std::map<std::string, std::string> m_std;
|
|
m_std["one"] = "eins";
|
|
m_std["two"] = "zwei";
|
|
m_std["three"] = "drei";
|
|
|
|
// output: m_ordered is ordered by insertion order, m_std is ordered by key
|
|
output("m_ordered", m_ordered);
|
|
output("m_std", m_std);
|
|
|
|
// erase and re-add "one" key
|
|
m_ordered.erase("one");
|
|
m_ordered["one"] = "eins";
|
|
|
|
m_std.erase("one");
|
|
m_std["one"] = "eins";
|
|
|
|
// output: m_ordered shows newly added key at the end; m_std is again ordered by key
|
|
output("m_ordered", m_ordered);
|
|
output("m_std", m_std);
|
|
}
|
|
```
|
|
|
|
Output:
|
|
|
|
```
|
|
m_ordered = { one:eins two:zwei three:drei }
|
|
m_std = { one:eins three:drei two:zwei }
|
|
m_ordered = { two:zwei three:drei one:eins }
|
|
m_std = { one:eins three:drei two:zwei }
|
|
```
|
|
|
|
## See also
|
|
|
|
- [`ordered_json`](https://json.nlohmann.me/api/ordered_json/index.md) - JSON specialization preserving insertion order
|
|
- [Object Order](https://json.nlohmann.me/features/object_order/index.md) - the article on object key order
|
|
|
|
## Version history
|
|
|
|
- Added in version 3.9.0 to implement [`nlohmann::ordered_json`](https://json.nlohmann.me/api/ordered_json/index.md).
|
|
- Added **key_compare** member in version 3.11.0.
|
|
- Changed in version 3.13.0 unreleased: growing the storage moves the mapped values instead of copying them.
|