diff --git a/docs/mkdocs/docs/api/basic_json/operator_ne.md b/docs/mkdocs/docs/api/basic_json/operator_ne.md index 982a06764..7b6496158 100644 --- a/docs/mkdocs/docs/api/basic_json/operator_ne.md +++ b/docs/mkdocs/docs/api/basic_json/operator_ne.md @@ -19,10 +19,8 @@ class basic_json { }; ``` -1. Compares two JSON values for inequality according to the following rules: - - The comparison always yields `#!cpp false` if (1) either operand is discarded, or (2) either operand is `NaN` and - the other operand is either `NaN` or any other number. - - Otherwise, returns the result of `#!cpp !(lhs == rhs)` (until C++20) or `#!cpp !(*this == rhs)` (since C++20). +1. Compares two JSON values for inequality. Returns `#!cpp !(lhs == rhs)` (until C++20) or `#!cpp !(*this == rhs)` (since C++20). + - This means the comparison is simply the logical negation of `operator==`, including for special values like `NaN` and `discarded`. 2. Compares a JSON value and a scalar or a scalar and a JSON value for inequality by converting the scalar to a JSON value and comparing both JSON values according to 1. @@ -54,13 +52,12 @@ Linear. ## Notes -!!! note "Comparing `NaN`" +!!! note "Comparing `NaN` and `discarded`" - `NaN` values are unordered within the domain of numbers. - The following comparisons all yield `#!cpp false`: - 1. Comparing a `NaN` with itself. - 2. Comparing a `NaN` with another `NaN`. - 3. Comparing a `NaN` and any other number. + Since `operator!=` is defined as `!(a == b)`, the behavior for special values follows that of `operator==`: + + - For `NaN` values: `NaN == NaN` yields `#!cpp false`, so `NaN != NaN` yields `#!cpp true`. + - For `discarded` values: `discarded == x` yields `#!cpp false` for any `x`, so `discarded != x` yields `#!cpp true`. ## Examples @@ -94,5 +91,5 @@ Linear. ## Version history -1. Added in version 1.0.0. Added C++20 member functions in version 3.11.0. -2. Added in version 1.0.0. Added C++20 member functions in version 3.11.0. +1. Added in version 1.0.0. Added C++20 member functions in version 3.11.0. Changed in version 3.12.x to remove special-casing for `NaN` and `discarded` values; `operator!=` now consistently means `!(a == b)`. +2. Added in version 1.0.0. Added C++20 member functions in version 3.11.0. Changed in version 3.12.x to remove special-casing for `NaN` and `discarded` values; `operator!=` now consistently means `!(a == b)`. diff --git a/include/nlohmann/json.hpp b/include/nlohmann/json.hpp index e3bd3ea0b..3d9fe8f5e 100644 --- a/include/nlohmann/json.hpp +++ b/include/nlohmann/json.hpp @@ -3776,17 +3776,6 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec return *this == basic_json(rhs); } - /// @brief comparison: not equal - /// @sa https://json.nlohmann.me/api/basic_json/operator_ne/ - bool operator!=(const_reference rhs) const noexcept - { - if (compares_unordered(rhs, true)) - { - return false; - } - return !operator==(rhs); - } - /// @brief comparison: 3-way /// @sa https://json.nlohmann.me/api/basic_json/operator_spaceship/ std::partial_ordering operator<=>(const_reference rhs) const noexcept // *NOPAD* @@ -3892,10 +3881,6 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec /// @sa https://json.nlohmann.me/api/basic_json/operator_ne/ friend bool operator!=(const_reference lhs, const_reference rhs) noexcept { - if (compares_unordered(lhs, rhs, true)) - { - return false; - } return !(lhs == rhs); } diff --git a/single_include/nlohmann/json.hpp b/single_include/nlohmann/json.hpp index a63e3326b..bfd6c4016 100644 --- a/single_include/nlohmann/json.hpp +++ b/single_include/nlohmann/json.hpp @@ -24622,17 +24622,6 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec return *this == basic_json(rhs); } - /// @brief comparison: not equal - /// @sa https://json.nlohmann.me/api/basic_json/operator_ne/ - bool operator!=(const_reference rhs) const noexcept - { - if (compares_unordered(rhs, true)) - { - return false; - } - return !operator==(rhs); - } - /// @brief comparison: 3-way /// @sa https://json.nlohmann.me/api/basic_json/operator_spaceship/ std::partial_ordering operator<=>(const_reference rhs) const noexcept // *NOPAD* @@ -24738,10 +24727,6 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec /// @sa https://json.nlohmann.me/api/basic_json/operator_ne/ friend bool operator!=(const_reference lhs, const_reference rhs) noexcept { - if (compares_unordered(lhs, rhs, true)) - { - return false; - } return !(lhs == rhs); } diff --git a/tests/src/unit-comparison.cpp b/tests/src/unit-comparison.cpp index befaa0404..d9df1a3a6 100644 --- a/tests/src/unit-comparison.cpp +++ b/tests/src/unit-comparison.cpp @@ -369,6 +369,7 @@ TEST_CASE("lexicographical comparison operators") SECTION("comparison: not equal") { // check that two values compare unequal as expected + // operator!= now means exactly !(a==b) without special cases for NaN/discarded for (size_t i = 0; i < j_values.size(); ++i) { for (size_t j = 0; j < j_values.size(); ++j) @@ -376,25 +377,12 @@ TEST_CASE("lexicographical comparison operators") CAPTURE(i) CAPTURE(j) - if (json::compares_unordered(j_values[i], j_values[j], true)) - { - // if two values compare unordered, - // check that the boolean comparison result is always false - CHECK_FALSE(j_values[i] != j_values[j]); - } - else - { - // otherwise, check that they compare according to their definition - // as the inverse of equal - CHECK((j_values[i] != j_values[j]) == !(j_values[i] == j_values[j])); - } + CHECK((j_values[i] != j_values[j]) == !(j_values[i] == j_values[j])); } } // compare with null pointer const json j_null; - CHECK((j_null != nullptr) == false); - CHECK((nullptr != j_null) == false); CHECK((j_null != nullptr) == !(j_null == nullptr)); CHECK((nullptr != j_null) == !(nullptr == j_null)); } @@ -594,3 +582,34 @@ TEST_CASE("lexicographical comparison operators") } #endif } + +#if JSON_HAS_THREE_WAY_COMPARISON +// JSON_HAS_CPP_20 (do not remove; see note at top of file) + +TEST_CASE("regression #3868 - heterogeneous comparisons compile under C++20 (P2468R2)") +{ + // Issue #3868: operator!= was preventing compiler from synthesizing reversed + // operator== candidates under C++20's P2468R2 rewritten candidate rules. + // Verify that heterogeneous comparisons now work. + + SECTION("string vs json") + { + std::string s = "string"; + json j = "string"; + CHECK(s == j); + CHECK(j == s); + CHECK_FALSE(s != j); + CHECK_FALSE(j != s); + } + + SECTION("other heterogeneous types") + { + int i = 42; + json j = 42; + CHECK(i == j); + CHECK(j == i); + CHECK_FALSE(i != j); + CHECK_FALSE(j != i); + } +} +#endif diff --git a/tests/src/unit-constructor1.cpp b/tests/src/unit-constructor1.cpp index 9917cd358..631d1a212 100644 --- a/tests/src/unit-constructor1.cpp +++ b/tests/src/unit-constructor1.cpp @@ -278,7 +278,7 @@ TEST_CASE("constructors") const auto t = j.get>(); CHECK(std::get<0>(t) == j[0]); CHECK(std::get<1>(t) == j[1]); - // CHECK(std::get<2>(t) == j[2]); // commented out due to CI issue, see https://github.com/nlohmann/json/pull/3985 and https://github.com/nlohmann/json/issues/4025 + CHECK(std::get<2>(t) == j[2]); } SECTION("std::tuple tie") diff --git a/tests/src/unit-iterators2.cpp b/tests/src/unit-iterators2.cpp index ee9d15c6f..6794de854 100644 --- a/tests/src/unit-iterators2.cpp +++ b/tests/src/unit-iterators2.cpp @@ -942,7 +942,7 @@ TEST_CASE("iterators 2") json j_expected{5, 4, 3, 2, 1}; auto reversed = j | std::views::reverse; - CHECK(std::ranges::equal(reversed, j_expected)); + CHECK(reversed == j_expected); } SECTION("transform")