mirror of
https://github.com/nlohmann/json.git
synced 2026-09-30 03:30:31 +00:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c5d40f05fe | ||
|
|
998456a218 | ||
|
|
4b2d2244c3 |
@@ -28,6 +28,11 @@ A minimal map-like container that preserves insertion order for use within [`nlo
|
||||
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 `#!cpp const` keys make them not nothrow move constructible; for
|
||||
[`ordered_json`](ordered_json.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`)
|
||||
@@ -56,6 +61,11 @@ std::equal_to<> // since C++14
|
||||
- **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
|
||||
@@ -122,3 +132,4 @@ This differs from `#!cpp std::map`, where the same operations are O(log n).
|
||||
|
||||
- Added in version 3.9.0 to implement [`nlohmann::ordered_json`](ordered_json.md).
|
||||
- Added **key_compare** member in version 3.11.0.
|
||||
- Changed in version 3.13.0: growing the storage moves the mapped values instead of copying them.
|
||||
|
||||
@@ -132,14 +132,8 @@ The library uses the following mapping from JSON values types to BJData types ac
|
||||
parsed back as a regular array,
|
||||
- every entry of `"_ArraySize_"` is a positive integer, and their product is representable as a `std::size_t`,
|
||||
- `"_ArrayData_"` is an array holding exactly that many elements, and
|
||||
- every element of `"_ArrayData_"` is a number of the kind named by `"_ArrayType_"`: for the integer types, a
|
||||
value that fits the named width; for `double`, any value; for `single`, a value that survives narrowing to
|
||||
`float` and back without change (for instance, `0.1` does not, since it is not exactly representable as
|
||||
`float`).
|
||||
|
||||
An annotated object is always read back with its keys in the order shown above, `"_ArrayType_"`, `"_ArraySize_"`,
|
||||
`"_ArrayData_"`, regardless of the order the ND-array's header stores them in on the wire. This matters for
|
||||
`ordered_json`, whose comparison takes key order into account.
|
||||
- every element of `"_ArrayData_"` is a number of the kind named by `"_ArrayType_"` (a floating-point number for
|
||||
`single` and `double`, an integer otherwise).
|
||||
|
||||
The current version of this library does not yet support automatic detection of and conversion from a nested JSON
|
||||
array input to a BJData ND-array.
|
||||
|
||||
@@ -2728,15 +2728,10 @@ class binary_reader
|
||||
is_ndarray can only return `true` when its initial value
|
||||
is `false`
|
||||
@param[in] prefix type marker if already read, otherwise set to 0
|
||||
@param[in] ndarray_dtype the element type marker of the enclosing bjdata ndarray if
|
||||
already known (it precedes the dimension vector read here),
|
||||
otherwise 0; used to emit the "_ArrayType_" annotation key
|
||||
before "_ArraySize_" if a dimension vector turns out to
|
||||
describe an ndarray
|
||||
|
||||
@return whether size determination completed
|
||||
*/
|
||||
bool get_ubjson_size_value(std::size_t& result, bool& is_ndarray, char_int_type prefix = 0, char_int_type ndarray_dtype = 0)
|
||||
bool get_ubjson_size_value(std::size_t& result, bool& is_ndarray, char_int_type prefix = 0)
|
||||
{
|
||||
if (prefix == 0)
|
||||
{
|
||||
@@ -2906,37 +2901,8 @@ class binary_reader
|
||||
}
|
||||
}
|
||||
|
||||
if (JSON_HEDLEY_UNLIKELY(!sax->start_object(3)))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// the element type precedes the dimension vector (see get_ubjson_size_type)
|
||||
// and is passed down as ndarray_dtype; emit it here so the annotation keys
|
||||
// follow the documented _ArrayType_, _ArraySize_, _ArrayData_ order
|
||||
if (ndarray_dtype != 0)
|
||||
{
|
||||
auto it = std::lower_bound(bjd_types_map.begin(), bjd_types_map.end(), ndarray_dtype, [](const bjd_type & p, char_int_type t)
|
||||
{
|
||||
return p.first < t;
|
||||
});
|
||||
if (JSON_HEDLEY_UNLIKELY(it == bjd_types_map.end() || it->first != ndarray_dtype))
|
||||
{
|
||||
auto last_token = get_token_string();
|
||||
return sax->parse_error(chars_read, last_token, parse_error::create(112, chars_read,
|
||||
exception_message(input_format, "invalid byte: 0x" + last_token, "type"), nullptr));
|
||||
}
|
||||
|
||||
string_t type_key = "_ArrayType_";
|
||||
string_t type = it->second; // sax->string() takes a reference
|
||||
if (JSON_HEDLEY_UNLIKELY(!sax->key(type_key) || !sax->string(type)))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
string_t key = "_ArraySize_";
|
||||
if (JSON_HEDLEY_UNLIKELY(!sax->key(key) || !sax->start_array(dim.size())))
|
||||
if (JSON_HEDLEY_UNLIKELY(!sax->start_object(3) || !sax->key(key) || !sax->start_array(dim.size())))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
@@ -3037,7 +3003,7 @@ class binary_reader
|
||||
exception_message(input_format, concat("expected '#' after type information; last byte: 0x", last_token), "size"), nullptr));
|
||||
}
|
||||
|
||||
const bool is_error = get_ubjson_size_value(result.first, is_ndarray, 0, result.second);
|
||||
const bool is_error = get_ubjson_size_value(result.first, is_ndarray);
|
||||
// an ndarray was read here only if the flag flipped; when it was
|
||||
// seeded true, get_ubjson_size_value() already rejected the nested
|
||||
// dimension vector
|
||||
@@ -3273,17 +3239,30 @@ class binary_reader
|
||||
if (input_format == input_format_t::bjdata && size_and_type.first != npos && (size_and_type.second & (1 << 8)) != 0)
|
||||
{
|
||||
size_and_type.second &= ~(static_cast<char_int_type>(1) << 8); // use bit 8 to indicate ndarray, here we remove the bit to restore the type marker
|
||||
auto it = std::lower_bound(bjd_types_map.begin(), bjd_types_map.end(), size_and_type.second, [](const bjd_type & p, char_int_type t)
|
||||
{
|
||||
return p.first < t;
|
||||
});
|
||||
string_t key = "_ArrayType_";
|
||||
if (JSON_HEDLEY_UNLIKELY(it == bjd_types_map.end() || it->first != size_and_type.second))
|
||||
{
|
||||
auto last_token = get_token_string();
|
||||
return sax->parse_error(chars_read, last_token, parse_error::create(112, chars_read,
|
||||
exception_message(input_format, "invalid byte: 0x" + last_token, "type"), nullptr));
|
||||
}
|
||||
|
||||
string_t type = it->second; // sax->string() takes a reference
|
||||
if (JSON_HEDLEY_UNLIKELY(!sax->key(key) || !sax->string(type)))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// the "_ArrayType_" and "_ArraySize_" annotation keys were already emitted by
|
||||
// get_ubjson_size_value() (the type marker is known before the dimension vector
|
||||
// that determines size_and_type.first is read, so it is emitted first there to
|
||||
// match the documented _ArrayType_, _ArraySize_, _ArrayData_ key order)
|
||||
if (size_and_type.second == 'C' || size_and_type.second == 'B')
|
||||
{
|
||||
size_and_type.second = 'U';
|
||||
}
|
||||
|
||||
string_t key = "_ArrayData_";
|
||||
key = "_ArrayData_";
|
||||
if (JSON_HEDLEY_UNLIKELY(!sax->key(key) || !sax->start_array(size_and_type.first) ))
|
||||
{
|
||||
return false;
|
||||
|
||||
@@ -1991,21 +1991,9 @@ class binary_writer
|
||||
case 'd':
|
||||
{
|
||||
const auto dval = el.template get<double>();
|
||||
#ifdef __GNUC__
|
||||
JSON_HEDLEY_DIAGNOSTIC_PUSH
|
||||
JSON_HEDLEY_PRAGMA(GCC diagnostic ignored "-Wfloat-equal")
|
||||
#endif
|
||||
// a value that would be rounded (rather than exactly represented) by the
|
||||
// narrowing to float is treated like an out-of-range integer element above;
|
||||
// this is the same criterion write_compact_float() uses for CBOR/MessagePack
|
||||
in_range = std::isnan(dval) ||
|
||||
in_range = !std::isfinite(dval) ||
|
||||
(dval >= static_cast<double>(std::numeric_limits<float>::lowest()) &&
|
||||
dval <= static_cast<double>((std::numeric_limits<float>::max)()) &&
|
||||
static_cast<double>(static_cast<float>(dval)) == dval) ||
|
||||
std::isinf(dval);
|
||||
#ifdef __GNUC__
|
||||
JSON_HEDLEY_DIAGNOSTIC_POP
|
||||
#endif
|
||||
dval <= static_cast<double>((std::numeric_limits<float>::max)()));
|
||||
break;
|
||||
}
|
||||
default:
|
||||
|
||||
@@ -8,13 +8,15 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <algorithm> // max, min
|
||||
#include <functional> // equal_to, less
|
||||
#include <initializer_list> // initializer_list
|
||||
#include <iterator> // input_iterator_tag, iterator_traits
|
||||
#include <memory> // allocator
|
||||
#include <stdexcept> // for out_of_range
|
||||
#include <type_traits> // enable_if, is_convertible
|
||||
#include <utility> // pair
|
||||
#include <tuple> // forward_as_tuple
|
||||
#include <type_traits> // enable_if, integral_constant, is_convertible, is_nothrow_move_constructible
|
||||
#include <utility> // forward, move, pair, piecewise_construct
|
||||
#include <vector> // vector
|
||||
|
||||
#include <nlohmann/detail/macro_scope.hpp>
|
||||
@@ -79,7 +81,7 @@ template <class Key, class T, class IgnoredLess = std::less<Key>,
|
||||
return {it, false};
|
||||
}
|
||||
}
|
||||
Container::emplace_back(key, std::forward<T>(t));
|
||||
append(key, std::forward<T>(t));
|
||||
return {std::prev(this->end()), true};
|
||||
}
|
||||
|
||||
@@ -94,7 +96,7 @@ template <class Key, class T, class IgnoredLess = std::less<Key>,
|
||||
return {it, false};
|
||||
}
|
||||
}
|
||||
Container::emplace_back(std::forward<KeyType>(key), std::forward<T>(t));
|
||||
append(std::forward<KeyType>(key), std::forward<T>(t));
|
||||
return {std::prev(this->end()), true};
|
||||
}
|
||||
|
||||
@@ -368,7 +370,7 @@ template <class Key, class T, class IgnoredLess = std::less<Key>,
|
||||
return {it, false};
|
||||
}
|
||||
}
|
||||
Container::push_back(value);
|
||||
append(value);
|
||||
return {--this->end(), true};
|
||||
}
|
||||
|
||||
@@ -386,6 +388,64 @@ template <class Key, class T, class IgnoredLess = std::less<Key>,
|
||||
}
|
||||
|
||||
private:
|
||||
/*!
|
||||
@brief add an element whose key is not yet contained at the end
|
||||
|
||||
A std::vector copies all elements when it grows, because their const keys
|
||||
make them not nothrow move constructible. For ordered_json, this is a deep
|
||||
copy of every value. Where the strong exception guarantee can be kept, grow
|
||||
the storage here instead, copying only the keys and moving the values.
|
||||
*/
|
||||
template<typename... Args>
|
||||
void append(Args&& ... args)
|
||||
{
|
||||
// evaluated here rather than at class scope, because T is still
|
||||
// incomplete when basic_json instantiates its object_t
|
||||
using move_values = std::integral_constant<bool, detail::conjunction<
|
||||
detail::negation<std::is_nothrow_move_constructible<value_type>>,
|
||||
std::is_copy_constructible<key_type>,
|
||||
detail::is_default_constructible<mapped_type>,
|
||||
std::is_nothrow_move_assignable<mapped_type>>::value>;
|
||||
append_impl(move_values{}, std::forward<Args>(args)...);
|
||||
}
|
||||
|
||||
template<typename... Args>
|
||||
void append_impl(std::true_type /*unused*/, Args&& ... args)
|
||||
{
|
||||
if (this->size() < this->capacity())
|
||||
{
|
||||
Container::emplace_back(std::forward<Args>(args)...);
|
||||
return;
|
||||
}
|
||||
|
||||
// 1. May throw, but only changes tmp: copy the keys, value-initialize
|
||||
// the values, and add the new element. The arguments may refer to
|
||||
// elements of this container, so they are used before any value is
|
||||
// moved out of it.
|
||||
Container tmp(this->get_allocator()); // equal allocators, so swap() is valid
|
||||
tmp.reserve((std::min)(this->max_size(), (std::max)(size_type{1}, 2 * this->size())));
|
||||
for (const auto& element : *this)
|
||||
{
|
||||
tmp.emplace_back(std::piecewise_construct, std::forward_as_tuple(element.first), std::forward_as_tuple());
|
||||
}
|
||||
tmp.emplace_back(std::forward<Args>(args)...);
|
||||
|
||||
// 2. Cannot throw: move the values over and adopt the new storage.
|
||||
auto it = tmp.begin();
|
||||
for (auto& element : *this)
|
||||
{
|
||||
it->second = std::move(element.second);
|
||||
++it;
|
||||
}
|
||||
Container::swap(tmp);
|
||||
}
|
||||
|
||||
template<typename... Args>
|
||||
void append_impl(std::false_type /*unused*/, Args&& ... args)
|
||||
{
|
||||
Container::emplace_back(std::forward<Args>(args)...);
|
||||
}
|
||||
|
||||
JSON_NO_UNIQUE_ADDRESS key_compare m_compare = key_compare();
|
||||
};
|
||||
|
||||
|
||||
@@ -15495,15 +15495,10 @@ class binary_reader
|
||||
is_ndarray can only return `true` when its initial value
|
||||
is `false`
|
||||
@param[in] prefix type marker if already read, otherwise set to 0
|
||||
@param[in] ndarray_dtype the element type marker of the enclosing bjdata ndarray if
|
||||
already known (it precedes the dimension vector read here),
|
||||
otherwise 0; used to emit the "_ArrayType_" annotation key
|
||||
before "_ArraySize_" if a dimension vector turns out to
|
||||
describe an ndarray
|
||||
|
||||
@return whether size determination completed
|
||||
*/
|
||||
bool get_ubjson_size_value(std::size_t& result, bool& is_ndarray, char_int_type prefix = 0, char_int_type ndarray_dtype = 0)
|
||||
bool get_ubjson_size_value(std::size_t& result, bool& is_ndarray, char_int_type prefix = 0)
|
||||
{
|
||||
if (prefix == 0)
|
||||
{
|
||||
@@ -15673,37 +15668,8 @@ class binary_reader
|
||||
}
|
||||
}
|
||||
|
||||
if (JSON_HEDLEY_UNLIKELY(!sax->start_object(3)))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// the element type precedes the dimension vector (see get_ubjson_size_type)
|
||||
// and is passed down as ndarray_dtype; emit it here so the annotation keys
|
||||
// follow the documented _ArrayType_, _ArraySize_, _ArrayData_ order
|
||||
if (ndarray_dtype != 0)
|
||||
{
|
||||
auto it = std::lower_bound(bjd_types_map.begin(), bjd_types_map.end(), ndarray_dtype, [](const bjd_type & p, char_int_type t)
|
||||
{
|
||||
return p.first < t;
|
||||
});
|
||||
if (JSON_HEDLEY_UNLIKELY(it == bjd_types_map.end() || it->first != ndarray_dtype))
|
||||
{
|
||||
auto last_token = get_token_string();
|
||||
return sax->parse_error(chars_read, last_token, parse_error::create(112, chars_read,
|
||||
exception_message(input_format, "invalid byte: 0x" + last_token, "type"), nullptr));
|
||||
}
|
||||
|
||||
string_t type_key = "_ArrayType_";
|
||||
string_t type = it->second; // sax->string() takes a reference
|
||||
if (JSON_HEDLEY_UNLIKELY(!sax->key(type_key) || !sax->string(type)))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
string_t key = "_ArraySize_";
|
||||
if (JSON_HEDLEY_UNLIKELY(!sax->key(key) || !sax->start_array(dim.size())))
|
||||
if (JSON_HEDLEY_UNLIKELY(!sax->start_object(3) || !sax->key(key) || !sax->start_array(dim.size())))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
@@ -15804,7 +15770,7 @@ class binary_reader
|
||||
exception_message(input_format, concat("expected '#' after type information; last byte: 0x", last_token), "size"), nullptr));
|
||||
}
|
||||
|
||||
const bool is_error = get_ubjson_size_value(result.first, is_ndarray, 0, result.second);
|
||||
const bool is_error = get_ubjson_size_value(result.first, is_ndarray);
|
||||
// an ndarray was read here only if the flag flipped; when it was
|
||||
// seeded true, get_ubjson_size_value() already rejected the nested
|
||||
// dimension vector
|
||||
@@ -16040,17 +16006,30 @@ class binary_reader
|
||||
if (input_format == input_format_t::bjdata && size_and_type.first != npos && (size_and_type.second & (1 << 8)) != 0)
|
||||
{
|
||||
size_and_type.second &= ~(static_cast<char_int_type>(1) << 8); // use bit 8 to indicate ndarray, here we remove the bit to restore the type marker
|
||||
auto it = std::lower_bound(bjd_types_map.begin(), bjd_types_map.end(), size_and_type.second, [](const bjd_type & p, char_int_type t)
|
||||
{
|
||||
return p.first < t;
|
||||
});
|
||||
string_t key = "_ArrayType_";
|
||||
if (JSON_HEDLEY_UNLIKELY(it == bjd_types_map.end() || it->first != size_and_type.second))
|
||||
{
|
||||
auto last_token = get_token_string();
|
||||
return sax->parse_error(chars_read, last_token, parse_error::create(112, chars_read,
|
||||
exception_message(input_format, "invalid byte: 0x" + last_token, "type"), nullptr));
|
||||
}
|
||||
|
||||
string_t type = it->second; // sax->string() takes a reference
|
||||
if (JSON_HEDLEY_UNLIKELY(!sax->key(key) || !sax->string(type)))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// the "_ArrayType_" and "_ArraySize_" annotation keys were already emitted by
|
||||
// get_ubjson_size_value() (the type marker is known before the dimension vector
|
||||
// that determines size_and_type.first is read, so it is emitted first there to
|
||||
// match the documented _ArrayType_, _ArraySize_, _ArrayData_ key order)
|
||||
if (size_and_type.second == 'C' || size_and_type.second == 'B')
|
||||
{
|
||||
size_and_type.second = 'U';
|
||||
}
|
||||
|
||||
string_t key = "_ArrayData_";
|
||||
key = "_ArrayData_";
|
||||
if (JSON_HEDLEY_UNLIKELY(!sax->key(key) || !sax->start_array(size_and_type.first) ))
|
||||
{
|
||||
return false;
|
||||
@@ -22342,21 +22321,9 @@ class binary_writer
|
||||
case 'd':
|
||||
{
|
||||
const auto dval = el.template get<double>();
|
||||
#ifdef __GNUC__
|
||||
JSON_HEDLEY_DIAGNOSTIC_PUSH
|
||||
JSON_HEDLEY_PRAGMA(GCC diagnostic ignored "-Wfloat-equal")
|
||||
#endif
|
||||
// a value that would be rounded (rather than exactly represented) by the
|
||||
// narrowing to float is treated like an out-of-range integer element above;
|
||||
// this is the same criterion write_compact_float() uses for CBOR/MessagePack
|
||||
in_range = std::isnan(dval) ||
|
||||
in_range = !std::isfinite(dval) ||
|
||||
(dval >= static_cast<double>(std::numeric_limits<float>::lowest()) &&
|
||||
dval <= static_cast<double>((std::numeric_limits<float>::max)()) &&
|
||||
static_cast<double>(static_cast<float>(dval)) == dval) ||
|
||||
std::isinf(dval);
|
||||
#ifdef __GNUC__
|
||||
JSON_HEDLEY_DIAGNOSTIC_POP
|
||||
#endif
|
||||
dval <= static_cast<double>((std::numeric_limits<float>::max)()));
|
||||
break;
|
||||
}
|
||||
default:
|
||||
@@ -25801,13 +25768,15 @@ NLOHMANN_JSON_NAMESPACE_END
|
||||
|
||||
|
||||
|
||||
#include <algorithm> // max, min
|
||||
#include <functional> // equal_to, less
|
||||
#include <initializer_list> // initializer_list
|
||||
#include <iterator> // input_iterator_tag, iterator_traits
|
||||
#include <memory> // allocator
|
||||
#include <stdexcept> // for out_of_range
|
||||
#include <type_traits> // enable_if, is_convertible
|
||||
#include <utility> // pair
|
||||
#include <tuple> // forward_as_tuple
|
||||
#include <type_traits> // enable_if, integral_constant, is_convertible, is_nothrow_move_constructible
|
||||
#include <utility> // forward, move, pair, piecewise_construct
|
||||
#include <vector> // vector
|
||||
|
||||
// #include <nlohmann/detail/macro_scope.hpp>
|
||||
@@ -25874,7 +25843,7 @@ template <class Key, class T, class IgnoredLess = std::less<Key>,
|
||||
return {it, false};
|
||||
}
|
||||
}
|
||||
Container::emplace_back(key, std::forward<T>(t));
|
||||
append(key, std::forward<T>(t));
|
||||
return {std::prev(this->end()), true};
|
||||
}
|
||||
|
||||
@@ -25889,7 +25858,7 @@ template <class Key, class T, class IgnoredLess = std::less<Key>,
|
||||
return {it, false};
|
||||
}
|
||||
}
|
||||
Container::emplace_back(std::forward<KeyType>(key), std::forward<T>(t));
|
||||
append(std::forward<KeyType>(key), std::forward<T>(t));
|
||||
return {std::prev(this->end()), true};
|
||||
}
|
||||
|
||||
@@ -26163,7 +26132,7 @@ template <class Key, class T, class IgnoredLess = std::less<Key>,
|
||||
return {it, false};
|
||||
}
|
||||
}
|
||||
Container::push_back(value);
|
||||
append(value);
|
||||
return {--this->end(), true};
|
||||
}
|
||||
|
||||
@@ -26181,6 +26150,64 @@ template <class Key, class T, class IgnoredLess = std::less<Key>,
|
||||
}
|
||||
|
||||
private:
|
||||
/*!
|
||||
@brief add an element whose key is not yet contained at the end
|
||||
|
||||
A std::vector copies all elements when it grows, because their const keys
|
||||
make them not nothrow move constructible. For ordered_json, this is a deep
|
||||
copy of every value. Where the strong exception guarantee can be kept, grow
|
||||
the storage here instead, copying only the keys and moving the values.
|
||||
*/
|
||||
template<typename... Args>
|
||||
void append(Args&& ... args)
|
||||
{
|
||||
// evaluated here rather than at class scope, because T is still
|
||||
// incomplete when basic_json instantiates its object_t
|
||||
using move_values = std::integral_constant<bool, detail::conjunction<
|
||||
detail::negation<std::is_nothrow_move_constructible<value_type>>,
|
||||
std::is_copy_constructible<key_type>,
|
||||
detail::is_default_constructible<mapped_type>,
|
||||
std::is_nothrow_move_assignable<mapped_type>>::value>;
|
||||
append_impl(move_values{}, std::forward<Args>(args)...);
|
||||
}
|
||||
|
||||
template<typename... Args>
|
||||
void append_impl(std::true_type /*unused*/, Args&& ... args)
|
||||
{
|
||||
if (this->size() < this->capacity())
|
||||
{
|
||||
Container::emplace_back(std::forward<Args>(args)...);
|
||||
return;
|
||||
}
|
||||
|
||||
// 1. May throw, but only changes tmp: copy the keys, value-initialize
|
||||
// the values, and add the new element. The arguments may refer to
|
||||
// elements of this container, so they are used before any value is
|
||||
// moved out of it.
|
||||
Container tmp(this->get_allocator()); // equal allocators, so swap() is valid
|
||||
tmp.reserve((std::min)(this->max_size(), (std::max)(size_type{1}, 2 * this->size())));
|
||||
for (const auto& element : *this)
|
||||
{
|
||||
tmp.emplace_back(std::piecewise_construct, std::forward_as_tuple(element.first), std::forward_as_tuple());
|
||||
}
|
||||
tmp.emplace_back(std::forward<Args>(args)...);
|
||||
|
||||
// 2. Cannot throw: move the values over and adopt the new storage.
|
||||
auto it = tmp.begin();
|
||||
for (auto& element : *this)
|
||||
{
|
||||
it->second = std::move(element.second);
|
||||
++it;
|
||||
}
|
||||
Container::swap(tmp);
|
||||
}
|
||||
|
||||
template<typename... Args>
|
||||
void append_impl(std::false_type /*unused*/, Args&& ... args)
|
||||
{
|
||||
Container::emplace_back(std::forward<Args>(args)...);
|
||||
}
|
||||
|
||||
JSON_NO_UNIQUE_ADDRESS key_compare m_compare = key_compare();
|
||||
};
|
||||
|
||||
|
||||
@@ -119,6 +119,39 @@ BENCHMARK_CAPTURE(ParseIndented, canada / 4, TEST_DATA_DIRECTORY "/nativej
|
||||
BENCHMARK_CAPTURE(ParseIndented, citm_catalog / 4, TEST_DATA_DIRECTORY "/nativejson-benchmark/citm_catalog.json", 4);
|
||||
BENCHMARK_CAPTURE(ParseIndented, twitter / 4, TEST_DATA_DIRECTORY "/nativejson-benchmark/twitter.json", 4);
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////////
|
||||
// parse JSON from string into an ordered_json
|
||||
//
|
||||
// Same as ParseString above, but with nlohmann::ordered_json, whose objects
|
||||
// keep their members in a vector: the pair of rows shows what preserving the
|
||||
// insertion order costs.
|
||||
//////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
static void ParseStringOrdered(benchmark::State& state, const char* filename)
|
||||
{
|
||||
std::ifstream f(filename);
|
||||
std::string str((std::istreambuf_iterator<char>(f)), std::istreambuf_iterator<char>());
|
||||
|
||||
while (state.KeepRunning())
|
||||
{
|
||||
state.PauseTiming();
|
||||
auto* j = new nlohmann::ordered_json();
|
||||
state.ResumeTiming();
|
||||
|
||||
*j = nlohmann::ordered_json::parse(str);
|
||||
|
||||
state.PauseTiming();
|
||||
delete j;
|
||||
state.ResumeTiming();
|
||||
}
|
||||
|
||||
state.SetBytesProcessed(state.iterations() * str.size());
|
||||
}
|
||||
BENCHMARK_CAPTURE(ParseStringOrdered, jeopardy, TEST_DATA_DIRECTORY "/jeopardy/jeopardy.json");
|
||||
BENCHMARK_CAPTURE(ParseStringOrdered, canada, TEST_DATA_DIRECTORY "/nativejson-benchmark/canada.json");
|
||||
BENCHMARK_CAPTURE(ParseStringOrdered, citm_catalog, TEST_DATA_DIRECTORY "/nativejson-benchmark/citm_catalog.json");
|
||||
BENCHMARK_CAPTURE(ParseStringOrdered, twitter, TEST_DATA_DIRECTORY "/nativejson-benchmark/twitter.json");
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////////
|
||||
// serialize JSON
|
||||
//////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
@@ -11,7 +11,6 @@
|
||||
#define JSON_TESTS_PRIVATE
|
||||
#include <nlohmann/json.hpp>
|
||||
using nlohmann::json;
|
||||
using ordered_json = nlohmann::ordered_json;
|
||||
|
||||
#include <algorithm>
|
||||
#include <climits>
|
||||
@@ -2295,33 +2294,29 @@ TEST_CASE("BJData")
|
||||
|
||||
SECTION("start_array() in ndarray _ArraySize_")
|
||||
{
|
||||
// _ArrayType_ (2 events: key + string) is now emitted before
|
||||
// _ArraySize_ (see GitHub issue #5661), which shifts the events
|
||||
// below later by the same 2 events
|
||||
std::vector<uint8_t> const v = {'[', '$', 'i', '#', '[', '$', 'i', '#', 'i', 2, 2, 1, 1, 2};
|
||||
SaxCountdown scp(4);
|
||||
SaxCountdown scp(2);
|
||||
CHECK_FALSE(json::sax_parse(v, &scp, json::input_format_t::bjdata));
|
||||
}
|
||||
|
||||
SECTION("number_integer() in ndarray _ArraySize_")
|
||||
{
|
||||
std::vector<uint8_t> const v = {'[', '$', 'U', '#', '[', '$', 'i', '#', 'i', 2, 2, 1, 1, 2};
|
||||
SaxCountdown scp(5);
|
||||
SaxCountdown scp(3);
|
||||
CHECK_FALSE(json::sax_parse(v, &scp, json::input_format_t::bjdata));
|
||||
}
|
||||
|
||||
SECTION("key() in ndarray _ArrayType_")
|
||||
{
|
||||
// _ArrayType_ is emitted right after start_object(), before _ArraySize_
|
||||
std::vector<uint8_t> const v = {'[', '$', 'U', '#', '[', '$', 'U', '#', 'i', 2, 2, 2, 1, 2, 3, 4};
|
||||
SaxCountdown scp(1);
|
||||
SaxCountdown scp(6);
|
||||
CHECK_FALSE(json::sax_parse(v, &scp, json::input_format_t::bjdata));
|
||||
}
|
||||
|
||||
SECTION("string() in ndarray _ArrayType_")
|
||||
{
|
||||
std::vector<uint8_t> const v = {'[', '$', 'U', '#', '[', '$', 'U', '#', 'i', 2, 2, 2, 1, 2, 3, 4};
|
||||
SaxCountdown scp(2);
|
||||
SaxCountdown scp(7);
|
||||
CHECK_FALSE(json::sax_parse(v, &scp, json::input_format_t::bjdata));
|
||||
}
|
||||
|
||||
@@ -2924,22 +2919,6 @@ TEST_CASE("BJData")
|
||||
CHECK(out_single.at(0) == '{');
|
||||
CHECK(json::from_bjdata(out_single) == j_single);
|
||||
|
||||
// a double element that is finite and within the range of "single"
|
||||
// but is not exactly representable as a float, so narrowing it would
|
||||
// silently round it (0.1 is read back as 0.10000000149011612); this,
|
||||
// like the overflow case above, falls back to a plain object (see
|
||||
// GitHub issue #5661)
|
||||
json const j_single_rounded = json({{"_ArrayType_", "single"}, {"_ArraySize_", {2, 1}}, {"_ArrayData_", {1.5, 0.1}}});
|
||||
const auto out_single_rounded = json::to_bjdata(j_single_rounded);
|
||||
CHECK(out_single_rounded.at(0) == '{');
|
||||
CHECK(json::from_bjdata(out_single_rounded) == j_single_rounded);
|
||||
|
||||
// a double element that underflows to 0 when narrowed to "single"
|
||||
json const j_single_underflow = json({{"_ArrayType_", "single"}, {"_ArraySize_", {2, 1}}, {"_ArrayData_", {1.5, 1e-300}}});
|
||||
const auto out_single_underflow = json::to_bjdata(j_single_underflow);
|
||||
CHECK(out_single_underflow.at(0) == '{');
|
||||
CHECK(json::from_bjdata(out_single_underflow) == j_single_underflow);
|
||||
|
||||
// in-range boundary values still use the compact ndarray encoding
|
||||
json const j_uint8_ok = json({{"_ArrayType_", "uint8"}, {"_ArraySize_", {2, 1}}, {"_ArrayData_", {0, 255}}});
|
||||
CHECK(json::to_bjdata(j_uint8_ok) == std::vector<uint8_t>({'[', '$', 'U', '#', '[', 'i', 2, 'i', 1, ']', 0, 255}));
|
||||
@@ -2953,23 +2932,6 @@ TEST_CASE("BJData")
|
||||
CHECK(json::from_bjdata(out_single_ok) == json({{"_ArrayType_", "single"}, {"_ArraySize_", {2, 1}}, {"_ArrayData_", {1.5f, -1.5f}}}));
|
||||
}
|
||||
|
||||
SECTION("ndarray annotation keys are read back in the documented order")
|
||||
{
|
||||
// from_bjdata() must emit the annotation object's keys in the order
|
||||
// used throughout the documentation, _ArrayType_, _ArraySize_,
|
||||
// _ArrayData_: the type marker precedes the dimension vector on the
|
||||
// wire (see get_ubjson_size_type()), so it is known, and emitted,
|
||||
// before _ArraySize_. For a plain json this key order is invisible
|
||||
// (its comparison ignores it), but for an ordered_json it is not (see
|
||||
// GitHub issue #5661).
|
||||
const ordered_json o = ordered_json::parse(R"({"_ArrayType_":"uint8","_ArraySize_":[2,2],"_ArrayData_":[1,2,3,4]})");
|
||||
const auto packed = ordered_json::to_bjdata(o);
|
||||
CHECK(packed.at(0) == '[');
|
||||
const ordered_json o_back = ordered_json::from_bjdata(packed);
|
||||
CHECK(o_back == o);
|
||||
CHECK(o_back.dump() == o.dump());
|
||||
}
|
||||
|
||||
SECTION("ndarray that would not be read back as an annotated object stays as object")
|
||||
{
|
||||
// the reader only restores an annotated object from an ND-array
|
||||
|
||||
@@ -46,6 +46,24 @@ TEST_CASE("Tests with disabled exceptions")
|
||||
CHECK(*sax_no_exception::error_string == "[json.exception.parse_error.101] parse error at line 1, column 1: syntax error while parsing value - invalid literal; last read: 'x'");
|
||||
delete sax_no_exception::error_string; // NOLINT(cppcoreguidelines-owning-memory)
|
||||
}
|
||||
|
||||
SECTION("growing an ordered_json object")
|
||||
{
|
||||
auto j = nlohmann::ordered_json::object();
|
||||
for (int i = 0; i < 100; ++i)
|
||||
{
|
||||
j[std::to_string(i)] = {{"nested", i}};
|
||||
}
|
||||
|
||||
CHECK(j.size() == 100);
|
||||
int i = 0;
|
||||
for (const auto& element : j.items())
|
||||
{
|
||||
CHECK(element.key() == std::to_string(i));
|
||||
CHECK(element.value()["nested"] == i);
|
||||
++i;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
DOCTEST_GCC_SUPPRESS_WARNING_POP
|
||||
|
||||
@@ -11,6 +11,97 @@
|
||||
#include <nlohmann/json.hpp>
|
||||
using nlohmann::ordered_map;
|
||||
|
||||
#include <stdexcept>
|
||||
#include <string>
|
||||
#include <type_traits>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
// The EDG front end (Intel icpc, NVIDIA nvc++) considers the defaulted move
|
||||
// constructor of std::pair<const Key, T> noexcept even if copying Key can
|
||||
// throw. std::vector then moves such elements itself when it grows (and calls
|
||||
// std::terminate if a key copy throws), so ordered_map leaves growing to it.
|
||||
#if defined(__EDG__)
|
||||
#define JSON_TEST_PAIR_MOVE_IS_NOEXCEPT
|
||||
#endif
|
||||
|
||||
namespace
|
||||
{
|
||||
// number of copies made of counted values
|
||||
int value_copies = 0;
|
||||
|
||||
// a mapped type that counts its copies; moving from it leaves -1 behind
|
||||
struct counted // NOLINT(cppcoreguidelines-special-member-functions,hicpp-special-member-functions)
|
||||
{
|
||||
int payload = 0;
|
||||
|
||||
counted() = default;
|
||||
explicit counted(int p) noexcept : payload(p) {}
|
||||
counted(const counted& other) : payload(other.payload)
|
||||
{
|
||||
++value_copies;
|
||||
}
|
||||
counted(counted&& other) noexcept : payload(other.payload)
|
||||
{
|
||||
other.payload = -1;
|
||||
}
|
||||
counted& operator=(const counted&) = delete;
|
||||
counted& operator=(counted&& other) noexcept
|
||||
{
|
||||
payload = other.payload;
|
||||
other.payload = -1;
|
||||
return *this;
|
||||
}
|
||||
};
|
||||
|
||||
#if !defined(JSON_NOEXCEPTION) && !defined(JSON_TEST_PAIR_MOVE_IS_NOEXCEPT)
|
||||
// number of throwing_key copies that still succeed; the next one throws
|
||||
// (a negative value means that copies never throw)
|
||||
int key_copies_until_throw = -1;
|
||||
|
||||
// a key type whose copy constructor can be made to throw
|
||||
struct throwing_key // NOLINT(cppcoreguidelines-special-member-functions,hicpp-special-member-functions)
|
||||
{
|
||||
int id = 0;
|
||||
|
||||
explicit throwing_key(int i) noexcept : id(i) {}
|
||||
throwing_key(const throwing_key& other) : id(other.id)
|
||||
{
|
||||
if (key_copies_until_throw == 0)
|
||||
{
|
||||
throw std::runtime_error("key copy failed");
|
||||
}
|
||||
if (key_copies_until_throw > 0)
|
||||
{
|
||||
--key_copies_until_throw;
|
||||
}
|
||||
}
|
||||
throwing_key& operator=(const throwing_key&) = delete;
|
||||
|
||||
friend bool operator==(const throwing_key& lhs, const throwing_key& rhs) noexcept
|
||||
{
|
||||
return lhs.id == rhs.id;
|
||||
}
|
||||
};
|
||||
#endif
|
||||
|
||||
// a mapped type that cannot be default-constructed
|
||||
struct no_default
|
||||
{
|
||||
explicit no_default(int v) noexcept : value(v) {}
|
||||
int value;
|
||||
};
|
||||
|
||||
// ordered_json must keep moving its values when an object grows
|
||||
using ordered_object_t = nlohmann::ordered_json::object_t;
|
||||
#if !defined(JSON_TEST_PAIR_MOVE_IS_NOEXCEPT)
|
||||
static_assert(!std::is_nothrow_move_constructible<ordered_object_t::value_type>::value, "std::vector would move the elements itself");
|
||||
#endif
|
||||
static_assert(std::is_copy_constructible<ordered_object_t::key_type>::value, "keys must be copyable");
|
||||
static_assert(std::is_default_constructible<ordered_object_t::mapped_type>::value, "values must be default-constructible");
|
||||
static_assert(std::is_nothrow_move_assignable<ordered_object_t::mapped_type>::value, "values must be nothrow move-assignable");
|
||||
} // namespace
|
||||
|
||||
TEST_CASE("ordered_map")
|
||||
{
|
||||
SECTION("constructor")
|
||||
@@ -313,3 +404,270 @@ TEST_CASE("ordered_map")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("ordered_map growth")
|
||||
{
|
||||
SECTION("values are moved, not copied, when the storage grows")
|
||||
{
|
||||
ordered_map<std::string, counted> om;
|
||||
std::size_t growths = 0;
|
||||
value_copies = 0;
|
||||
|
||||
// inserts 100 elements with the given function and counts the growths
|
||||
const auto fill = [&om, &growths](void (*insert)(ordered_map<std::string, counted>&, int))
|
||||
{
|
||||
for (int i = 0; i < 100; ++i)
|
||||
{
|
||||
const auto old_capacity = om.capacity();
|
||||
insert(om, i);
|
||||
if (om.capacity() > old_capacity)
|
||||
{
|
||||
++growths;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// checks that the elements are in insertion order with their values
|
||||
const auto check_contents = [&om]
|
||||
{
|
||||
CHECK(om.size() == 100);
|
||||
int i = 0;
|
||||
for (const auto& element : om)
|
||||
{
|
||||
CHECK(element.first == std::to_string(i));
|
||||
CHECK(element.second.payload == i);
|
||||
++i;
|
||||
}
|
||||
};
|
||||
|
||||
SECTION("emplace")
|
||||
{
|
||||
fill([](ordered_map<std::string, counted>& m, int i)
|
||||
{
|
||||
m.emplace(std::to_string(i), counted(i));
|
||||
});
|
||||
CHECK(growths >= 3);
|
||||
CHECK(value_copies == 0);
|
||||
check_contents();
|
||||
}
|
||||
|
||||
SECTION("operator[]")
|
||||
{
|
||||
fill([](ordered_map<std::string, counted>& m, int i)
|
||||
{
|
||||
m[std::to_string(i)] = counted(i);
|
||||
});
|
||||
CHECK(growths >= 3);
|
||||
CHECK(value_copies == 0);
|
||||
check_contents();
|
||||
}
|
||||
|
||||
SECTION("insert(value_type&&)")
|
||||
{
|
||||
fill([](ordered_map<std::string, counted>& m, int i)
|
||||
{
|
||||
m.insert({std::to_string(i), counted(i)});
|
||||
});
|
||||
CHECK(growths >= 3);
|
||||
CHECK(value_copies == 0);
|
||||
check_contents();
|
||||
}
|
||||
|
||||
SECTION("insert(const value_type&)")
|
||||
{
|
||||
fill([](ordered_map<std::string, counted>& m, int i)
|
||||
{
|
||||
const std::pair<const std::string, counted> value(std::to_string(i), counted(i));
|
||||
m.insert(value);
|
||||
});
|
||||
CHECK(growths >= 3);
|
||||
// only the inserted values are copied
|
||||
CHECK(value_copies == 100);
|
||||
check_contents();
|
||||
}
|
||||
|
||||
SECTION("insert(first, last)")
|
||||
{
|
||||
std::vector<std::pair<const std::string, counted>> values;
|
||||
values.reserve(100);
|
||||
for (int i = 0; i < 100; ++i)
|
||||
{
|
||||
values.emplace_back(std::to_string(i), counted(i));
|
||||
}
|
||||
value_copies = 0;
|
||||
|
||||
om.insert(values.cbegin(), values.cend());
|
||||
// only the inserted values are copied
|
||||
CHECK(value_copies == 100);
|
||||
check_contents();
|
||||
}
|
||||
}
|
||||
|
||||
SECTION("elements keep their order and values over many growths")
|
||||
{
|
||||
ordered_map<std::string, counted> om;
|
||||
for (int i = 0; i < 1000; ++i)
|
||||
{
|
||||
om.emplace(std::to_string(i), counted(i));
|
||||
}
|
||||
|
||||
CHECK(om.size() == 1000);
|
||||
int i = 0;
|
||||
for (const auto& element : om)
|
||||
{
|
||||
CHECK(element.first == std::to_string(i));
|
||||
CHECK(element.second.payload == i);
|
||||
++i;
|
||||
}
|
||||
}
|
||||
|
||||
SECTION("arguments may refer to elements of the full container")
|
||||
{
|
||||
SECTION("moving a value out of the container")
|
||||
{
|
||||
ordered_map<std::string, counted> om;
|
||||
om.reserve(4);
|
||||
while (om.size() < om.capacity())
|
||||
{
|
||||
const auto i = static_cast<int>(om.size());
|
||||
om.emplace(std::to_string(i), counted(i));
|
||||
}
|
||||
const auto size = om.size();
|
||||
|
||||
om.emplace("new", std::move(om.at("0")));
|
||||
CHECK(om.size() == size + 1);
|
||||
CHECK(om.at("new").payload == 0);
|
||||
CHECK(om.at("0").payload == -1);
|
||||
}
|
||||
|
||||
SECTION("using a value as key")
|
||||
{
|
||||
ordered_map<std::string, std::string> om;
|
||||
om.reserve(4);
|
||||
while (om.size() < om.capacity())
|
||||
{
|
||||
const auto i = std::to_string(om.size());
|
||||
om.emplace("k" + i, "v" + i);
|
||||
}
|
||||
const auto size = om.size();
|
||||
|
||||
om.emplace(om.at("k0"), std::string("x"));
|
||||
CHECK(om.size() == size + 1);
|
||||
CHECK(om.at("k0") == "v0");
|
||||
CHECK(om.at("v0") == "x");
|
||||
}
|
||||
|
||||
SECTION("ordered_json")
|
||||
{
|
||||
auto j = nlohmann::ordered_json::object();
|
||||
auto& object = j.get_ref<nlohmann::ordered_json::object_t&>();
|
||||
object.reserve(4);
|
||||
while (object.size() < object.capacity())
|
||||
{
|
||||
const auto i = std::to_string(object.size());
|
||||
j[i] = "a value that is too long for the small string optimization " + i;
|
||||
}
|
||||
const auto size = j.size();
|
||||
|
||||
j.emplace("new", std::move(j["0"]));
|
||||
CHECK(j.size() == size + 1);
|
||||
CHECK(j["new"] == "a value that is too long for the small string optimization 0");
|
||||
CHECK(j["0"].is_null());
|
||||
}
|
||||
}
|
||||
|
||||
#if !defined(JSON_NOEXCEPTION) && !defined(JSON_TEST_PAIR_MOVE_IS_NOEXCEPT)
|
||||
SECTION("the container is unchanged if growing it throws")
|
||||
{
|
||||
ordered_map<throwing_key, counted> om;
|
||||
om.reserve(4);
|
||||
while (om.size() < om.capacity())
|
||||
{
|
||||
const auto i = static_cast<int>(om.size());
|
||||
om.emplace(throwing_key(i), counted(i));
|
||||
}
|
||||
const auto size = om.size();
|
||||
const auto capacity = om.capacity();
|
||||
|
||||
// checks that the elements are unchanged
|
||||
const auto check_unchanged = [&om, size, capacity]
|
||||
{
|
||||
CHECK(om.size() == size);
|
||||
CHECK(om.capacity() == capacity);
|
||||
int i = 0;
|
||||
for (const auto& element : om)
|
||||
{
|
||||
CHECK(element.first.id == i);
|
||||
CHECK(element.second.payload == i);
|
||||
++i;
|
||||
}
|
||||
};
|
||||
|
||||
SECTION("emplace")
|
||||
{
|
||||
// growing copies the existing keys and then the new one; let each of these copies throw
|
||||
for (std::size_t k = 0; k <= size; ++k)
|
||||
{
|
||||
counted value(100);
|
||||
key_copies_until_throw = static_cast<int>(k);
|
||||
CHECK_THROWS_AS(om.emplace(throwing_key(100), std::move(value)), std::runtime_error);
|
||||
key_copies_until_throw = -1;
|
||||
|
||||
check_unchanged();
|
||||
CHECK(value.payload == 100); // NOLINT(bugprone-use-after-move,hicpp-invalid-access-moved)
|
||||
}
|
||||
|
||||
om.emplace(throwing_key(100), counted(100));
|
||||
CHECK(om.size() == size + 1);
|
||||
CHECK(om.capacity() > capacity);
|
||||
CHECK(om.at(throwing_key(100)).payload == 100);
|
||||
}
|
||||
|
||||
SECTION("insert(const value_type&)")
|
||||
{
|
||||
const std::pair<const throwing_key, counted> value(throwing_key(100), counted(100));
|
||||
value_copies = 0;
|
||||
|
||||
key_copies_until_throw = static_cast<int>(size / 2);
|
||||
CHECK_THROWS_AS(om.insert(value), std::runtime_error);
|
||||
key_copies_until_throw = -1;
|
||||
|
||||
check_unchanged();
|
||||
CHECK(value_copies == 0);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
SECTION("elements that std::vector moves, or that cannot be moved back")
|
||||
{
|
||||
SECTION("nothrow move-constructible elements")
|
||||
{
|
||||
ordered_map<int, counted> om;
|
||||
value_copies = 0;
|
||||
for (int i = 0; i < 100; ++i)
|
||||
{
|
||||
om.emplace(i, counted(i));
|
||||
}
|
||||
CHECK(om.size() == 100);
|
||||
CHECK(value_copies == 0);
|
||||
}
|
||||
|
||||
SECTION("mapped type without default constructor")
|
||||
{
|
||||
ordered_map<std::string, no_default> om;
|
||||
for (int i = 0; i < 100; ++i)
|
||||
{
|
||||
om.emplace(std::to_string(i), no_default(i));
|
||||
}
|
||||
|
||||
CHECK(om.size() == 100);
|
||||
int i = 0;
|
||||
for (const auto& element : om)
|
||||
{
|
||||
CHECK(element.first == std::to_string(i));
|
||||
CHECK(element.second.value == i);
|
||||
++i;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user