From db5bd7fbcc52d59e6daf4bfa9e14ab9334671a2a Mon Sep 17 00:00:00 2001 From: Niels Lohmann Date: Thu, 22 Oct 2026 20:13:22 +0200 Subject: [PATCH] Fix the compiler and lint failures of the json_view comparison - all_digits(): the lambda declares its constant itself; MSVC does not let a lambda without captures read a constexpr local of the function - equal(): the frame's "in document order" index is a namespace-scope constant (document_order); the local constexpr npos in the default member initializers of the local struct crashed GCC 9 and MinGW GCC and was not found by MSVC - keys_differ_early() keeps the first keys in std::arrays (cpplint: variable-length arrays; clang-tidy: C arrays), and the basic_json side's first_keys() is not noexcept (key() may throw) - the numbers of one type are compared in an if chain instead of a nested conditional operator - the dump test avoids C arrays and temporary string concatenations View comparison instructions are unchanged (Apple clang, twitter, citm, canada: -0.1 % to +0.1 %). Signed-off-by: Niels Lohmann --- include/nlohmann/detail/view/compare.hpp | 39 ++++++++++++++------- include/nlohmann/detail/view/image.hpp | 2 +- single_include/nlohmann/json_view.hpp | 41 ++++++++++++++-------- tests/src/unit-json_view_dump.cpp | 44 ++++++++++++++++++------ 4 files changed, 88 insertions(+), 38 deletions(-) diff --git a/include/nlohmann/detail/view/compare.hpp b/include/nlohmann/detail/view/compare.hpp index 532c05f6e..5fbe6ad1a 100644 --- a/include/nlohmann/detail/view/compare.hpp +++ b/include/nlohmann/detail/view/compare.hpp @@ -479,7 +479,7 @@ class json_side } /// the keys of the first n members of this object (n <= its size), in document order - void first_keys(string_view_t* out, std::size_t n) const noexcept + void first_keys(string_view_t* out, std::size_t n) const { auto it = m_json->cbegin(); for (std::size_t i = 0; i < n; ++i, ++it) @@ -808,10 +808,10 @@ bool keys_differ_early(const SideA& a, const SideB& b) using string_view_t = typename SideA::string_view_t; constexpr std::size_t limit = 4; const std::size_t n = (std::min)((std::min)(a.size(), b.size()), limit); - string_view_t ka[limit]; - string_view_t kb[limit]; - a.first_keys(ka, n); - b.first_keys(kb, n); + std::array ka{}; + std::array kb{}; + a.first_keys(ka.data(), n); + b.first_keys(kb.data(), n); for (std::size_t i = 0; i < n; ++i) { if (ka[i] != kb[i]) @@ -834,6 +834,9 @@ inline bool is_number(value_t t) noexcept return t == value_t::number_integer || t == value_t::number_unsigned || t == value_t::number_float; } +/// the index_a/index_b of a frame of equal() whose members are compared in document order +constexpr std::size_t document_order = static_cast(-1); + /// whether two sides are equal; iterative, so that the nesting depth is /// limited by memory only template @@ -842,7 +845,6 @@ bool equal(const A& a0, const B& b0) using string_view_t = typename A::string_view_t; const bool ordered = is_ordered_map::value; - constexpr std::size_t npos = static_cast(-1); struct frame { A array_a{}; @@ -851,8 +853,8 @@ bool equal(const A& a0, const B& b0) typename B::cursor cursor_b{}; std::size_t base_a = 0; ///< the members of an object: from this position in members_a std::size_t base_b = 0; - std::size_t index_a = npos; ///< their order in the comparison: from this position in indices (npos: as they are) - std::size_t index_b = npos; + std::size_t index_a = document_order; ///< their order in the comparison: from this position in indices (document_order: as they are) + std::size_t index_b = document_order; std::size_t index_mark = 0; ///< the size of indices before this frame bool object = false; std::size_t next = 0; @@ -983,9 +985,20 @@ bool equal(const A& a0, const B& b0) else if (numbers && ta == tb) { // numbers of one type: as basic_json compares them - if (ta == value_t::number_integer ? a.integer() != b.integer() - : ta == value_t::number_unsigned ? a.unsigned_integer() != b.unsigned_integer() - : !floats_equal(a, b)) + bool differ = false; + if (ta == value_t::number_integer) + { + differ = a.integer() != b.integer(); + } + else if (ta == value_t::number_unsigned) + { + differ = a.unsigned_integer() != b.unsigned_integer(); + } + else + { + differ = !floats_equal(a, b); + } + if (differ) { return false; } @@ -1018,8 +1031,8 @@ bool equal(const A& a0, const B& b0) } if (f.object) { - const auto& member_a = members_a[f.base_a + (f.index_a == npos ? f.next : indices[f.index_a + f.next])]; - const auto& member_b = members_b[f.base_b + (f.index_b == npos ? f.next : indices[f.index_b + f.next])]; + const auto& member_a = members_a[f.base_a + (f.index_a == document_order ? f.next : indices[f.index_a + f.next])]; + const auto& member_b = members_b[f.base_b + (f.index_b == document_order ? f.next : indices[f.index_b + f.next])]; if (!(member_a.first == member_b.first)) { return false; diff --git a/include/nlohmann/detail/view/image.hpp b/include/nlohmann/detail/view/image.hpp index 14ab6609a..e6df3ea2c 100644 --- a/include/nlohmann/detail/view/image.hpp +++ b/include/nlohmann/detail/view/image.hpp @@ -282,9 +282,9 @@ inline const unsigned char* digit_run(const unsigned char* p, const unsigned cha /// whether the n bytes at p are ASCII digits (n > 0) inline bool all_digits(const unsigned char* p, std::size_t n) noexcept { - constexpr std::uint64_t ones = 0x0101010101010101ull; const auto digits = [](const unsigned char* q) { + constexpr std::uint64_t ones = 0x0101010101010101ull; const std::uint64_t x = read_eight_bytes(q) ^ (ones * 0x30u); return (((x + (ones * 0x76u)) | x) & (ones * 0x80u)) == 0; }; diff --git a/single_include/nlohmann/json_view.hpp b/single_include/nlohmann/json_view.hpp index c497f3d7b..0fb64bb3d 100644 --- a/single_include/nlohmann/json_view.hpp +++ b/single_include/nlohmann/json_view.hpp @@ -3167,7 +3167,7 @@ class json_side } /// the keys of the first n members of this object (n <= its size), in document order - void first_keys(string_view_t* out, std::size_t n) const noexcept + void first_keys(string_view_t* out, std::size_t n) const { auto it = m_json->cbegin(); for (std::size_t i = 0; i < n; ++i, ++it) @@ -3496,10 +3496,10 @@ bool keys_differ_early(const SideA& a, const SideB& b) using string_view_t = typename SideA::string_view_t; constexpr std::size_t limit = 4; const std::size_t n = (std::min)((std::min)(a.size(), b.size()), limit); - string_view_t ka[limit]; - string_view_t kb[limit]; - a.first_keys(ka, n); - b.first_keys(kb, n); + std::array ka{}; + std::array kb{}; + a.first_keys(ka.data(), n); + b.first_keys(kb.data(), n); for (std::size_t i = 0; i < n; ++i) { if (ka[i] != kb[i]) @@ -3522,6 +3522,9 @@ inline bool is_number(value_t t) noexcept return t == value_t::number_integer || t == value_t::number_unsigned || t == value_t::number_float; } +/// the index_a/index_b of a frame of equal() whose members are compared in document order +constexpr std::size_t document_order = static_cast(-1); + /// whether two sides are equal; iterative, so that the nesting depth is /// limited by memory only template @@ -3530,7 +3533,6 @@ bool equal(const A& a0, const B& b0) using string_view_t = typename A::string_view_t; const bool ordered = is_ordered_map::value; - constexpr std::size_t npos = static_cast(-1); struct frame { A array_a{}; @@ -3539,8 +3541,8 @@ bool equal(const A& a0, const B& b0) typename B::cursor cursor_b{}; std::size_t base_a = 0; ///< the members of an object: from this position in members_a std::size_t base_b = 0; - std::size_t index_a = npos; ///< their order in the comparison: from this position in indices (npos: as they are) - std::size_t index_b = npos; + std::size_t index_a = document_order; ///< their order in the comparison: from this position in indices (document_order: as they are) + std::size_t index_b = document_order; std::size_t index_mark = 0; ///< the size of indices before this frame bool object = false; std::size_t next = 0; @@ -3671,9 +3673,20 @@ bool equal(const A& a0, const B& b0) else if (numbers && ta == tb) { // numbers of one type: as basic_json compares them - if (ta == value_t::number_integer ? a.integer() != b.integer() - : ta == value_t::number_unsigned ? a.unsigned_integer() != b.unsigned_integer() - : !floats_equal(a, b)) + bool differ = false; + if (ta == value_t::number_integer) + { + differ = a.integer() != b.integer(); + } + else if (ta == value_t::number_unsigned) + { + differ = a.unsigned_integer() != b.unsigned_integer(); + } + else + { + differ = !floats_equal(a, b); + } + if (differ) { return false; } @@ -3706,8 +3719,8 @@ bool equal(const A& a0, const B& b0) } if (f.object) { - const auto& member_a = members_a[f.base_a + (f.index_a == npos ? f.next : indices[f.index_a + f.next])]; - const auto& member_b = members_b[f.base_b + (f.index_b == npos ? f.next : indices[f.index_b + f.next])]; + const auto& member_a = members_a[f.base_a + (f.index_a == document_order ? f.next : indices[f.index_a + f.next])]; + const auto& member_b = members_b[f.base_b + (f.index_b == document_order ? f.next : indices[f.index_b + f.next])]; if (!(member_a.first == member_b.first)) { return false; @@ -5739,9 +5752,9 @@ inline const unsigned char* digit_run(const unsigned char* p, const unsigned cha /// whether the n bytes at p are ASCII digits (n > 0) inline bool all_digits(const unsigned char* p, std::size_t n) noexcept { - constexpr std::uint64_t ones = 0x0101010101010101ull; const auto digits = [](const unsigned char* q) { + constexpr std::uint64_t ones = 0x0101010101010101ull; const std::uint64_t x = read_eight_bytes(q) ^ (ones * 0x30u); return (((x + (ones * 0x76u)) | x) & (ones * 0x80u)) == 0; }; diff --git a/tests/src/unit-json_view_dump.cpp b/tests/src/unit-json_view_dump.cpp index 6776b951d..2fd194852 100644 --- a/tests/src/unit-json_view_dump.cpp +++ b/tests/src/unit-json_view_dump.cpp @@ -347,12 +347,14 @@ TEST_CASE("json_view comparison") SECTION("objects whose first keys differ or repeat") { - const char* const texts[] = + const std::array texts = { - R"({"a":1,"b":2})", R"({"a":1,"c":2})", R"({"b":1,"a":2})", R"({"a":1,"a":2,"b":3})", R"({"a":2,"b":3})", - R"({"a":1,"b":1,"a":2})", R"({"a":2,"b":1})", R"({"a":1,"a":2})", R"({"b":1})", R"({"a":1,"b":2,"c":3,"d":4,"e":5})", - R"({"a":1,"b":2,"c":3,"d":4,"f":5})", R"({"a":1,"b":2,"c":3,"a":4,"e":5})", R"({"a":4,"b":2,"c":3,"e":5})", - R"({"\u0061":1,"b":2})", R"({})", R"([{"a":1,"b":2}])", R"([{"a":1,"c":2}])" + { + R"({"a":1,"b":2})", R"({"a":1,"c":2})", R"({"b":1,"a":2})", R"({"a":1,"a":2,"b":3})", R"({"a":2,"b":3})", + R"({"a":1,"b":1,"a":2})", R"({"a":2,"b":1})", R"({"a":1,"a":2})", R"({"b":1})", R"({"a":1,"b":2,"c":3,"d":4,"e":5})", + R"({"a":1,"b":2,"c":3,"d":4,"f":5})", R"({"a":1,"b":2,"c":3,"a":4,"e":5})", R"({"a":4,"b":2,"c":3,"e":5})", + R"({"\u0061":1,"b":2})", R"({})", R"([{"a":1,"b":2}])", R"([{"a":1,"c":2}])" + } }; for (const char* const x : texts) { @@ -426,12 +428,19 @@ TEST_CASE("json_view comparison") for (int i = 0; i < depth; ++i) { head += i % 2 == 0 ? R"({"k":[1,"s",{"x":true},)" : R"([null,2.5,{"y":)"; - tail = (i % 2 == 0 ? "]}" : "}]") + tail; + tail.insert(0, i % 2 == 0 ? "]}" : "}]"); } // the same text, one number changed, and one number written differently - const std::string text = head + "7" + tail; - const std::string changed = head + "8" + tail; - const std::string spelled = head + "7.0" + tail; + const auto with_number = [&](const char* number) + { + std::string result = head; + result += number; + result += tail; + return result; + }; + const std::string text = with_number("7"); + const std::string changed = with_number("8"); + const std::string spelled = with_number("7.0"); const json_document a = json_document::parse(text); const json_document b = json_document::parse(text); const json_document c = json_document::parse(changed); @@ -500,7 +509,22 @@ TEST_CASE("json_view comparison") { text += (i != 0 ? "," : ""); const std::string id = std::to_string(i == changed ? i + 1 : i); - text += swap_members && i % 3 == 0 ? R"({"v":[1.5,"s",{"k":)" + id + R"(}],"id":)" + id + "}" : R"({"id":)" + id + R"(,"v":[1.5,"s",{"k":)" + id + "}]}"; + if (swap_members && i % 3 == 0) + { + text += R"({"v":[1.5,"s",{"k":)"; + text += id; + text += R"(}],"id":)"; + text += id; + text += "}"; + } + else + { + text += R"({"id":)"; + text += id; + text += R"(,"v":[1.5,"s",{"k":)"; + text += id; + text += "}]}"; + } } return text + R"(],"a":{"y":2,"x":3}})"; };