Compare commits

..
Author SHA1 Message Date
Niels Lohmann 54ed6480d5 Fix deprecated json_pointer/string operator== warning in value()
value(KeyType&&, default) is constrained on is_comparable_with_object_key,
which passes KeyType as a reference. is_comparable's dispatch on
is_json_pointer_of<A, B> only matches a json_pointer as a plain type or a
plain reference, so a const-qualified reference (as produced when KeyType
is deduced from a json_pointer argument) fell through to
is_comparable_no_json_pointer, which instantiates the deprecated
json_pointer/string comparison operators. This made ordered_json's
transparent comparator (and any transparent comparator on a custom string
type) warn under -Wdeprecated-declarations when calling
value(json_pointer, default), even though no such comparison is ever
performed. at() was already fixed for this in #5289, which does not use
is_comparable_with_object_key.

Strip references and cv-qualifiers with uncvref_t before the
is_json_pointer_of dispatch, so any reference-to-json_pointer is
recognized regardless of qualifiers.

Fixes #5664.

Signed-off-by: Niels Lohmann <mail@nlohmann.me>
2026-09-29 23:17:00 +02:00
6 changed files with 24 additions and 156 deletions
@@ -96,8 +96,5 @@ Linear.
## Version history
1. Added in version 3.11.0. Fixed in version 3.13.0 to keep the result independent of nesting depth: before, a pair
of binary values with the same bytes but a different subtype - unequal by `operator==`, yet equivalent by
`operator<=>` - could end an ordered comparison as `unordered` only past 128 levels of nesting, or at every depth
with [`JSON_NO_THREAD_LOCAL`](../macros/json_no_thread_local.md) defined.
1. Added in version 3.11.0.
2. Added in version 3.11.0.
+1 -1
View File
@@ -725,7 +725,7 @@ std::is_constructible <decltype(std::declval<Compare>()(std::declval<A>(), std::
// avoid their instantiation on all compilers, even when the first operand
// is false. The dispatch on is_json_pointer_of can be removed once the
// deprecated json_pointer comparison operators have been removed.
template<typename Compare, typename A, typename B, bool = is_json_pointer_of<A, B>::value>
template<typename Compare, typename A, typename B, bool = is_json_pointer_of<uncvref_t<A>, uncvref_t<B>>::value>
struct is_comparable : std::false_type {};
template<typename Compare, typename A, typename B>
+1 -51
View File
@@ -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
+2 -52
View File
@@ -4735,7 +4735,7 @@ std::is_constructible <decltype(std::declval<Compare>()(std::declval<A>(), std::
// avoid their instantiation on all compilers, even when the first operand
// is false. The dispatch on is_json_pointer_of can be removed once the
// deprecated json_pointer comparison operators have been removed.
template<typename Compare, typename A, typename B, bool = is_json_pointer_of<A, B>::value>
template<typename Compare, typename A, typename B, bool = is_json_pointer_of<uncvref_t<A>, uncvref_t<B>>::value>
struct is_comparable : std::false_type {};
template<typename Compare, typename A, typename B>
@@ -27388,65 +27388,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
-48
View File
@@ -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
+19
View File
@@ -858,6 +858,25 @@ TEST_CASE("JSON pointers")
}
}
SECTION("value(json_pointer, default) with ordered_json #5664")
{
// ordered_json's transparent object comparator made value()'s
// is_comparable_with_object_key check (which passes the pointer as
// a reference) instantiate the deprecated json_pointer/string
// comparison; this must compile without relying on it. The
// deprecation warning itself is not observable here, since the
// unit test build disables -Wdeprecated-declarations (see
// cmake/clang_flags.cmake); it was checked manually instead.
const nlohmann::ordered_json j = {{"n", 1}, {"s", "text"}};
const nlohmann::ordered_json::json_pointer ptr_n("/n");
const nlohmann::ordered_json::json_pointer ptr_s("/s");
const nlohmann::ordered_json::json_pointer ptr_missing("/missing");
CHECK(j.value(ptr_n, 0) == 1);
CHECK(j.value(ptr_s, std::string("x")) == "text");
CHECK(j.value(ptr_missing, 42) == 42);
}
// build with C++20
// JSON_HAS_CPP_20
#if defined(__cpp_char8_t)