mirror of
https://github.com/nlohmann/json.git
synced 2026-10-04 21:50:33 +00:00
* Accept lvalues in ordered_map::emplace's value parameter
ordered_map::emplace(key, value) took the mapped value only by T&&, an
rvalue reference rather than a forwarding reference, so
ordered_json::emplace("a", value) failed to compile whenever value was
an lvalue or a const lvalue, even though the same call compiles for
json (whose object_t is std::map, with a variadic emplace). Turn the
value parameter into a separately-deduced forwarding reference,
constrained with std::is_constructible so the overloads still only
accept something convertible to the mapped type. std::map-compatible
semantics are unchanged: emplace still does nothing if the key already
exists.
Open PR #5609 also touches ordered_map.hpp (moving values on vector
growth); this change only touches the two emplace() overloads and
should not conflict.
Fixes #5673.
Signed-off-by: Niels Lohmann <mail@nlohmann.me>
* Avoid astyle's padding in ordered_map::emplace's template headers
Use detail::conjunction instead of && and drop the redundant V&& in detail::is_constructible, so astyle keeps the usual template formatting. Addresses review comment by @gregmarr.
Signed-off-by: Niels Lohmann <mail@nlohmann.me>
---------
Signed-off-by: Niels Lohmann <mail@nlohmann.me>
747 lines
23 KiB
C++
747 lines
23 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";
|
|
std::string four = "four";
|
|
|
|
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";
|
|
|
|
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;
|
|
}
|
|
}
|
|
}
|
|
}
|