mirror of
https://github.com/nlohmann/json.git
synced 2026-09-30 11:40:30 +00:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1daad8efe0 | ||
|
|
989b8ed841 |
@@ -69,3 +69,5 @@ Logarithmic in the size of the container, O(log(`size()`)).
|
||||
## Version history
|
||||
|
||||
- Since version 2.0.8.
|
||||
- Fixed in version 3.13.0: for [`ordered_json`](../ordered_json.md), the value could previously only be passed as an
|
||||
rvalue; it can now also be passed as an lvalue or a `#!cpp const` lvalue, matching the behavior of `json`.
|
||||
|
||||
@@ -1307,65 +1307,15 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
|
||||
*/
|
||||
template<bool Ordered>
|
||||
static compare_result compare_leaves(const_reference lhs, const_reference rhs) noexcept
|
||||
{
|
||||
return compare_leaves(lhs, rhs, std::integral_constant<bool, Ordered> {});
|
||||
}
|
||||
|
||||
/// @brief compare two leaves that are only being checked for equality
|
||||
static compare_result compare_leaves(const_reference lhs, const_reference rhs, std::false_type /*ordered*/) noexcept
|
||||
{
|
||||
if (lhs == rhs)
|
||||
{
|
||||
return compare_result::equal;
|
||||
}
|
||||
|
||||
return order_leaves(lhs, rhs, std::false_type {});
|
||||
return order_leaves(lhs, rhs, std::integral_constant<bool, Ordered> {});
|
||||
}
|
||||
|
||||
#if JSON_HAS_THREE_WAY_COMPARISON
|
||||
/*!
|
||||
@brief compare two leaves that are being ordered, for operator<=>
|
||||
|
||||
Reached only from operator<=>, so the leaves must be classified exactly
|
||||
as operator<=> classifies them - which is not the same as asking
|
||||
== and then order_leaves(), the way the other overload does it. The two
|
||||
disagree on a binary value: == also compares the subtype, but <=> compares
|
||||
only the bytes, through std::vector<std::uint8_t>::operator<=>. Using <=>
|
||||
itself here keeps a leaf pair classified the same way regardless of how
|
||||
deep it is nested - == first would again call operator<=> a level down
|
||||
through order_leaves(), but call it after a mismatching == already ended
|
||||
the comparison for a pair that <=> alone would still call equivalent.
|
||||
*/
|
||||
static compare_result compare_leaves(const_reference lhs, const_reference rhs, std::true_type /*ordered*/) noexcept
|
||||
{
|
||||
const std::partial_ordering order = lhs <=> rhs; // *NOPAD*
|
||||
if (order == 0)
|
||||
{
|
||||
return compare_result::equal;
|
||||
}
|
||||
if (order < 0)
|
||||
{
|
||||
return compare_result::less;
|
||||
}
|
||||
if (order > 0)
|
||||
{
|
||||
return compare_result::greater;
|
||||
}
|
||||
return compare_result::unordered;
|
||||
}
|
||||
#else
|
||||
/// @brief compare two leaves that are being ordered, for operator<
|
||||
static compare_result compare_leaves(const_reference lhs, const_reference rhs, std::true_type /*ordered*/) noexcept
|
||||
{
|
||||
if (lhs == rhs)
|
||||
{
|
||||
return compare_result::equal;
|
||||
}
|
||||
|
||||
return order_leaves(lhs, rhs, std::true_type {});
|
||||
}
|
||||
#endif
|
||||
|
||||
/*!
|
||||
@brief compare two object keys
|
||||
|
||||
|
||||
@@ -70,7 +70,9 @@ template <class Key, class T, class IgnoredLess = std::less<Key>,
|
||||
return *this;
|
||||
}
|
||||
|
||||
std::pair<iterator, bool> emplace(const key_type& key, T&& t)
|
||||
template<class V, detail::enable_if_t<
|
||||
detail::is_constructible<T, V>::value, int> = 0>
|
||||
std::pair<iterator, bool> emplace(const key_type& key, V && t)
|
||||
{
|
||||
for (auto it = this->begin(); it != this->end(); ++it)
|
||||
{
|
||||
@@ -79,13 +81,14 @@ template <class Key, class T, class IgnoredLess = std::less<Key>,
|
||||
return {it, false};
|
||||
}
|
||||
}
|
||||
Container::emplace_back(key, std::forward<T>(t));
|
||||
Container::emplace_back(key, std::forward<V>(t));
|
||||
return {std::prev(this->end()), true};
|
||||
}
|
||||
|
||||
template<class KeyType, detail::enable_if_t<
|
||||
detail::is_usable_as_key_type<key_compare, key_type, KeyType>::value, int> = 0>
|
||||
std::pair<iterator, bool> emplace(KeyType && key, T && t)
|
||||
template<class KeyType, class V, detail::enable_if_t<
|
||||
detail::conjunction<detail::is_usable_as_key_type<key_compare, key_type, KeyType>,
|
||||
detail::is_constructible<T, V>>::value, int> = 0>
|
||||
std::pair<iterator, bool> emplace(KeyType && key, V && t)
|
||||
{
|
||||
for (auto it = this->begin(); it != this->end(); ++it)
|
||||
{
|
||||
@@ -94,7 +97,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));
|
||||
Container::emplace_back(std::forward<KeyType>(key), std::forward<V>(t));
|
||||
return {std::prev(this->end()), true};
|
||||
}
|
||||
|
||||
|
||||
@@ -25832,7 +25832,9 @@ template <class Key, class T, class IgnoredLess = std::less<Key>,
|
||||
return *this;
|
||||
}
|
||||
|
||||
std::pair<iterator, bool> emplace(const key_type& key, T&& t)
|
||||
template<class V, detail::enable_if_t<
|
||||
detail::is_constructible<T, V>::value, int> = 0>
|
||||
std::pair<iterator, bool> emplace(const key_type& key, V && t)
|
||||
{
|
||||
for (auto it = this->begin(); it != this->end(); ++it)
|
||||
{
|
||||
@@ -25841,13 +25843,14 @@ template <class Key, class T, class IgnoredLess = std::less<Key>,
|
||||
return {it, false};
|
||||
}
|
||||
}
|
||||
Container::emplace_back(key, std::forward<T>(t));
|
||||
Container::emplace_back(key, std::forward<V>(t));
|
||||
return {std::prev(this->end()), true};
|
||||
}
|
||||
|
||||
template<class KeyType, detail::enable_if_t<
|
||||
detail::is_usable_as_key_type<key_compare, key_type, KeyType>::value, int> = 0>
|
||||
std::pair<iterator, bool> emplace(KeyType && key, T && t)
|
||||
template<class KeyType, class V, detail::enable_if_t<
|
||||
detail::conjunction<detail::is_usable_as_key_type<key_compare, key_type, KeyType>,
|
||||
detail::is_constructible<T, V>>::value, int> = 0>
|
||||
std::pair<iterator, bool> emplace(KeyType && key, V && t)
|
||||
{
|
||||
for (auto it = this->begin(); it != this->end(); ++it)
|
||||
{
|
||||
@@ -25856,7 +25859,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));
|
||||
Container::emplace_back(std::forward<KeyType>(key), std::forward<V>(t));
|
||||
return {std::prev(this->end()), true};
|
||||
}
|
||||
|
||||
@@ -27388,65 +27391,15 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
|
||||
*/
|
||||
template<bool Ordered>
|
||||
static compare_result compare_leaves(const_reference lhs, const_reference rhs) noexcept
|
||||
{
|
||||
return compare_leaves(lhs, rhs, std::integral_constant<bool, Ordered> {});
|
||||
}
|
||||
|
||||
/// @brief compare two leaves that are only being checked for equality
|
||||
static compare_result compare_leaves(const_reference lhs, const_reference rhs, std::false_type /*ordered*/) noexcept
|
||||
{
|
||||
if (lhs == rhs)
|
||||
{
|
||||
return compare_result::equal;
|
||||
}
|
||||
|
||||
return order_leaves(lhs, rhs, std::false_type {});
|
||||
return order_leaves(lhs, rhs, std::integral_constant<bool, Ordered> {});
|
||||
}
|
||||
|
||||
#if JSON_HAS_THREE_WAY_COMPARISON
|
||||
/*!
|
||||
@brief compare two leaves that are being ordered, for operator<=>
|
||||
|
||||
Reached only from operator<=>, so the leaves must be classified exactly
|
||||
as operator<=> classifies them - which is not the same as asking
|
||||
== and then order_leaves(), the way the other overload does it. The two
|
||||
disagree on a binary value: == also compares the subtype, but <=> compares
|
||||
only the bytes, through std::vector<std::uint8_t>::operator<=>. Using <=>
|
||||
itself here keeps a leaf pair classified the same way regardless of how
|
||||
deep it is nested - == first would again call operator<=> a level down
|
||||
through order_leaves(), but call it after a mismatching == already ended
|
||||
the comparison for a pair that <=> alone would still call equivalent.
|
||||
*/
|
||||
static compare_result compare_leaves(const_reference lhs, const_reference rhs, std::true_type /*ordered*/) noexcept
|
||||
{
|
||||
const std::partial_ordering order = lhs <=> rhs; // *NOPAD*
|
||||
if (order == 0)
|
||||
{
|
||||
return compare_result::equal;
|
||||
}
|
||||
if (order < 0)
|
||||
{
|
||||
return compare_result::less;
|
||||
}
|
||||
if (order > 0)
|
||||
{
|
||||
return compare_result::greater;
|
||||
}
|
||||
return compare_result::unordered;
|
||||
}
|
||||
#else
|
||||
/// @brief compare two leaves that are being ordered, for operator<
|
||||
static compare_result compare_leaves(const_reference lhs, const_reference rhs, std::true_type /*ordered*/) noexcept
|
||||
{
|
||||
if (lhs == rhs)
|
||||
{
|
||||
return compare_result::equal;
|
||||
}
|
||||
|
||||
return order_leaves(lhs, rhs, std::true_type {});
|
||||
}
|
||||
#endif
|
||||
|
||||
/*!
|
||||
@brief compare two object keys
|
||||
|
||||
|
||||
@@ -952,51 +952,3 @@ TEST_CASE("containers are compared element by element")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#if JSON_HAS_THREE_WAY_COMPARISON
|
||||
// JSON_HAS_CPP_20 (do not remove; see note at top of file)
|
||||
TEST_CASE("operator<=> of binary values with a different subtype does not depend on nesting depth")
|
||||
{
|
||||
// #5654: std::vector<std::uint8_t>::operator<=>, which the binary type's
|
||||
// own operator<=> uses, ignores the subtype that operator== checks. So a
|
||||
// pair of binary values with the same bytes but a different subtype is
|
||||
// unequal, yet <=>-equivalent - the same inconsistency between == and <=>
|
||||
// that a NaN has. Within the nesting bound, an array compares itself
|
||||
// with std::vector's own operator<=>, which treats an equivalent pair as
|
||||
// undecided and lets the next element decide, same as
|
||||
// std::lexicographical_compare_three_way does. Past the bound,
|
||||
// compare_iteratively<true>() takes over and must classify the pair the
|
||||
// same way, or the result of operator<=> - and of <, which C++20 derives
|
||||
// from it - depends on how deeply the values are nested.
|
||||
const json a = json::array({json::binary({1}, 1), 1});
|
||||
const json b = json::array({json::binary({1}, 2), 2});
|
||||
|
||||
// the root inconsistency: unequal, yet <=>-equivalent
|
||||
CHECK_FALSE(a[0] == b[0]);
|
||||
CHECK((a[0] <=> b[0]) == std::partial_ordering::equivalent); // *NOPAD*
|
||||
|
||||
const auto deep = [](const json & j, const std::size_t depth)
|
||||
{
|
||||
json result = j;
|
||||
for (std::size_t i = 0; i < depth; ++i)
|
||||
{
|
||||
result = json::array({std::move(result)});
|
||||
}
|
||||
return result;
|
||||
};
|
||||
|
||||
// 127 levels stay within nesting_depth_limit() (128); 128 and 200 do not,
|
||||
// and must still agree with the levels that do
|
||||
for (const std::size_t depth : std::vector<std::size_t> {0, 127, 128, 200})
|
||||
{
|
||||
CAPTURE(depth);
|
||||
const json x = deep(a, depth);
|
||||
const json y = deep(b, depth);
|
||||
CHECK((x <=> y) == std::partial_ordering::less); // *NOPAD*
|
||||
CHECK((y <=> x) == std::partial_ordering::greater); // *NOPAD*
|
||||
CHECK(x < y);
|
||||
CHECK(y > x);
|
||||
CHECK_FALSE(y < x);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
@@ -196,3 +196,44 @@ TEST_CASE("regression test - diff() must account for ordered_json member order")
|
||||
CHECK(a.patch(p) == b);
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("regression test for issue #5673 - ordered_json::emplace with a non-rvalue value")
|
||||
{
|
||||
SECTION("lvalue value")
|
||||
{
|
||||
ordered_json oj = ordered_json::object();
|
||||
ordered_json value = 1;
|
||||
auto res = oj.emplace("a", value);
|
||||
CHECK(res.second == true);
|
||||
CHECK(oj.dump() == "{\"a\":1}");
|
||||
}
|
||||
|
||||
SECTION("const lvalue value")
|
||||
{
|
||||
ordered_json oj = ordered_json::object();
|
||||
const ordered_json value = 1;
|
||||
auto res = oj.emplace("a", value);
|
||||
CHECK(res.second == true);
|
||||
CHECK(oj.dump() == "{\"a\":1}");
|
||||
}
|
||||
|
||||
SECTION("rvalue value")
|
||||
{
|
||||
ordered_json oj = ordered_json::object();
|
||||
auto res = oj.emplace("a", ordered_json(1));
|
||||
CHECK(res.second == true);
|
||||
CHECK(oj.dump() == "{\"a\":1}");
|
||||
}
|
||||
|
||||
SECTION("existing key is not overwritten (std::map-compatible semantics)")
|
||||
{
|
||||
ordered_json oj = ordered_json::object();
|
||||
ordered_json value = 1;
|
||||
oj.emplace("a", value);
|
||||
|
||||
ordered_json other_value = 2;
|
||||
auto res = oj.emplace("a", other_value);
|
||||
CHECK(res.second == false);
|
||||
CHECK(oj.dump() == "{\"a\":1}");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -312,4 +312,77 @@ TEST_CASE("ordered_map")
|
||||
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");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user