mirror of
https://github.com/nlohmann/json.git
synced 2026-10-05 06:00:29 +00:00
* Keep the serializer conversion for objects whose keys cannot be converted #5591 added a test converting nlohmann::json into a basic_json whose string type cannot be constructed from std::string. That instantiates convert_iteratively(), whose members.emplace_back(next.key(), ...) needs exactly that key conversion, and broke the build of unit-alt-string. Dispatch on the key's constructibility and leave such conversions to the serializers, as the levels above the nesting bound already do (#3425). Signed-off-by: Niels Lohmann <mail@nlohmann.me> * Fix the remaining CI failures on develop - unit-wstring: with a 16-bit wchar_t (Windows), a lone surrogate is reported as the ill-formed byte 0xFF since #5704; the std::wstring expectations still had the previous <U+0000>. - ci_single_binaries: json_literals.hpp (#5610) and json.hpp include each other on purpose, and IWYU, not following the cycle, asks to replace json.hpp with json_fwd.hpp. Report its findings without failing the build, as already done for json.hpp. Signed-off-by: Niels Lohmann <mail@nlohmann.me> * Fix the library warnings and noexcept specifications from the merged PRs - binary_reader: rename the error_handler constructor parameter, which shadowed the member (-Wshadow, -Wshadow-field-in-constructor; #5746) - basic_json(copy_construct_tag, ...): declare it noexcept when copying the base class is (GCC 16 -Wnoexcept; #5690) - the scalar-on-left legacy comparison operators: noexcept only when converting the scalar is, like their member counterparts (#5682, #5751) - compare_leaves: use std::is_eq/is_lt/is_gt instead of comparing a std::partial_ordering with 0 (-Wzero-as-null-pointer-constant; #5686) - serializer: silence MSVC C4127 for the EnsureAscii template parameter (#5741, #5746) - clang-tidy: return the sanitized reference in binary_writer, take the key of ordered_map::find_impl by const reference (#5727), and mark the switches over parse_array_index (#5728) - ordered_map: keep <memory> for std::allocator (IWYU) Signed-off-by: Niels Lohmann <mail@nlohmann.me> * Split unit-conversions.cpp so MinGW can link it clang 18 with the MinGW linker failed to link test-conversions_cpp17 ("relocation truncated to fit: IMAGE_REL_AMD64_REL32"). As windows.yml recommends, keep the objects small by splitting the test file. Signed-off-by: Niels Lohmann <mail@nlohmann.me> * Fix the tests added by the merged PRs for all CI configurations - discard the results of dump() and from_*() in CHECK_THROWS with utils::ignore_return_value (GCC -Werror=unused-result) - give unit-bson's huge_string_t a default constructor (MSVC C2512, GCC 5, clang 3.5) - unit-disabled_exceptions: use the literals namespace when the global UDLs are off (ci_test_noglobaludls; #5700) - unit-binary_utf8_strict: expect the JSON pointer prefix with JSON_DIAGNOSTICS (#5741) - skip the tests that rely on exceptions under JSON_NOEXCEPTION (#5678, #5732) - clang-tidy and clang -Werror: static test data, CAPTURE(...);, const-correctness, use-after-move alias, unused conversion operator, a missing <iterator> include Signed-off-by: Niels Lohmann <mail@nlohmann.me> * Title the macro examples and add JSON_STRICT_BINARY_UTF8 to the docset The documentation style check requires "Example: ..." titles on pages with several examples (#5741, #5591) and a docset entry for every macro page. Signed-off-by: Niels Lohmann <mail@nlohmann.me> * Regenerate BUILD.bazel and nlohmann_json.natvis Signed-off-by: Niels Lohmann <mail@nlohmann.me> #5746 added detail/output/error_handler.hpp and #5741 the json_abi_sbu8 ABI tag. * Install libidn11 for the CMake 3.5.0 binary in ci_cmake_flags Signed-off-by: Niels Lohmann <mail@nlohmann.me> #5733 moved ci_cmake_options from ubuntu:focal to ubuntu:24.04, which no longer ships libidn.so.11; the CMake 3.5.0 release binary links against it, so every ci_cmake_flags run has failed since. Install focal's libidn11 package for that matrix entry only. * Suppress Infer's false STACK_VARIABLE_ADDRESS_ESCAPE in get_impl get_impl() returns its local by value. A test added by the merged PRs instantiates it with a type Infer misreads, so ci_infer reported the 2021 code for the first time. Signed-off-by: Niels Lohmann <mail@nlohmann.me> --------- Signed-off-by: Niels Lohmann <mail@nlohmann.me>
747 lines
24 KiB
C++
747 lines
24 KiB
C++
// __ _____ _____ _____
|
|
// __| | __| | | | JSON for Modern C++ (supporting code)
|
|
// | | |__ | | | | | | version 3.12.0
|
|
// |_____|_____|_____|_|___| https://github.com/nlohmann/json
|
|
//
|
|
// SPDX-FileCopyrightText: 2013-2026 Niels Lohmann <https://nlohmann.me>
|
|
// SPDX-License-Identifier: MIT
|
|
|
|
#include "doctest_compatibility.h"
|
|
|
|
#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")
|
|
{
|
|
SECTION("constructor from iterator range")
|
|
{
|
|
std::map<std::string, std::string> m {{"eins", "one"}, {"zwei", "two"}, {"drei", "three"}};
|
|
ordered_map<std::string, std::string> const om(m.begin(), m.end());
|
|
CHECK(om.size() == 3);
|
|
}
|
|
|
|
SECTION("copy assignment")
|
|
{
|
|
std::map<std::string, std::string> m {{"eins", "one"}, {"zwei", "two"}, {"drei", "three"}};
|
|
ordered_map<std::string, std::string> om(m.begin(), m.end());
|
|
const auto com = om;
|
|
om.clear(); // silence a warning by forbidding having "const auto& com = om;"
|
|
CHECK(com.size() == 3);
|
|
}
|
|
}
|
|
|
|
SECTION("at")
|
|
{
|
|
std::map<std::string, std::string> m {{"eins", "one"}, {"zwei", "two"}, {"drei", "three"}};
|
|
ordered_map<std::string, std::string> om(m.begin(), m.end());
|
|
const auto com = om; // NOLINT(performance-unnecessary-copy-initialization)
|
|
|
|
SECTION("with Key&&")
|
|
{
|
|
CHECK(om.at(std::string("eins")) == std::string("one"));
|
|
CHECK(com.at(std::string("eins")) == std::string("one"));
|
|
CHECK_THROWS_AS(om.at(std::string("vier")), std::out_of_range);
|
|
CHECK_THROWS_AS(com.at(std::string("vier")), std::out_of_range);
|
|
}
|
|
|
|
SECTION("with const Key&&")
|
|
{
|
|
const std::string eins = "eins";
|
|
const std::string vier = "vier";
|
|
CHECK(om.at(eins) == std::string("one"));
|
|
CHECK(com.at(eins) == std::string("one"));
|
|
CHECK_THROWS_AS(om.at(vier), std::out_of_range);
|
|
CHECK_THROWS_AS(com.at(vier), std::out_of_range);
|
|
}
|
|
|
|
SECTION("with string literal")
|
|
{
|
|
CHECK(om.at("eins") == std::string("one"));
|
|
CHECK(com.at("eins") == std::string("one"));
|
|
CHECK_THROWS_AS(om.at("vier"), std::out_of_range);
|
|
CHECK_THROWS_AS(com.at("vier"), std::out_of_range);
|
|
}
|
|
}
|
|
|
|
SECTION("operator[]")
|
|
{
|
|
std::map<std::string, std::string> m {{"eins", "one"}, {"zwei", "two"}, {"drei", "three"}};
|
|
ordered_map<std::string, std::string> om(m.begin(), m.end());
|
|
const auto com = om; // NOLINT(performance-unnecessary-copy-initialization)
|
|
|
|
SECTION("with Key&&")
|
|
{
|
|
CHECK(om[std::string("eins")] == std::string("one"));
|
|
CHECK(com[std::string("eins")] == std::string("one"));
|
|
|
|
CHECK(om[std::string("vier")] == std::string(""));
|
|
CHECK(om.size() == 4);
|
|
}
|
|
|
|
SECTION("with const Key&&")
|
|
{
|
|
const std::string eins = "eins";
|
|
const std::string vier = "vier";
|
|
|
|
CHECK(om[eins] == std::string("one"));
|
|
CHECK(com[eins] == std::string("one"));
|
|
|
|
CHECK(om[vier] == std::string(""));
|
|
CHECK(om.size() == 4);
|
|
}
|
|
|
|
SECTION("with string literal")
|
|
{
|
|
CHECK(om["eins"] == std::string("one"));
|
|
CHECK(com["eins"] == std::string("one"));
|
|
|
|
CHECK(om["vier"] == std::string(""));
|
|
CHECK(om.size() == 4);
|
|
}
|
|
}
|
|
|
|
SECTION("erase")
|
|
{
|
|
ordered_map<std::string, std::string> om;
|
|
om["eins"] = "one";
|
|
om["zwei"] = "two";
|
|
om["drei"] = "three";
|
|
|
|
{
|
|
auto it = om.begin();
|
|
CHECK(it->first == "eins");
|
|
++it;
|
|
CHECK(it->first == "zwei");
|
|
++it;
|
|
CHECK(it->first == "drei");
|
|
++it;
|
|
CHECK(it == om.end());
|
|
}
|
|
|
|
SECTION("with Key&&")
|
|
{
|
|
CHECK(om.size() == 3);
|
|
CHECK(om.erase(std::string("eins")) == 1);
|
|
CHECK(om.size() == 2);
|
|
CHECK(om.erase(std::string("vier")) == 0);
|
|
CHECK(om.size() == 2);
|
|
|
|
auto it = om.begin();
|
|
CHECK(it->first == "zwei");
|
|
++it;
|
|
CHECK(it->first == "drei");
|
|
++it;
|
|
CHECK(it == om.end());
|
|
}
|
|
|
|
SECTION("with const Key&&")
|
|
{
|
|
const std::string eins = "eins";
|
|
const std::string vier = "vier";
|
|
CHECK(om.size() == 3);
|
|
CHECK(om.erase(eins) == 1);
|
|
CHECK(om.size() == 2);
|
|
CHECK(om.erase(vier) == 0);
|
|
CHECK(om.size() == 2);
|
|
|
|
auto it = om.begin();
|
|
CHECK(it->first == "zwei");
|
|
++it;
|
|
CHECK(it->first == "drei");
|
|
++it;
|
|
CHECK(it == om.end());
|
|
}
|
|
|
|
SECTION("with string literal")
|
|
{
|
|
CHECK(om.size() == 3);
|
|
CHECK(om.erase("eins") == 1);
|
|
CHECK(om.size() == 2);
|
|
CHECK(om.erase("vier") == 0);
|
|
CHECK(om.size() == 2);
|
|
|
|
auto it = om.begin();
|
|
CHECK(it->first == "zwei");
|
|
++it;
|
|
CHECK(it->first == "drei");
|
|
++it;
|
|
CHECK(it == om.end());
|
|
}
|
|
|
|
SECTION("with iterator")
|
|
{
|
|
CHECK(om.size() == 3);
|
|
CHECK(om.begin()->first == "eins");
|
|
CHECK(std::next(om.begin(), 1)->first == "zwei");
|
|
CHECK(std::next(om.begin(), 2)->first == "drei");
|
|
|
|
auto it = om.erase(om.begin());
|
|
CHECK(it->first == "zwei");
|
|
CHECK(om.size() == 2);
|
|
|
|
auto it2 = om.begin();
|
|
CHECK(it2->first == "zwei");
|
|
++it2;
|
|
CHECK(it2->first == "drei");
|
|
++it2;
|
|
CHECK(it2 == om.end());
|
|
}
|
|
|
|
SECTION("with iterator pair")
|
|
{
|
|
SECTION("range in the middle")
|
|
{
|
|
// need more elements
|
|
om["vier"] = "four";
|
|
om["fünf"] = "five";
|
|
|
|
// delete "zwei" and "drei"
|
|
auto it = om.erase(om.begin() + 1, om.begin() + 3);
|
|
CHECK(it->first == "vier");
|
|
CHECK(om.size() == 3);
|
|
}
|
|
|
|
SECTION("range at the beginning")
|
|
{
|
|
// need more elements
|
|
om["vier"] = "four";
|
|
om["fünf"] = "five";
|
|
|
|
// delete "eins" and "zwei"
|
|
auto it = om.erase(om.begin(), om.begin() + 2);
|
|
CHECK(it->first == "drei");
|
|
CHECK(om.size() == 3);
|
|
}
|
|
|
|
SECTION("range at the end")
|
|
{
|
|
// need more elements
|
|
om["vier"] = "four";
|
|
om["fünf"] = "five";
|
|
|
|
// delete "vier" and "fünf"
|
|
auto it = om.erase(om.begin() + 3, om.end());
|
|
CHECK(it == om.end());
|
|
CHECK(om.size() == 3);
|
|
}
|
|
}
|
|
}
|
|
|
|
SECTION("count")
|
|
{
|
|
ordered_map<std::string, std::string> om;
|
|
om["eins"] = "one";
|
|
om["zwei"] = "two";
|
|
om["drei"] = "three";
|
|
|
|
const std::string eins("eins");
|
|
const std::string vier("vier");
|
|
CHECK(om.count("eins") == 1);
|
|
CHECK(om.count(std::string("eins")) == 1);
|
|
CHECK(om.count(eins) == 1);
|
|
CHECK(om.count("vier") == 0);
|
|
CHECK(om.count(std::string("vier")) == 0);
|
|
CHECK(om.count(vier) == 0);
|
|
}
|
|
|
|
SECTION("find")
|
|
{
|
|
ordered_map<std::string, std::string> om;
|
|
om["eins"] = "one";
|
|
om["zwei"] = "two";
|
|
om["drei"] = "three";
|
|
const auto com = om;
|
|
|
|
const std::string eins("eins");
|
|
const std::string vier("vier");
|
|
CHECK(om.find("eins") == om.begin());
|
|
CHECK(om.find(std::string("eins")) == om.begin());
|
|
CHECK(om.find(eins) == om.begin());
|
|
CHECK(om.find("vier") == om.end());
|
|
CHECK(om.find(std::string("vier")) == om.end());
|
|
CHECK(om.find(vier) == om.end());
|
|
|
|
CHECK(com.find("eins") == com.begin());
|
|
CHECK(com.find(std::string("eins")) == com.begin());
|
|
CHECK(com.find(eins) == com.begin());
|
|
CHECK(com.find("vier") == com.end());
|
|
CHECK(com.find(std::string("vier")) == com.end());
|
|
CHECK(com.find(vier) == com.end());
|
|
|
|
#ifdef JSON_HAS_CPP_17
|
|
CHECK(om.find(std::string_view("eins")) == om.begin());
|
|
CHECK(com.find(std::string_view("eins")) == com.begin());
|
|
#endif
|
|
}
|
|
|
|
SECTION("insert")
|
|
{
|
|
ordered_map<std::string, std::string> om;
|
|
om["eins"] = "one";
|
|
om["zwei"] = "two";
|
|
om["drei"] = "three";
|
|
|
|
SECTION("const value_type&")
|
|
{
|
|
ordered_map<std::string, std::string>::value_type const vt1 {"eins", "1"};
|
|
ordered_map<std::string, std::string>::value_type const vt4 {"vier", "four"};
|
|
|
|
auto res1 = om.insert(vt1);
|
|
CHECK(res1.first == om.begin());
|
|
CHECK(res1.second == false);
|
|
CHECK(om.size() == 3);
|
|
|
|
auto res4 = om.insert(vt4);
|
|
CHECK(res4.first == om.begin() + 3);
|
|
CHECK(res4.second == true);
|
|
CHECK(om.size() == 4);
|
|
}
|
|
|
|
SECTION("value_type&&")
|
|
{
|
|
auto res1 = om.insert({"eins", "1"});
|
|
CHECK(res1.first == om.begin());
|
|
CHECK(res1.second == false);
|
|
CHECK(om.size() == 3);
|
|
|
|
auto res4 = om.insert({"vier", "four"});
|
|
CHECK(res4.first == om.begin() + 3);
|
|
CHECK(res4.second == true);
|
|
CHECK(om.size() == 4);
|
|
}
|
|
}
|
|
|
|
SECTION("emplace")
|
|
{
|
|
// regression test for issue #5673: the mapped-value parameter must
|
|
// accept lvalues and const lvalues, not just rvalues
|
|
ordered_map<std::string, std::string> om;
|
|
om["eins"] = "one";
|
|
om["zwei"] = "two";
|
|
om["drei"] = "three";
|
|
|
|
SECTION("with T&& (rvalue)")
|
|
{
|
|
auto res1 = om.emplace("eins", std::string("1"));
|
|
CHECK(res1.first == om.begin());
|
|
CHECK(res1.second == false);
|
|
CHECK(om.size() == 3);
|
|
CHECK(om.at("eins") == "one"); // existing key is not overwritten
|
|
|
|
auto res4 = om.emplace("vier", std::string("four"));
|
|
CHECK(res4.first == om.begin() + 3);
|
|
CHECK(res4.second == true);
|
|
CHECK(om.size() == 4);
|
|
CHECK(om.at("vier") == "four");
|
|
}
|
|
|
|
SECTION("with T& (lvalue)")
|
|
{
|
|
std::string one = "1"; // NOLINT(misc-const-correctness): emplace must accept a non-const lvalue
|
|
std::string four = "four"; // NOLINT(misc-const-correctness): see above
|
|
|
|
auto res1 = om.emplace("eins", one);
|
|
CHECK(res1.first == om.begin());
|
|
CHECK(res1.second == false);
|
|
CHECK(om.size() == 3);
|
|
CHECK(om.at("eins") == "one"); // existing key is not overwritten
|
|
|
|
auto res4 = om.emplace("vier", four);
|
|
CHECK(res4.first == om.begin() + 3);
|
|
CHECK(res4.second == true);
|
|
CHECK(om.size() == 4);
|
|
CHECK(om.at("vier") == "four");
|
|
CHECK(four == "four"); // source was copied, not moved from
|
|
}
|
|
|
|
SECTION("with const T&")
|
|
{
|
|
const std::string one = "1";
|
|
const std::string four = "four";
|
|
|
|
auto res1 = om.emplace("eins", one);
|
|
CHECK(res1.first == om.begin());
|
|
CHECK(res1.second == false);
|
|
CHECK(om.size() == 3);
|
|
|
|
auto res4 = om.emplace("vier", four);
|
|
CHECK(res4.first == om.begin() + 3);
|
|
CHECK(res4.second == true);
|
|
CHECK(om.size() == 4);
|
|
CHECK(om.at("vier") == "four");
|
|
}
|
|
|
|
SECTION("with key of key_type (non-template overload)")
|
|
{
|
|
const std::string key_vier{"vier"};
|
|
std::string four = "four"; // NOLINT(misc-const-correctness): emplace must accept a non-const lvalue
|
|
|
|
auto res4 = om.emplace(key_vier, four);
|
|
CHECK(res4.first == om.begin() + 3);
|
|
CHECK(res4.second == true);
|
|
CHECK(om.size() == 4);
|
|
CHECK(om.at("vier") == "four");
|
|
}
|
|
}
|
|
}
|
|
|
|
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;
|
|
}
|
|
}
|
|
}
|
|
}
|