From 9c834ea0c45992ed5ffe535b91f7581057cea0c3 Mon Sep 17 00:00:00 2001 From: Florian Albrechtskirchinger Date: Sun, 10 Apr 2022 12:54:03 +0200 Subject: [PATCH] Add C++20 3-way comparison operator and fix broken comparisons Fixes #3207. Fixes #3409. --- cmake/test.cmake | 17 +- .../docs/api/basic_json/operator_spaceship.md | 5 + doc/mkdocs/mkdocs.yml | 1 + include/nlohmann/detail/macro_scope.hpp | 22 +- include/nlohmann/detail/macro_unscope.hpp | 2 + include/nlohmann/detail/value_t.hpp | 29 +- include/nlohmann/json.hpp | 362 +++++++---- single_include/nlohmann/json.hpp | 570 +++++++++++------- test/CMakeLists.txt | 9 + test/src/unit-class_parser.cpp | 8 +- test/src/unit-comparison.cpp | 511 ++++++++++++---- 11 files changed, 1082 insertions(+), 454 deletions(-) create mode 100644 doc/mkdocs/docs/api/basic_json/operator_spaceship.md diff --git a/cmake/test.cmake b/cmake/test.cmake index 1d146646b8..89b7c6ec75 100644 --- a/cmake/test.cmake +++ b/cmake/test.cmake @@ -156,6 +156,7 @@ endfunction() ############################################################################# # json_test_add_test_for( # +# [NAME ] # MAIN
# [CXX_STANDARDS ...] [FORCE]) # @@ -165,6 +166,7 @@ endfunction() # # if C++ standard is supported by the compiler and the # source file contains JSON_HAS_CPP_. +# Use NAME to override the filename-derived test name. # Use FORCE to create the test regardless of the file containing # JSON_HAS_CPP_. # Test targets are linked against
. @@ -172,15 +174,22 @@ endfunction() ############################################################################# function(json_test_add_test_for file) - cmake_parse_arguments(args "FORCE" "MAIN" "CXX_STANDARDS" ${ARGN}) - - get_filename_component(file_basename ${file} NAME_WE) - string(REGEX REPLACE "unit-([^$]+)" "test-\\1" test_name ${file_basename}) + cmake_parse_arguments(args "FORCE" "MAIN;NAME" "CXX_STANDARDS" ${ARGN}) if("${args_MAIN}" STREQUAL "") message(FATAL_ERROR "Required argument MAIN
missing.") endif() + if("${args_NAME}" STREQUAL "") + get_filename_component(file_basename ${file} NAME_WE) + string(REGEX REPLACE "unit-([^$]+)" "test-\\1" test_name ${file_basename}) + else() + set(test_name ${args_NAME}) + if(NOT test_name MATCHES "test-[^$]+") + message(FATAL_ERROR "Test name must start with 'test-'.") + endif() + endif() + if("${args_CXX_STANDARDS}" STREQUAL "") set(args_CXX_STANDARDS 11) endif() diff --git a/doc/mkdocs/docs/api/basic_json/operator_spaceship.md b/doc/mkdocs/docs/api/basic_json/operator_spaceship.md new file mode 100644 index 0000000000..72c5b8232c --- /dev/null +++ b/doc/mkdocs/docs/api/basic_json/operator_spaceship.md @@ -0,0 +1,5 @@ +# nlohmann::basic_json::operator<=> + +## Version history + +- Added in version 3.11.0. diff --git a/doc/mkdocs/mkdocs.yml b/doc/mkdocs/mkdocs.yml index 59f0ae700b..1995edc105 100644 --- a/doc/mkdocs/mkdocs.yml +++ b/doc/mkdocs/mkdocs.yml @@ -151,6 +151,7 @@ nav: - 'operator value_t': api/basic_json/operator_value_t.md - 'operator[]': api/basic_json/operator[].md - 'operator=': api/basic_json/operator=.md + - 'operator<=>': api/basic_json/operator_spaceship.md - 'operator==': api/basic_json/operator_eq.md - 'operator!=': api/basic_json/operator_ne.md - 'operator<': api/basic_json/operator_lt.md diff --git a/include/nlohmann/detail/macro_scope.hpp b/include/nlohmann/detail/macro_scope.hpp index f636b908a4..d1f0df0dfc 100644 --- a/include/nlohmann/detail/macro_scope.hpp +++ b/include/nlohmann/detail/macro_scope.hpp @@ -98,14 +98,22 @@ #endif #ifndef JSON_HAS_THREE_WAY_COMPARISON - #if defined(__cpp_lib_three_way_comparison) && __cpp_lib_three_way_comparison >= 201907L \ - && defined(__cpp_impl_three_way_comparison)&& __cpp_impl_three_way_comparison >= 201907L - #define JSON_HAS_THREE_WAY_COMPARISON 1 - #else - #define JSON_HAS_THREE_WAY_COMPARISON 0 + #if defined(__cpp_impl_three_way_comparison) && __cpp_impl_three_way_comparison >= 201907L + #if !defined(__cpp_lib_three_way_comparison) && defined(__has_include) + #if __has_include() + #include + #endif + #endif + #if defined(__cpp_lib_three_way_comparison) && __cpp_lib_three_way_comparison >= 201907L + #define JSON_HAS_THREE_WAY_COMPARISON 1 + #endif #endif #endif +#ifndef JSON_HAS_THREE_WAY_COMPARISON + #define JSON_HAS_THREE_WAY_COMPARISON 0 +#endif + // disable documentation warnings on clang #if defined(__clang__) #pragma clang diagnostic push @@ -423,3 +431,7 @@ #ifndef JSON_DIAGNOSTICS #define JSON_DIAGNOSTICS 0 #endif + +#ifndef JSON_USE_LEGACY_DISCARDED_VALUE_COMPARISON + #define JSON_USE_LEGACY_DISCARDED_VALUE_COMPARISON 0 +#endif diff --git a/include/nlohmann/detail/macro_unscope.hpp b/include/nlohmann/detail/macro_unscope.hpp index 377d3f11d4..c7291bde57 100644 --- a/include/nlohmann/detail/macro_unscope.hpp +++ b/include/nlohmann/detail/macro_unscope.hpp @@ -25,6 +25,8 @@ #undef JSON_HAS_FILESYSTEM #undef JSON_HAS_EXPERIMENTAL_FILESYSTEM #undef JSON_HAS_THREE_WAY_COMPARISON + #undef JSON_HAS_RANGES + #undef JSON_USE_LEGACY_DISCARDED_VALUE_COMPARISON #endif #include diff --git a/include/nlohmann/detail/value_t.hpp b/include/nlohmann/detail/value_t.hpp index a98c4355a0..afc2f4d4cc 100644 --- a/include/nlohmann/detail/value_t.hpp +++ b/include/nlohmann/detail/value_t.hpp @@ -5,6 +5,11 @@ #include // uint8_t #include // string +#include +#if JSON_HAS_THREE_WAY_COMPARISON + #include // partial_ordering +#endif + namespace nlohmann { namespace detail @@ -64,7 +69,11 @@ Returns an ordering that is similar to Python: @since version 1.0.0 */ -inline bool operator<(const value_t lhs, const value_t rhs) noexcept +#if JSON_HAS_THREE_WAY_COMPARISON + inline std::partial_ordering operator<=>(const value_t lhs, const value_t rhs) noexcept // *NOPAD* +#else + inline bool operator<(const value_t lhs, const value_t rhs) noexcept +#endif { static constexpr std::array order = {{ 0 /* null */, 3 /* object */, 4 /* array */, 5 /* string */, @@ -75,7 +84,25 @@ inline bool operator<(const value_t lhs, const value_t rhs) noexcept const auto l_index = static_cast(lhs); const auto r_index = static_cast(rhs); +#if JSON_HAS_THREE_WAY_COMPARISON + if (l_index < order.size() && r_index < order.size()) + { + return order[l_index] <=> order[r_index]; // *NOPAD* + } + return std::partial_ordering::unordered; +#else return l_index < order.size() && r_index < order.size() && order[l_index] < order[r_index]; +#endif +} + +// GCC disagrees with Clang, MSVC, and ICC; selects builtin operator over user-defined +// (see GCC bug https://gcc.gnu.org/bugzilla/show_bug.cgi?id=105200) +// ICC may produce incorrect result; test and add if needed +#if JSON_HAS_THREE_WAY_COMPARISON && defined(__GNUC__) +inline bool operator<(const value_t lhs, const value_t rhs) noexcept +{ + return std::is_lt(lhs <=> rhs); // *NOPAD* } +#endif } // namespace detail } // namespace nlohmann diff --git a/include/nlohmann/json.hpp b/include/nlohmann/json.hpp index 0a18f16345..01cef93c67 100644 --- a/include/nlohmann/json.hpp +++ b/include/nlohmann/json.hpp @@ -1899,7 +1899,6 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec detail::negation>, detail::negation>, detail::negation>>, - #if defined(JSON_HAS_CPP_17) && (defined(__GNUC__) || (defined(_MSC_VER) && _MSC_VER >= 1910 && _MSC_VER <= 1914)) detail::negation>, #endif @@ -3348,7 +3347,6 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec /// @} - public: ////////////////////////////////////////// // lexicographical comparison operators // ////////////////////////////////////////// @@ -3356,79 +3354,231 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec /// @name lexicographical comparison operators /// @{ + // note parentheses around operands are necessary; see + // https://github.com/nlohmann/json/issues/1530 +#define JSON_IMPLEMENT_OPERATOR(op, null_result, unordered_result, default_result) \ + const auto lhs_type = lhs.type(); \ + const auto rhs_type = rhs.type(); \ + \ + if (lhs_type == rhs_type) /* NOLINT(readability/braces) */ \ + { \ + switch (lhs_type) \ + { \ + case value_t::array: \ + return (*lhs.m_value.array) op (*rhs.m_value.array); \ + \ + case value_t::object: \ + return (*lhs.m_value.object) op (*rhs.m_value.object); \ + \ + case value_t::null: \ + return (null_result); \ + \ + case value_t::string: \ + return (*lhs.m_value.string) op (*rhs.m_value.string); \ + \ + case value_t::boolean: \ + return (lhs.m_value.boolean) op (rhs.m_value.boolean); \ + \ + case value_t::number_integer: \ + return (lhs.m_value.number_integer) op (rhs.m_value.number_integer); \ + \ + case value_t::number_unsigned: \ + return (lhs.m_value.number_unsigned) op (rhs.m_value.number_unsigned); \ + \ + case value_t::number_float: \ + return (lhs.m_value.number_float) op (rhs.m_value.number_float); \ + \ + case value_t::binary: \ + return (*lhs.m_value.binary) op (*rhs.m_value.binary); \ + \ + case value_t::discarded: \ + default: \ + return (unordered_result); \ + } \ + } \ + else if (lhs_type == value_t::number_integer && rhs_type == value_t::number_float) \ + { \ + return static_cast(lhs.m_value.number_integer) op rhs.m_value.number_float; \ + } \ + else if (lhs_type == value_t::number_float && rhs_type == value_t::number_integer) \ + { \ + return lhs.m_value.number_float op static_cast(rhs.m_value.number_integer); \ + } \ + else if (lhs_type == value_t::number_unsigned && rhs_type == value_t::number_float) \ + { \ + return static_cast(lhs.m_value.number_unsigned) op rhs.m_value.number_float; \ + } \ + else if (lhs_type == value_t::number_float && rhs_type == value_t::number_unsigned) \ + { \ + return lhs.m_value.number_float op static_cast(rhs.m_value.number_unsigned); \ + } \ + else if (lhs_type == value_t::number_unsigned && rhs_type == value_t::number_integer) \ + { \ + return static_cast(lhs.m_value.number_unsigned) op rhs.m_value.number_integer; \ + } \ + else if (lhs_type == value_t::number_integer && rhs_type == value_t::number_unsigned) \ + { \ + return lhs.m_value.number_integer op static_cast(rhs.m_value.number_unsigned); \ + } \ + else if(compares_unordered(lhs, rhs))\ + {\ + return (unordered_result);\ + }\ + \ + return (default_result); + + JSON_PRIVATE_UNLESS_TESTED: + // returns true if: + // - any operand is NaN and the other operand is of number type + // - any operand is discarded + // in legacy mode, discarded values are considered ordered if + // an operation is computed as an odd number of inverses of others + static bool compares_unordered(const_reference lhs, const_reference rhs, bool inverse = false) noexcept + { + if ((lhs.is_number_float() && std::isnan(lhs.m_value.number_float) && rhs.is_number()) + || (rhs.is_number_float() && std::isnan(rhs.m_value.number_float) && lhs.is_number())) + { + return true; + } +#if JSON_USE_LEGACY_DISCARDED_VALUE_COMPARISON + return (lhs.is_discarded() || rhs.is_discarded()) && !inverse; +#else + static_cast(inverse); + return lhs.is_discarded() || rhs.is_discarded(); +#endif + } + + private: + bool compares_unordered(const_reference rhs, bool inverse = false) const noexcept + { + return compares_unordered(*this, rhs, inverse); + } + + public: +#if JSON_HAS_THREE_WAY_COMPARISON /// @brief comparison: equal /// @sa https://json.nlohmann.me/api/basic_json/operator_eq/ - friend bool operator==(const_reference lhs, const_reference rhs) noexcept + bool operator==(const_reference rhs) const noexcept { #ifdef __GNUC__ #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wfloat-equal" #endif - const auto lhs_type = lhs.type(); - const auto rhs_type = rhs.type(); - - if (lhs_type == rhs_type) - { - switch (lhs_type) - { - case value_t::array: - return *lhs.m_value.array == *rhs.m_value.array; - - case value_t::object: - return *lhs.m_value.object == *rhs.m_value.object; - - case value_t::null: - return true; + const_reference lhs = *this; + JSON_IMPLEMENT_OPERATOR( ==, true, false, false) +#ifdef __GNUC__ +#pragma GCC diagnostic pop +#endif + } - case value_t::string: - return *lhs.m_value.string == *rhs.m_value.string; + /// @brief comparison: equal + /// @sa https://json.nlohmann.me/api/basic_json/operator_eq/ + template + requires std::is_scalar_v + bool operator==(ScalarType rhs) const noexcept + { + return *this == basic_json(rhs); + } - case value_t::boolean: - return lhs.m_value.boolean == rhs.m_value.boolean; + /// @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); + } - case value_t::number_integer: - return lhs.m_value.number_integer == rhs.m_value.number_integer; + /// @brief comparison: not equal + /// @sa https://json.nlohmann.me/api/basic_json/operator_ne/ + template + requires std::is_scalar_v + bool operator!=(ScalarType rhs) const noexcept + { + return *this != basic_json(rhs); + } - case value_t::number_unsigned: - return lhs.m_value.number_unsigned == rhs.m_value.number_unsigned; + /// @brief comparison: 3-way + /// @sa https://json.nlohmann.me/api/basic_json/operator_spaceship/ + std::partial_ordering operator<=>(const_reference rhs) const noexcept // *NOPAD* + { + const_reference lhs = *this; + // default_result is used if we cannot compare values. In that case, + // we compare types. + JSON_IMPLEMENT_OPERATOR(<=>, // *NOPAD* + std::partial_ordering::equivalent, + std::partial_ordering::unordered, + lhs_type <=> rhs_type) // *NOPAD* + } - case value_t::number_float: - return lhs.m_value.number_float == rhs.m_value.number_float; + /// @brief comparison: 3-way + /// @sa https://json.nlohmann.me/api/basic_json/operator_spaceship/ + template + requires std::is_scalar_v + std::partial_ordering operator<=>(ScalarType rhs) const noexcept // *NOPAD* + { + return *this <=> basic_json(rhs); // *NOPAD* + } - case value_t::binary: - return *lhs.m_value.binary == *rhs.m_value.binary; +#if JSON_USE_LEGACY_DISCARDED_VALUE_COMPARISON + // all operators that are computed as the inverse of another operation, unless + // that operation is itself computed as the inverse of yet another operation, + // need to be overloaded to emulate the legacy comparison behavior - case value_t::discarded: - default: - return false; - } - } - else if (lhs_type == value_t::number_integer && rhs_type == value_t::number_float) - { - return static_cast(lhs.m_value.number_integer) == rhs.m_value.number_float; - } - else if (lhs_type == value_t::number_float && rhs_type == value_t::number_integer) - { - return lhs.m_value.number_float == static_cast(rhs.m_value.number_integer); - } - else if (lhs_type == value_t::number_unsigned && rhs_type == value_t::number_float) - { - return static_cast(lhs.m_value.number_unsigned) == rhs.m_value.number_float; - } - else if (lhs_type == value_t::number_float && rhs_type == value_t::number_unsigned) - { - return lhs.m_value.number_float == static_cast(rhs.m_value.number_unsigned); - } - else if (lhs_type == value_t::number_unsigned && rhs_type == value_t::number_integer) + /// @brief comparison: less than or equal + /// @sa https://json.nlohmann.me/api/basic_json/operator_le/ + JSON_HEDLEY_DEPRECATED_FOR(3.11.0, undef JSON_USE_LEGACY_DISCARDED_VALUE_COMPARISON) + bool operator<=(const_reference rhs) const noexcept + { + if (compares_unordered(rhs, true)) { - return static_cast(lhs.m_value.number_unsigned) == rhs.m_value.number_integer; + return false; } - else if (lhs_type == value_t::number_integer && rhs_type == value_t::number_unsigned) + return !(rhs < *this); + } + + /// @brief comparison: less than or equal + /// @sa https://json.nlohmann.me/api/basic_json/operator_le/ + template + requires std::is_scalar_v + bool operator<=(ScalarType rhs) const noexcept + { + return *this <= basic_json(rhs); + } + + /// @brief comparison: greater than or equal + /// @sa https://json.nlohmann.me/api/basic_json/operator_ge/ + JSON_HEDLEY_DEPRECATED_FOR(3.11.0, undef JSON_USE_LEGACY_DISCARDED_VALUE_COMPARISON) + bool operator>=(const_reference rhs) const noexcept + { + if (compares_unordered(rhs, true)) { - return lhs.m_value.number_integer == static_cast(rhs.m_value.number_unsigned); + return false; } + return !(*this < rhs); + } - return false; + /// @brief comparison: greater than or equal + /// @sa https://json.nlohmann.me/api/basic_json/operator_ge/ + template + requires std::is_scalar_v + bool operator>=(ScalarType rhs) const noexcept + { + return *this >= basic_json(rhs); + } +#endif +#else + /// @brief comparison: equal + /// @sa https://json.nlohmann.me/api/basic_json/operator_eq/ + friend bool operator==(const_reference lhs, const_reference rhs) noexcept + { +#ifdef __GNUC__ +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wfloat-equal" +#endif + JSON_IMPLEMENT_OPERATOR( ==, true, false, false) #ifdef __GNUC__ #pragma GCC diagnostic pop #endif @@ -3456,6 +3606,10 @@ 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); } @@ -3481,76 +3635,10 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec /// @sa https://json.nlohmann.me/api/basic_json/operator_lt/ friend bool operator<(const_reference lhs, const_reference rhs) noexcept { - const auto lhs_type = lhs.type(); - const auto rhs_type = rhs.type(); - - if (lhs_type == rhs_type) - { - switch (lhs_type) - { - case value_t::array: - // note parentheses are necessary, see - // https://github.com/nlohmann/json/issues/1530 - return (*lhs.m_value.array) < (*rhs.m_value.array); - - case value_t::object: - return (*lhs.m_value.object) < (*rhs.m_value.object); - - case value_t::null: - return false; - - case value_t::string: - return (*lhs.m_value.string) < (*rhs.m_value.string); - - case value_t::boolean: - return (lhs.m_value.boolean) < (rhs.m_value.boolean); - - case value_t::number_integer: - return (lhs.m_value.number_integer) < (rhs.m_value.number_integer); - - case value_t::number_unsigned: - return (lhs.m_value.number_unsigned) < (rhs.m_value.number_unsigned); - - case value_t::number_float: - return (lhs.m_value.number_float) < (rhs.m_value.number_float); - - case value_t::binary: - return (*lhs.m_value.binary) < (*rhs.m_value.binary); - - case value_t::discarded: - default: - return false; - } - } - else if (lhs_type == value_t::number_integer && rhs_type == value_t::number_float) - { - return static_cast(lhs.m_value.number_integer) < rhs.m_value.number_float; - } - else if (lhs_type == value_t::number_float && rhs_type == value_t::number_integer) - { - return lhs.m_value.number_float < static_cast(rhs.m_value.number_integer); - } - else if (lhs_type == value_t::number_unsigned && rhs_type == value_t::number_float) - { - return static_cast(lhs.m_value.number_unsigned) < rhs.m_value.number_float; - } - else if (lhs_type == value_t::number_float && rhs_type == value_t::number_unsigned) - { - return lhs.m_value.number_float < static_cast(rhs.m_value.number_unsigned); - } - else if (lhs_type == value_t::number_integer && rhs_type == value_t::number_unsigned) - { - return lhs.m_value.number_integer < static_cast(rhs.m_value.number_unsigned); - } - else if (lhs_type == value_t::number_unsigned && rhs_type == value_t::number_integer) - { - return static_cast(lhs.m_value.number_unsigned) < rhs.m_value.number_integer; - } - - // We only reach this line if we cannot compare values. In that case, + // default_result is used if we cannot compare values. In that case, // we compare types. Note we have to call the operator explicitly, // because MSVC has problems otherwise. - return operator<(lhs_type, rhs_type); + JSON_IMPLEMENT_OPERATOR( <, false, false, operator<(lhs_type, rhs_type)) } /// @brief comparison: less than @@ -3575,6 +3663,10 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec /// @sa https://json.nlohmann.me/api/basic_json/operator_le/ friend bool operator<=(const_reference lhs, const_reference rhs) noexcept { + if (compares_unordered(lhs, rhs, true)) + { + return false; + } return !(rhs < lhs); } @@ -3600,6 +3692,11 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec /// @sa https://json.nlohmann.me/api/basic_json/operator_gt/ friend bool operator>(const_reference lhs, const_reference rhs) noexcept { + // double inverse + if (compares_unordered(lhs, rhs)) + { + return false; + } return !(lhs <= rhs); } @@ -3625,6 +3722,10 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec /// @sa https://json.nlohmann.me/api/basic_json/operator_ge/ friend bool operator>=(const_reference lhs, const_reference rhs) noexcept { + if (compares_unordered(lhs, rhs, true)) + { + return false; + } return !(lhs < rhs); } @@ -3645,6 +3746,9 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec { return basic_json(lhs) >= rhs; } +#endif + +#undef JSON_IMPLEMENT_OPERATOR /// @} @@ -4864,10 +4968,14 @@ struct less< ::nlohmann::detail::value_t> // do not remove the space after '<', @brief compare two value_t enum values @since version 3.0.0 */ - bool operator()(nlohmann::detail::value_t lhs, - nlohmann::detail::value_t rhs) const noexcept + bool operator()(::nlohmann::detail::value_t lhs, + ::nlohmann::detail::value_t rhs) const noexcept { - return nlohmann::detail::operator<(lhs, rhs); +#if JSON_HAS_THREE_WAY_COMPARISON + return std::is_lt(lhs <=> rhs); // *NOPAD* +#else + return ::nlohmann::detail::operator<(lhs, rhs); +#endif } }; diff --git a/single_include/nlohmann/json.hpp b/single_include/nlohmann/json.hpp index d596180c6a..d6acd2d470 100644 --- a/single_include/nlohmann/json.hpp +++ b/single_include/nlohmann/json.hpp @@ -103,84 +103,6 @@ SOFTWARE. #include // uint8_t #include // string -namespace nlohmann -{ -namespace detail -{ -/////////////////////////// -// JSON type enumeration // -/////////////////////////// - -/*! -@brief the JSON type enumeration - -This enumeration collects the different JSON types. It is internally used to -distinguish the stored values, and the functions @ref basic_json::is_null(), -@ref basic_json::is_object(), @ref basic_json::is_array(), -@ref basic_json::is_string(), @ref basic_json::is_boolean(), -@ref basic_json::is_number() (with @ref basic_json::is_number_integer(), -@ref basic_json::is_number_unsigned(), and @ref basic_json::is_number_float()), -@ref basic_json::is_discarded(), @ref basic_json::is_primitive(), and -@ref basic_json::is_structured() rely on it. - -@note There are three enumeration entries (number_integer, number_unsigned, and -number_float), because the library distinguishes these three types for numbers: -@ref basic_json::number_unsigned_t is used for unsigned integers, -@ref basic_json::number_integer_t is used for signed integers, and -@ref basic_json::number_float_t is used for floating-point numbers or to -approximate integers which do not fit in the limits of their respective type. - -@sa see @ref basic_json::basic_json(const value_t value_type) -- create a JSON -value with the default value for a given type - -@since version 1.0.0 -*/ -enum class value_t : std::uint8_t -{ - null, ///< null value - object, ///< object (unordered set of name/value pairs) - array, ///< array (ordered collection of values) - string, ///< string value - boolean, ///< boolean value - number_integer, ///< number value (signed integer) - number_unsigned, ///< number value (unsigned integer) - number_float, ///< number value (floating-point) - binary, ///< binary array (ordered collection of bytes) - discarded ///< discarded by the parser callback function -}; - -/*! -@brief comparison operator for JSON types - -Returns an ordering that is similar to Python: -- order: null < boolean < number < object < array < string < binary -- furthermore, each type is not smaller than itself -- discarded values are not comparable -- binary is represented as a b"" string in python and directly comparable to a - string; however, making a binary array directly comparable with a string would - be surprising behavior in a JSON file. - -@since version 1.0.0 -*/ -inline bool operator<(const value_t lhs, const value_t rhs) noexcept -{ - static constexpr std::array order = {{ - 0 /* null */, 3 /* object */, 4 /* array */, 5 /* string */, - 1 /* boolean */, 2 /* integer */, 2 /* unsigned */, 2 /* float */, - 6 /* binary */ - } - }; - - const auto l_index = static_cast(lhs); - const auto r_index = static_cast(rhs); - return l_index < order.size() && r_index < order.size() && order[l_index] < order[r_index]; -} -} // namespace detail -} // namespace nlohmann - -// #include - - // #include @@ -2403,14 +2325,22 @@ using is_detected_convertible = #endif #ifndef JSON_HAS_THREE_WAY_COMPARISON - #if defined(__cpp_lib_three_way_comparison) && __cpp_lib_three_way_comparison >= 201907L \ - && defined(__cpp_impl_three_way_comparison)&& __cpp_impl_three_way_comparison >= 201907L - #define JSON_HAS_THREE_WAY_COMPARISON 1 - #else - #define JSON_HAS_THREE_WAY_COMPARISON 0 + #if defined(__cpp_impl_three_way_comparison) && __cpp_impl_three_way_comparison >= 201907L + #if !defined(__cpp_lib_three_way_comparison) && defined(__has_include) + #if __has_include() + #include + #endif + #endif + #if defined(__cpp_lib_three_way_comparison) && __cpp_lib_three_way_comparison >= 201907L + #define JSON_HAS_THREE_WAY_COMPARISON 1 + #endif #endif #endif +#ifndef JSON_HAS_THREE_WAY_COMPARISON + #define JSON_HAS_THREE_WAY_COMPARISON 0 +#endif + // disable documentation warnings on clang #if defined(__clang__) #pragma clang diagnostic push @@ -2729,6 +2659,116 @@ using is_detected_convertible = #define JSON_DIAGNOSTICS 0 #endif +#ifndef JSON_USE_LEGACY_DISCARDED_VALUE_COMPARISON + #define JSON_USE_LEGACY_DISCARDED_VALUE_COMPARISON 0 +#endif + +#if JSON_HAS_THREE_WAY_COMPARISON + #include // partial_ordering +#endif + +namespace nlohmann +{ +namespace detail +{ +/////////////////////////// +// JSON type enumeration // +/////////////////////////// + +/*! +@brief the JSON type enumeration + +This enumeration collects the different JSON types. It is internally used to +distinguish the stored values, and the functions @ref basic_json::is_null(), +@ref basic_json::is_object(), @ref basic_json::is_array(), +@ref basic_json::is_string(), @ref basic_json::is_boolean(), +@ref basic_json::is_number() (with @ref basic_json::is_number_integer(), +@ref basic_json::is_number_unsigned(), and @ref basic_json::is_number_float()), +@ref basic_json::is_discarded(), @ref basic_json::is_primitive(), and +@ref basic_json::is_structured() rely on it. + +@note There are three enumeration entries (number_integer, number_unsigned, and +number_float), because the library distinguishes these three types for numbers: +@ref basic_json::number_unsigned_t is used for unsigned integers, +@ref basic_json::number_integer_t is used for signed integers, and +@ref basic_json::number_float_t is used for floating-point numbers or to +approximate integers which do not fit in the limits of their respective type. + +@sa see @ref basic_json::basic_json(const value_t value_type) -- create a JSON +value with the default value for a given type + +@since version 1.0.0 +*/ +enum class value_t : std::uint8_t +{ + null, ///< null value + object, ///< object (unordered set of name/value pairs) + array, ///< array (ordered collection of values) + string, ///< string value + boolean, ///< boolean value + number_integer, ///< number value (signed integer) + number_unsigned, ///< number value (unsigned integer) + number_float, ///< number value (floating-point) + binary, ///< binary array (ordered collection of bytes) + discarded ///< discarded by the parser callback function +}; + +/*! +@brief comparison operator for JSON types + +Returns an ordering that is similar to Python: +- order: null < boolean < number < object < array < string < binary +- furthermore, each type is not smaller than itself +- discarded values are not comparable +- binary is represented as a b"" string in python and directly comparable to a + string; however, making a binary array directly comparable with a string would + be surprising behavior in a JSON file. + +@since version 1.0.0 +*/ +#if JSON_HAS_THREE_WAY_COMPARISON + inline std::partial_ordering operator<=>(const value_t lhs, const value_t rhs) noexcept // *NOPAD* +#else + inline bool operator<(const value_t lhs, const value_t rhs) noexcept +#endif +{ + static constexpr std::array order = {{ + 0 /* null */, 3 /* object */, 4 /* array */, 5 /* string */, + 1 /* boolean */, 2 /* integer */, 2 /* unsigned */, 2 /* float */, + 6 /* binary */ + } + }; + + const auto l_index = static_cast(lhs); + const auto r_index = static_cast(rhs); +#if JSON_HAS_THREE_WAY_COMPARISON + if (l_index < order.size() && r_index < order.size()) + { + return order[l_index] <=> order[r_index]; // *NOPAD* + } + return std::partial_ordering::unordered; +#else + return l_index < order.size() && r_index < order.size() && order[l_index] < order[r_index]; +#endif +} + +// GCC disagrees with Clang, MSVC, and ICC; selects builtin operator over user-defined +// (see GCC bug https://gcc.gnu.org/bugzilla/show_bug.cgi?id=105200) +// ICC may produce incorrect result; test and add if needed +#if JSON_HAS_THREE_WAY_COMPARISON && defined(__GNUC__) +inline bool operator<(const value_t lhs, const value_t rhs) noexcept +{ + return std::is_lt(lhs <=> rhs); // *NOPAD* +} +#endif +} // namespace detail +} // namespace nlohmann + +// #include + + +// #include + namespace nlohmann { @@ -19870,7 +19910,6 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec detail::negation>, detail::negation>, detail::negation>>, - #if defined(JSON_HAS_CPP_17) && (defined(__GNUC__) || (defined(_MSC_VER) && _MSC_VER >= 1910 && _MSC_VER <= 1914)) detail::negation>, #endif @@ -21319,7 +21358,6 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec /// @} - public: ////////////////////////////////////////// // lexicographical comparison operators // ////////////////////////////////////////// @@ -21327,79 +21365,231 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec /// @name lexicographical comparison operators /// @{ + // note parentheses around operands are necessary; see + // https://github.com/nlohmann/json/issues/1530 +#define JSON_IMPLEMENT_OPERATOR(op, null_result, unordered_result, default_result) \ + const auto lhs_type = lhs.type(); \ + const auto rhs_type = rhs.type(); \ + \ + if (lhs_type == rhs_type) /* NOLINT(readability/braces) */ \ + { \ + switch (lhs_type) \ + { \ + case value_t::array: \ + return (*lhs.m_value.array) op (*rhs.m_value.array); \ + \ + case value_t::object: \ + return (*lhs.m_value.object) op (*rhs.m_value.object); \ + \ + case value_t::null: \ + return (null_result); \ + \ + case value_t::string: \ + return (*lhs.m_value.string) op (*rhs.m_value.string); \ + \ + case value_t::boolean: \ + return (lhs.m_value.boolean) op (rhs.m_value.boolean); \ + \ + case value_t::number_integer: \ + return (lhs.m_value.number_integer) op (rhs.m_value.number_integer); \ + \ + case value_t::number_unsigned: \ + return (lhs.m_value.number_unsigned) op (rhs.m_value.number_unsigned); \ + \ + case value_t::number_float: \ + return (lhs.m_value.number_float) op (rhs.m_value.number_float); \ + \ + case value_t::binary: \ + return (*lhs.m_value.binary) op (*rhs.m_value.binary); \ + \ + case value_t::discarded: \ + default: \ + return (unordered_result); \ + } \ + } \ + else if (lhs_type == value_t::number_integer && rhs_type == value_t::number_float) \ + { \ + return static_cast(lhs.m_value.number_integer) op rhs.m_value.number_float; \ + } \ + else if (lhs_type == value_t::number_float && rhs_type == value_t::number_integer) \ + { \ + return lhs.m_value.number_float op static_cast(rhs.m_value.number_integer); \ + } \ + else if (lhs_type == value_t::number_unsigned && rhs_type == value_t::number_float) \ + { \ + return static_cast(lhs.m_value.number_unsigned) op rhs.m_value.number_float; \ + } \ + else if (lhs_type == value_t::number_float && rhs_type == value_t::number_unsigned) \ + { \ + return lhs.m_value.number_float op static_cast(rhs.m_value.number_unsigned); \ + } \ + else if (lhs_type == value_t::number_unsigned && rhs_type == value_t::number_integer) \ + { \ + return static_cast(lhs.m_value.number_unsigned) op rhs.m_value.number_integer; \ + } \ + else if (lhs_type == value_t::number_integer && rhs_type == value_t::number_unsigned) \ + { \ + return lhs.m_value.number_integer op static_cast(rhs.m_value.number_unsigned); \ + } \ + else if(compares_unordered(lhs, rhs))\ + {\ + return (unordered_result);\ + }\ + \ + return (default_result); + + JSON_PRIVATE_UNLESS_TESTED: + // returns true if: + // - any operand is NaN and the other operand is of number type + // - any operand is discarded + // in legacy mode, discarded values are considered ordered if + // an operation is computed as an odd number of inverses of others + static bool compares_unordered(const_reference lhs, const_reference rhs, bool inverse = false) noexcept + { + if ((lhs.is_number_float() && std::isnan(lhs.m_value.number_float) && rhs.is_number()) + || (rhs.is_number_float() && std::isnan(rhs.m_value.number_float) && lhs.is_number())) + { + return true; + } +#if JSON_USE_LEGACY_DISCARDED_VALUE_COMPARISON + return (lhs.is_discarded() || rhs.is_discarded()) && !inverse; +#else + static_cast(inverse); + return lhs.is_discarded() || rhs.is_discarded(); +#endif + } + + private: + bool compares_unordered(const_reference rhs, bool inverse = false) const noexcept + { + return compares_unordered(*this, rhs, inverse); + } + + public: +#if JSON_HAS_THREE_WAY_COMPARISON /// @brief comparison: equal /// @sa https://json.nlohmann.me/api/basic_json/operator_eq/ - friend bool operator==(const_reference lhs, const_reference rhs) noexcept + bool operator==(const_reference rhs) const noexcept { #ifdef __GNUC__ #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wfloat-equal" #endif - const auto lhs_type = lhs.type(); - const auto rhs_type = rhs.type(); - - if (lhs_type == rhs_type) - { - switch (lhs_type) - { - case value_t::array: - return *lhs.m_value.array == *rhs.m_value.array; - - case value_t::object: - return *lhs.m_value.object == *rhs.m_value.object; - - case value_t::null: - return true; + const_reference lhs = *this; + JSON_IMPLEMENT_OPERATOR( ==, true, false, false) +#ifdef __GNUC__ +#pragma GCC diagnostic pop +#endif + } - case value_t::string: - return *lhs.m_value.string == *rhs.m_value.string; + /// @brief comparison: equal + /// @sa https://json.nlohmann.me/api/basic_json/operator_eq/ + template + requires std::is_scalar_v + bool operator==(ScalarType rhs) const noexcept + { + return *this == basic_json(rhs); + } - case value_t::boolean: - return lhs.m_value.boolean == rhs.m_value.boolean; + /// @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); + } - case value_t::number_integer: - return lhs.m_value.number_integer == rhs.m_value.number_integer; + /// @brief comparison: not equal + /// @sa https://json.nlohmann.me/api/basic_json/operator_ne/ + template + requires std::is_scalar_v + bool operator!=(ScalarType rhs) const noexcept + { + return *this != basic_json(rhs); + } - case value_t::number_unsigned: - return lhs.m_value.number_unsigned == rhs.m_value.number_unsigned; + /// @brief comparison: 3-way + /// @sa https://json.nlohmann.me/api/basic_json/operator_spaceship/ + std::partial_ordering operator<=>(const_reference rhs) const noexcept // *NOPAD* + { + const_reference lhs = *this; + // default_result is used if we cannot compare values. In that case, + // we compare types. + JSON_IMPLEMENT_OPERATOR(<=>, // *NOPAD* + std::partial_ordering::equivalent, + std::partial_ordering::unordered, + lhs_type <=> rhs_type) // *NOPAD* + } - case value_t::number_float: - return lhs.m_value.number_float == rhs.m_value.number_float; + /// @brief comparison: 3-way + /// @sa https://json.nlohmann.me/api/basic_json/operator_spaceship/ + template + requires std::is_scalar_v + std::partial_ordering operator<=>(ScalarType rhs) const noexcept // *NOPAD* + { + return *this <=> basic_json(rhs); // *NOPAD* + } - case value_t::binary: - return *lhs.m_value.binary == *rhs.m_value.binary; +#if JSON_USE_LEGACY_DISCARDED_VALUE_COMPARISON + // all operators that are computed as the inverse of another operation, unless + // that operation is itself computed as the inverse of yet another operation, + // need to be overloaded to emulate the legacy comparison behavior - case value_t::discarded: - default: - return false; - } - } - else if (lhs_type == value_t::number_integer && rhs_type == value_t::number_float) - { - return static_cast(lhs.m_value.number_integer) == rhs.m_value.number_float; - } - else if (lhs_type == value_t::number_float && rhs_type == value_t::number_integer) - { - return lhs.m_value.number_float == static_cast(rhs.m_value.number_integer); - } - else if (lhs_type == value_t::number_unsigned && rhs_type == value_t::number_float) - { - return static_cast(lhs.m_value.number_unsigned) == rhs.m_value.number_float; - } - else if (lhs_type == value_t::number_float && rhs_type == value_t::number_unsigned) - { - return lhs.m_value.number_float == static_cast(rhs.m_value.number_unsigned); - } - else if (lhs_type == value_t::number_unsigned && rhs_type == value_t::number_integer) + /// @brief comparison: less than or equal + /// @sa https://json.nlohmann.me/api/basic_json/operator_le/ + JSON_HEDLEY_DEPRECATED_FOR(3.11.0, undef JSON_USE_LEGACY_DISCARDED_VALUE_COMPARISON) + bool operator<=(const_reference rhs) const noexcept + { + if (compares_unordered(rhs, true)) { - return static_cast(lhs.m_value.number_unsigned) == rhs.m_value.number_integer; + return false; } - else if (lhs_type == value_t::number_integer && rhs_type == value_t::number_unsigned) + return !(rhs < *this); + } + + /// @brief comparison: less than or equal + /// @sa https://json.nlohmann.me/api/basic_json/operator_le/ + template + requires std::is_scalar_v + bool operator<=(ScalarType rhs) const noexcept + { + return *this <= basic_json(rhs); + } + + /// @brief comparison: greater than or equal + /// @sa https://json.nlohmann.me/api/basic_json/operator_ge/ + JSON_HEDLEY_DEPRECATED_FOR(3.11.0, undef JSON_USE_LEGACY_DISCARDED_VALUE_COMPARISON) + bool operator>=(const_reference rhs) const noexcept + { + if (compares_unordered(rhs, true)) { - return lhs.m_value.number_integer == static_cast(rhs.m_value.number_unsigned); + return false; } + return !(*this < rhs); + } - return false; + /// @brief comparison: greater than or equal + /// @sa https://json.nlohmann.me/api/basic_json/operator_ge/ + template + requires std::is_scalar_v + bool operator>=(ScalarType rhs) const noexcept + { + return *this >= basic_json(rhs); + } +#endif +#else + /// @brief comparison: equal + /// @sa https://json.nlohmann.me/api/basic_json/operator_eq/ + friend bool operator==(const_reference lhs, const_reference rhs) noexcept + { +#ifdef __GNUC__ +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wfloat-equal" +#endif + JSON_IMPLEMENT_OPERATOR( ==, true, false, false) #ifdef __GNUC__ #pragma GCC diagnostic pop #endif @@ -21427,6 +21617,10 @@ 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); } @@ -21452,76 +21646,10 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec /// @sa https://json.nlohmann.me/api/basic_json/operator_lt/ friend bool operator<(const_reference lhs, const_reference rhs) noexcept { - const auto lhs_type = lhs.type(); - const auto rhs_type = rhs.type(); - - if (lhs_type == rhs_type) - { - switch (lhs_type) - { - case value_t::array: - // note parentheses are necessary, see - // https://github.com/nlohmann/json/issues/1530 - return (*lhs.m_value.array) < (*rhs.m_value.array); - - case value_t::object: - return (*lhs.m_value.object) < (*rhs.m_value.object); - - case value_t::null: - return false; - - case value_t::string: - return (*lhs.m_value.string) < (*rhs.m_value.string); - - case value_t::boolean: - return (lhs.m_value.boolean) < (rhs.m_value.boolean); - - case value_t::number_integer: - return (lhs.m_value.number_integer) < (rhs.m_value.number_integer); - - case value_t::number_unsigned: - return (lhs.m_value.number_unsigned) < (rhs.m_value.number_unsigned); - - case value_t::number_float: - return (lhs.m_value.number_float) < (rhs.m_value.number_float); - - case value_t::binary: - return (*lhs.m_value.binary) < (*rhs.m_value.binary); - - case value_t::discarded: - default: - return false; - } - } - else if (lhs_type == value_t::number_integer && rhs_type == value_t::number_float) - { - return static_cast(lhs.m_value.number_integer) < rhs.m_value.number_float; - } - else if (lhs_type == value_t::number_float && rhs_type == value_t::number_integer) - { - return lhs.m_value.number_float < static_cast(rhs.m_value.number_integer); - } - else if (lhs_type == value_t::number_unsigned && rhs_type == value_t::number_float) - { - return static_cast(lhs.m_value.number_unsigned) < rhs.m_value.number_float; - } - else if (lhs_type == value_t::number_float && rhs_type == value_t::number_unsigned) - { - return lhs.m_value.number_float < static_cast(rhs.m_value.number_unsigned); - } - else if (lhs_type == value_t::number_integer && rhs_type == value_t::number_unsigned) - { - return lhs.m_value.number_integer < static_cast(rhs.m_value.number_unsigned); - } - else if (lhs_type == value_t::number_unsigned && rhs_type == value_t::number_integer) - { - return static_cast(lhs.m_value.number_unsigned) < rhs.m_value.number_integer; - } - - // We only reach this line if we cannot compare values. In that case, + // default_result is used if we cannot compare values. In that case, // we compare types. Note we have to call the operator explicitly, // because MSVC has problems otherwise. - return operator<(lhs_type, rhs_type); + JSON_IMPLEMENT_OPERATOR( <, false, false, operator<(lhs_type, rhs_type)) } /// @brief comparison: less than @@ -21546,6 +21674,10 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec /// @sa https://json.nlohmann.me/api/basic_json/operator_le/ friend bool operator<=(const_reference lhs, const_reference rhs) noexcept { + if (compares_unordered(lhs, rhs, true)) + { + return false; + } return !(rhs < lhs); } @@ -21571,6 +21703,11 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec /// @sa https://json.nlohmann.me/api/basic_json/operator_gt/ friend bool operator>(const_reference lhs, const_reference rhs) noexcept { + // double inverse + if (compares_unordered(lhs, rhs)) + { + return false; + } return !(lhs <= rhs); } @@ -21596,6 +21733,10 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec /// @sa https://json.nlohmann.me/api/basic_json/operator_ge/ friend bool operator>=(const_reference lhs, const_reference rhs) noexcept { + if (compares_unordered(lhs, rhs, true)) + { + return false; + } return !(lhs < rhs); } @@ -21616,6 +21757,9 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec { return basic_json(lhs) >= rhs; } +#endif + +#undef JSON_IMPLEMENT_OPERATOR /// @} @@ -22835,10 +22979,14 @@ struct less< ::nlohmann::detail::value_t> // do not remove the space after '<', @brief compare two value_t enum values @since version 3.0.0 */ - bool operator()(nlohmann::detail::value_t lhs, - nlohmann::detail::value_t rhs) const noexcept + bool operator()(::nlohmann::detail::value_t lhs, + ::nlohmann::detail::value_t rhs) const noexcept { - return nlohmann::detail::operator<(lhs, rhs); +#if JSON_HAS_THREE_WAY_COMPARISON + return std::is_lt(lhs <=> rhs); // *NOPAD* +#else + return ::nlohmann::detail::operator<(lhs, rhs); +#endif } }; @@ -22903,6 +23051,8 @@ inline nlohmann::json::json_pointer operator "" _json_pointer(const char* s, std #undef JSON_HAS_FILESYSTEM #undef JSON_HAS_EXPERIMENTAL_FILESYSTEM #undef JSON_HAS_THREE_WAY_COMPARISON + #undef JSON_HAS_RANGES + #undef JSON_USE_LEGACY_DISCARDED_VALUE_COMPARISON #endif // #include diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 1d5c786974..4af3e2dddf 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -119,6 +119,15 @@ foreach(file ${files}) json_test_add_test_for(${file} MAIN test_main CXX_STANDARDS ${test_cxx_standards} ${test_force}) endforeach() +# test legacy comparison of discarded values +json_test_set_test_options(test-comparison_legacy + COMPILE_DEFINITIONS JSON_USE_LEGACY_DISCARDED_VALUE_COMPARISON=1 +) +json_test_add_test_for(src/unit-comparison.cpp + NAME test-comparison_legacy + MAIN test_main CXX_STANDARDS ${test_cxx_standards} ${test_force} +) + # *DO NOT* use json_test_set_test_options() below this line ############################################################################# diff --git a/test/src/unit-class_parser.cpp b/test/src/unit-class_parser.cpp index 95a958d1ff..3d26773943 100644 --- a/test/src/unit-class_parser.cpp +++ b/test/src/unit-class_parser.cpp @@ -1454,17 +1454,17 @@ TEST_CASE("parser class") SECTION("filter specific element") { - json j_object = json::parse(s_object, [](int /*unused*/, json::parse_event_t /*unused*/, const json & j) noexcept + json j_object = json::parse(s_object, [](int /*unused*/, json::parse_event_t event, const json & j) noexcept { // filter all number(2) elements - return j != json(2); + return event != json::parse_event_t::value || j != json(2); }); CHECK (j_object == json({{"bar", {{"baz", 1}}}})); - json j_array = json::parse(s_array, [](int /*unused*/, json::parse_event_t /*unused*/, const json & j) noexcept + json j_array = json::parse(s_array, [](int /*unused*/, json::parse_event_t event, const json & j) noexcept { - return j != json(2); + return event != json::parse_event_t::value || j != json(2); }); CHECK (j_array == json({1, {3, 4, 5}, 4, 5})); diff --git a/test/src/unit-comparison.cpp b/test/src/unit-comparison.cpp index 6c94add602..a75f598ba0 100644 --- a/test/src/unit-comparison.cpp +++ b/test/src/unit-comparison.cpp @@ -29,9 +29,44 @@ SOFTWARE. #include "doctest_compatibility.h" +#define JSON_TESTS_PRIVATE #include using nlohmann::json; + +// build this testcase in C++20-mode (CMake code detects macro use) +// JSON_HAS_CPP_20 + +#if JSON_HAS_THREE_WAY_COMPARISON +// this can be replaced with the doctest stl extension header in version 2.5 +namespace doctest +{ +template<> struct StringMaker +{ + static String convert(const std::partial_ordering& order) + { + if (order == std::partial_ordering::less) + { + return "std::partial_ordering::less"; + } + if (order == std::partial_ordering::equivalent) + { + return "std::partial_ordering::equivalent"; + } + if (order == std::partial_ordering::greater) + { + return "std::partial_ordering::greater"; + } + if (order == std::partial_ordering::unordered) + { + return "std::partial_ordering::unordered"; + } + return "{?}"; + } +}; +} // namespace doctest +#endif + namespace { // helper function to check std::less @@ -45,6 +80,27 @@ bool f(A a, B b, U u = U()) TEST_CASE("lexicographical comparison operators") { + constexpr auto f_ = false; + constexpr auto _t = true; + constexpr auto nan = std::numeric_limits::quiet_NaN(); +#if JSON_HAS_THREE_WAY_COMPARISON + constexpr auto lt = std::partial_ordering::less; + constexpr auto gt = std::partial_ordering::greater; + constexpr auto eq = std::partial_ordering::equivalent; + constexpr auto un = std::partial_ordering::unordered; +#endif + +#if JSON_HAS_THREE_WAY_COMPARISON + INFO("using 3-way comparison"); +#endif + +#if JSON_USE_LEGACY_DISCARDED_VALUE_COMPARISON + INFO("using legacy comparison"); +#endif + + //REQUIRE(std::numeric_limits::has_quiet_NaN); + REQUIRE(std::isnan(nan)); + SECTION("types") { std::vector j_types = @@ -57,97 +113,263 @@ TEST_CASE("lexicographical comparison operators") json::value_t::object, json::value_t::array, json::value_t::string, - json::value_t::binary + json::value_t::binary, + json::value_t::discarded + }; + + std::vector> expected_lt = + { + //0 1 2 3 4 5 6 7 8 9 + {f_, _t, _t, _t, _t, _t, _t, _t, _t, f_}, // 0 + {f_, f_, _t, _t, _t, _t, _t, _t, _t, f_}, // 1 + {f_, f_, f_, f_, f_, _t, _t, _t, _t, f_}, // 2 + {f_, f_, f_, f_, f_, _t, _t, _t, _t, f_}, // 3 + {f_, f_, f_, f_, f_, _t, _t, _t, _t, f_}, // 4 + {f_, f_, f_, f_, f_, f_, _t, _t, _t, f_}, // 5 + {f_, f_, f_, f_, f_, f_, f_, _t, _t, f_}, // 6 + {f_, f_, f_, f_, f_, f_, f_, f_, _t, f_}, // 7 + {f_, f_, f_, f_, f_, f_, f_, f_, f_, f_}, // 8 + {f_, f_, f_, f_, f_, f_, f_, f_, f_, f_}, // 9 }; SECTION("comparison: less") { - std::vector> expected = + REQUIRE(expected_lt.size() == j_types.size()); + for (size_t i = 0; i < j_types.size(); ++i) { - {false, true, true, true, true, true, true, true, true}, - {false, false, true, true, true, true, true, true, true}, - {false, false, false, false, false, true, true, true, true}, - {false, false, false, false, false, true, true, true, true}, - {false, false, false, false, false, true, true, true, true}, - {false, false, false, false, false, false, true, true, true}, - {false, false, false, false, false, false, false, true, true}, - {false, false, false, false, false, false, false, false, true}, - {false, false, false, false, false, false, false, false, false} + REQUIRE(expected_lt[i].size() == j_types.size()); + for (size_t j = 0; j < j_types.size(); ++j) + { + CAPTURE(i) + CAPTURE(j) + // check precomputed values +#if JSON_HAS_THREE_WAY_COMPARISON + CHECK((j_types[i] < j_types[j]) == expected_lt[i][j]); +#else + CHECK(operator<(j_types[i], j_types[j]) == expected_lt[i][j]); +#endif + CHECK(f(j_types[i], j_types[j]) == expected_lt[i][j]); + } + } + } +#if JSON_HAS_THREE_WAY_COMPARISON + SECTION("comparison: 3-way") + { + std::vector> expected = + { + //0 1 2 3 4 5 6 7 8 9 + {eq, lt, lt, lt, lt, lt, lt, lt, lt, un}, // 0 + {gt, eq, lt, lt, lt, lt, lt, lt, lt, un}, // 1 + {gt, gt, eq, eq, eq, lt, lt, lt, lt, un}, // 2 + {gt, gt, eq, eq, eq, lt, lt, lt, lt, un}, // 3 + {gt, gt, eq, eq, eq, lt, lt, lt, lt, un}, // 4 + {gt, gt, gt, gt, gt, eq, lt, lt, lt, un}, // 5 + {gt, gt, gt, gt, gt, gt, eq, lt, lt, un}, // 6 + {gt, gt, gt, gt, gt, gt, gt, eq, lt, un}, // 7 + {gt, gt, gt, gt, gt, gt, gt, gt, eq, un}, // 8 + {un, un, un, un, un, un, un, un, un, un}, // 9 }; + REQUIRE(expected.size() == expected_lt.size()); + for (size_t i = 0; i < expected.size(); ++i) + { + REQUIRE(expected[i].size() == expected_lt[i].size()); + for (size_t j = 0; j < expected[i].size(); ++j) + { + CAPTURE(i) + CAPTURE(j) + CHECK(std::is_lt(expected[i][j]) == expected_lt[i][j]); + } + } + + REQUIRE(expected.size() == j_types.size()); for (size_t i = 0; i < j_types.size(); ++i) { + REQUIRE(expected[i].size() == j_types.size()); for (size_t j = 0; j < j_types.size(); ++j) { CAPTURE(i) CAPTURE(j) // check precomputed values - CHECK(operator<(j_types[i], j_types[j]) == expected[i][j]); - CHECK(f(j_types[i], j_types[j]) == expected[i][j]); + CHECK((j_types[i] <=> j_types[j]) == expected[i][j]); // *NOPAD* } } } +#endif } SECTION("values") { json j_values = { - nullptr, nullptr, - -17, 42, - 8u, 13u, - 3.14159, 23.42, - "foo", "bar", - true, false, - {1, 2, 3}, {"one", "two", "three"}, - {{"first", 1}, {"second", 2}}, {{"a", "A"}, {"b", {"B"}}}, - json::binary({1, 2, 3}), json::binary({1, 2, 4}) + nullptr, nullptr, // 0 1 + -17, 42, // 2 3 + 8u, 13u, // 4 5 + 3.14159, 23.42, // 6 7 + nan, nan, // 8 9 + "foo", "bar", // 10 11 + true, false, // 12 13 + {1, 2, 3}, {"one", "two", "three"}, // 14 15 + {{"first", 1}, {"second", 2}}, {{"a", "A"}, {"b", {"B"}}}, // 16 17 + json::binary({1, 2, 3}), json::binary({1, 2, 4}), // 18 19 + json(json::value_t::discarded), json(json::value_t::discarded) // 20 21 }; - SECTION("comparison: equal") + std::vector> expected_eq = + { + //0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 + {_t, _t, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_}, // 0 + {_t, _t, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_}, // 1 + {f_, f_, _t, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_}, // 2 + {f_, f_, f_, _t, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_}, // 3 + {f_, f_, f_, f_, _t, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_}, // 4 + {f_, f_, f_, f_, f_, _t, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_}, // 5 + {f_, f_, f_, f_, f_, f_, _t, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_}, // 6 + {f_, f_, f_, f_, f_, f_, f_, _t, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_}, // 7 + {f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_}, // 8 + {f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_}, // 9 + {f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, _t, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_}, // 10 + {f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, _t, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_}, // 11 + {f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, _t, f_, f_, f_, f_, f_, f_, f_, f_, f_}, // 12 + {f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, _t, f_, f_, f_, f_, f_, f_, f_, f_}, // 13 + {f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, _t, f_, f_, f_, f_, f_, f_, f_}, // 14 + {f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, _t, f_, f_, f_, f_, f_, f_}, // 15 + {f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, _t, f_, f_, f_, f_, f_}, // 16 + {f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, _t, f_, f_, f_, f_}, // 17 + {f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, _t, f_, f_, f_}, // 18 + {f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, _t, f_, f_}, // 19 + {f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_}, // 20 + {f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_}, // 21 + }; + + std::vector> expected_lt = + { + //0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 + {f_, f_, _t, _t, _t, _t, _t, _t, _t, _t, _t, _t, _t, _t, _t, _t, _t, _t, _t, _t, f_, f_}, // 0 + {f_, f_, _t, _t, _t, _t, _t, _t, _t, _t, _t, _t, _t, _t, _t, _t, _t, _t, _t, _t, f_, f_}, // 1 + {f_, f_, f_, _t, _t, _t, _t, _t, f_, f_, _t, _t, f_, f_, _t, _t, _t, _t, _t, _t, f_, f_}, // 2 + {f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, _t, _t, f_, f_, _t, _t, _t, _t, _t, _t, f_, f_}, // 3 + {f_, f_, f_, _t, f_, _t, f_, _t, f_, f_, _t, _t, f_, f_, _t, _t, _t, _t, _t, _t, f_, f_}, // 4 + {f_, f_, f_, _t, f_, f_, f_, _t, f_, f_, _t, _t, f_, f_, _t, _t, _t, _t, _t, _t, f_, f_}, // 5 + {f_, f_, f_, _t, _t, _t, f_, _t, f_, f_, _t, _t, f_, f_, _t, _t, _t, _t, _t, _t, f_, f_}, // 6 + {f_, f_, f_, _t, f_, f_, f_, f_, f_, f_, _t, _t, f_, f_, _t, _t, _t, _t, _t, _t, f_, f_}, // 7 + {f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, _t, _t, f_, f_, _t, _t, _t, _t, _t, _t, f_, f_}, // 8 + {f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, _t, _t, f_, f_, _t, _t, _t, _t, _t, _t, f_, f_}, // 9 + {f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, _t, _t, f_, f_}, // 10 + {f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, _t, f_, f_, f_, f_, f_, f_, f_, _t, _t, f_, f_}, // 11 + {f_, f_, _t, _t, _t, _t, _t, _t, _t, _t, _t, _t, f_, f_, _t, _t, _t, _t, _t, _t, f_, f_}, // 12 + {f_, f_, _t, _t, _t, _t, _t, _t, _t, _t, _t, _t, _t, f_, _t, _t, _t, _t, _t, _t, f_, f_}, // 13 + {f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, _t, _t, f_, f_, f_, _t, f_, f_, _t, _t, f_, f_}, // 14 + {f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, _t, _t, f_, f_, f_, f_, f_, f_, _t, _t, f_, f_}, // 15 + {f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, _t, _t, f_, f_, _t, _t, f_, f_, _t, _t, f_, f_}, // 16 + {f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, _t, _t, f_, f_, _t, _t, _t, f_, _t, _t, f_, f_}, // 17 + {f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, _t, f_, f_}, // 18 + {f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_}, // 19 + {f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_}, // 20 + {f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_}, // 21 + }; + + SECTION("compares unordered") + { + std::vector> expected = + { + //0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 + {f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, _t, _t}, // 0 + {f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, _t, _t}, // 1 + {f_, f_, f_, f_, f_, f_, f_, f_, _t, _t, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, _t, _t}, // 2 + {f_, f_, f_, f_, f_, f_, f_, f_, _t, _t, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, _t, _t}, // 3 + {f_, f_, f_, f_, f_, f_, f_, f_, _t, _t, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, _t, _t}, // 4 + {f_, f_, f_, f_, f_, f_, f_, f_, _t, _t, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, _t, _t}, // 5 + {f_, f_, f_, f_, f_, f_, f_, f_, _t, _t, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, _t, _t}, // 6 + {f_, f_, f_, f_, f_, f_, f_, f_, _t, _t, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, _t, _t}, // 7 + {f_, f_, _t, _t, _t, _t, _t, _t, _t, _t, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, _t, _t}, // 8 + {f_, f_, _t, _t, _t, _t, _t, _t, _t, _t, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, _t, _t}, // 9 + {f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, _t, _t}, // 10 + {f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, _t, _t}, // 11 + {f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, _t, _t}, // 12 + {f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, _t, _t}, // 13 + {f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, _t, _t}, // 14 + {f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, _t, _t}, // 15 + {f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, _t, _t}, // 16 + {f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, _t, _t}, // 17 + {f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, _t, _t}, // 18 + {f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, _t, _t}, // 19 + {_t, _t, _t, _t, _t, _t, _t, _t, _t, _t, _t, _t, _t, _t, _t, _t, _t, _t, _t, _t, _t, _t}, // 20 + {_t, _t, _t, _t, _t, _t, _t, _t, _t, _t, _t, _t, _t, _t, _t, _t, _t, _t, _t, _t, _t, _t}, // 21 + }; + + REQUIRE(expected.size() == j_values.size()); + for (size_t i = 0; i < j_values.size(); ++i) + { + REQUIRE(expected[i].size() == j_values.size()); + for (size_t j = 0; j < j_values.size(); ++j) + { + CAPTURE(i) + CAPTURE(j) + CHECK(json::compares_unordered(j_values[i], j_values[j]) == expected[i][j]); + } + } + } + +#if JSON_USE_LEGACY_DISCARDED_VALUE_COMPARISON + SECTION("compares unordered (inverse)") { std::vector> expected = { - {true, true, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false}, - {true, true, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false}, - {false, false, true, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false}, - {false, false, false, true, false, false, false, false, false, false, false, false, false, false, false, false, false, false}, - {false, false, false, false, true, false, false, false, false, false, false, false, false, false, false, false, false, false}, - {false, false, false, false, false, true, false, false, false, false, false, false, false, false, false, false, false, false}, - {false, false, false, false, false, false, true, false, false, false, false, false, false, false, false, false, false, false}, - {false, false, false, false, false, false, false, true, false, false, false, false, false, false, false, false, false, false}, - {false, false, false, false, false, false, false, false, true, false, false, false, false, false, false, false, false, false}, - {false, false, false, false, false, false, false, false, false, true, false, false, false, false, false, false, false, false}, - {false, false, false, false, false, false, false, false, false, false, true, false, false, false, false, false, false, false}, - {false, false, false, false, false, false, false, false, false, false, false, true, false, false, false, false, false, false}, - {false, false, false, false, false, false, false, false, false, false, false, false, true, false, false, false, false, false}, - {false, false, false, false, false, false, false, false, false, false, false, false, false, true, false, false, false, false}, - {false, false, false, false, false, false, false, false, false, false, false, false, false, false, true, false, false, false}, - {false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, true, false, false}, - {false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, true, false}, - {false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, true} + //0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 + {f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_}, // 0 + {f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_}, // 1 + {f_, f_, f_, f_, f_, f_, f_, f_, _t, _t, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_}, // 2 + {f_, f_, f_, f_, f_, f_, f_, f_, _t, _t, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_}, // 3 + {f_, f_, f_, f_, f_, f_, f_, f_, _t, _t, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_}, // 4 + {f_, f_, f_, f_, f_, f_, f_, f_, _t, _t, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_}, // 5 + {f_, f_, f_, f_, f_, f_, f_, f_, _t, _t, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_}, // 6 + {f_, f_, f_, f_, f_, f_, f_, f_, _t, _t, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_}, // 7 + {f_, f_, _t, _t, _t, _t, _t, _t, _t, _t, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_}, // 8 + {f_, f_, _t, _t, _t, _t, _t, _t, _t, _t, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_}, // 9 + {f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_}, // 10 + {f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_}, // 11 + {f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_}, // 12 + {f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_}, // 13 + {f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_}, // 14 + {f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_}, // 15 + {f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_}, // 16 + {f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_}, // 17 + {f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_}, // 18 + {f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_}, // 19 + {f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_}, // 20 + {f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_}, // 21 }; + REQUIRE(expected.size() == j_values.size()); for (size_t i = 0; i < j_values.size(); ++i) { + REQUIRE(expected[i].size() == j_values.size()); for (size_t j = 0; j < j_values.size(); ++j) { CAPTURE(i) CAPTURE(j) CAPTURE(j_values[i]) CAPTURE(j_values[j]) - // check precomputed values - CHECK( (j_values[i] == j_values[j]) == expected[i][j] ); + CHECK(json::compares_unordered(j_values[i], j_values[j], true) == expected[i][j]); } } + } +#endif - // comparison with discarded elements - json j_discarded(json::value_t::discarded); - for (const auto& v : j_values) + SECTION("comparison: equal") + { + REQUIRE(expected_eq.size() == j_values.size()); + for (size_t i = 0; i < j_values.size(); ++i) { - CHECK( (v == j_discarded) == false); - CHECK( (j_discarded == v) == false); - CHECK( (j_discarded == j_discarded) == false); + REQUIRE(expected_eq[i].size() == j_values.size()); + for (size_t j = 0; j < j_values.size(); ++j) + { + CAPTURE(i) + CAPTURE(j) + // check precomputed values + CHECK((j_values[i] == j_values[j]) == expected_eq[i][j]); + } } // compare with null pointer @@ -164,76 +386,65 @@ TEST_CASE("lexicographical comparison operators") { CAPTURE(i) CAPTURE(j) - // check definition - CHECK( (j_values[i] != j_values[j]) == !(j_values[i] == j_values[j]) ); + + if (json::compares_unordered(j_values[i], j_values[j], true)) + { + CHECK_FALSE(j_values[i] != j_values[j]); + } + else + { + // check definition + CHECK((j_values[i] != j_values[j]) == !(j_values[i] == j_values[j])); + } } } // compare with null pointer 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)); + CHECK((j_null != nullptr) == false); + CHECK((nullptr != j_null) == false); + CHECK((j_null != nullptr) == !(j_null == nullptr)); + CHECK((nullptr != j_null) == !(nullptr == j_null)); } SECTION("comparison: less") { - std::vector> expected = - { - {false, false, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true}, - {false, false, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true}, - {false, false, false, true, true, true, true, true, true, true, false, false, true, true, true, true, true, true}, - {false, false, false, false, false, false, false, false, true, true, false, false, true, true, true, true, true, true}, - {false, false, false, true, false, true, false, true, true, true, false, false, true, true, true, true, true, true}, - {false, false, false, true, false, false, false, true, true, true, false, false, true, true, true, true, true, true}, - {false, false, false, true, true, true, false, true, true, true, false, false, true, true, true, true, true, true}, - {false, false, false, true, false, false, false, false, true, true, false, false, true, true, true, true, true, true}, - {false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, true, true}, - {false, false, false, false, false, false, false, false, true, false, false, false, false, false, false, false, true, true}, - {false, false, true, true, true, true, true, true, true, true, false, false, true, true, true, true, true, true}, - {false, false, true, true, true, true, true, true, true, true, true, false, true, true, true, true, true, true}, - {false, false, false, false, false, false, false, false, true, true, false, false, false, true, false, false, true, true}, - {false, false, false, false, false, false, false, false, true, true, false, false, false, false, false, false, true, true}, - {false, false, false, false, false, false, false, false, true, true, false, false, true, true, false, false, true, true}, - {false, false, false, false, false, false, false, false, true, true, false, false, true, true, true, false, true, true}, - {false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, true}, - {false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false} - }; - + REQUIRE(expected_lt.size() == j_values.size()); for (size_t i = 0; i < j_values.size(); ++i) { + REQUIRE(expected_lt[i].size() == j_values.size()); for (size_t j = 0; j < j_values.size(); ++j) { - // Skip comparing indicies 12 and 13, and 13 and 12 in C++20 pending fix - // See issue #3207 -#if defined(JSON_HAS_CPP_20) || JSON_HAS_THREE_WAY_COMPARISON - if ((i == 12 && j == 13) || (i == 13 && j == 12)) - { - continue; - } -#endif CAPTURE(i) CAPTURE(j) - CAPTURE(j_values[i]) - CAPTURE(j_values[j]) // check precomputed values - CHECK( (j_values[i] < j_values[j]) == expected[i][j] ); + CHECK((j_values[i] < j_values[j]) == expected_lt[i][j]); } } + } - // comparison with discarded elements - json j_discarded(json::value_t::discarded); + SECTION("comparison: less than or equal equal") + { for (size_t i = 0; i < j_values.size(); ++i) { - CAPTURE(i) - CHECK( (j_values[i] < j_discarded) == false); - CHECK( (j_discarded < j_values[i]) == false); - CHECK( (j_discarded < j_discarded) == false); + for (size_t j = 0; j < j_values.size(); ++j) + { + CAPTURE(i) + CAPTURE(j) + if (json::compares_unordered(j_values[i], j_values[j], true)) + { + CHECK_FALSE(j_values[i] <= j_values[j]); + } + else + { + // check definition + CHECK((j_values[i] <= j_values[j]) == !(j_values[j] < j_values[i])); + } + } } } - SECTION("comparison: less than or equal equal") + SECTION("comparison: greater than") { for (size_t i = 0; i < j_values.size(); ++i) { @@ -241,13 +452,20 @@ TEST_CASE("lexicographical comparison operators") { CAPTURE(i) CAPTURE(j) - // check definition - CHECK( (j_values[i] <= j_values[j]) == !(j_values[j] < j_values[i]) ); + if (json::compares_unordered(j_values[i], j_values[j])) + { + CHECK_FALSE(j_values[i] > j_values[j]); + } + else + { + // check definition + CHECK((j_values[i] > j_values[j]) == (j_values[j] < j_values[i])); + } } } } - SECTION("comparison: greater than") + SECTION("comparison: greater than or equal") { for (size_t i = 0; i < j_values.size(); ++i) { @@ -255,24 +473,111 @@ TEST_CASE("lexicographical comparison operators") { CAPTURE(i) CAPTURE(j) - // check definition - CHECK( (j_values[i] > j_values[j]) == (j_values[j] < j_values[i]) ); + if (json::compares_unordered(j_values[i], j_values[j], true)) + { + CHECK_FALSE(j_values[i] >= j_values[j]); + } + else + { + // check definition + CHECK((j_values[i] >= j_values[j]) == !(j_values[i] < j_values[j])); + } } } } - SECTION("comparison: greater than or equal") +#if JSON_HAS_THREE_WAY_COMPARISON + SECTION("comparison: 3-way") { + std::vector> expected = + { + //0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 + {eq, eq, lt, lt, lt, lt, lt, lt, lt, lt, lt, lt, lt, lt, lt, lt, lt, lt, lt, lt, un, un}, // 0 + {eq, eq, lt, lt, lt, lt, lt, lt, lt, lt, lt, lt, lt, lt, lt, lt, lt, lt, lt, lt, un, un}, // 1 + {gt, gt, eq, lt, lt, lt, lt, lt, un, un, lt, lt, gt, gt, lt, lt, lt, lt, lt, lt, un, un}, // 2 + {gt, gt, gt, eq, gt, gt, gt, gt, un, un, lt, lt, gt, gt, lt, lt, lt, lt, lt, lt, un, un}, // 3 + {gt, gt, gt, lt, eq, lt, gt, lt, un, un, lt, lt, gt, gt, lt, lt, lt, lt, lt, lt, un, un}, // 4 + {gt, gt, gt, lt, gt, eq, gt, lt, un, un, lt, lt, gt, gt, lt, lt, lt, lt, lt, lt, un, un}, // 5 + {gt, gt, gt, lt, lt, lt, eq, lt, un, un, lt, lt, gt, gt, lt, lt, lt, lt, lt, lt, un, un}, // 6 + {gt, gt, gt, lt, gt, gt, gt, eq, un, un, lt, lt, gt, gt, lt, lt, lt, lt, lt, lt, un, un}, // 7 + {gt, gt, un, un, un, un, un, un, un, un, lt, lt, gt, gt, lt, lt, lt, lt, lt, lt, un, un}, // 8 + {gt, gt, un, un, un, un, un, un, un, un, lt, lt, gt, gt, lt, lt, lt, lt, lt, lt, un, un}, // 9 + {gt, gt, gt, gt, gt, gt, gt, gt, gt, gt, eq, gt, gt, gt, gt, gt, gt, gt, lt, lt, un, un}, // 10 + {gt, gt, gt, gt, gt, gt, gt, gt, gt, gt, lt, eq, gt, gt, gt, gt, gt, gt, lt, lt, un, un}, // 11 + {gt, gt, lt, lt, lt, lt, lt, lt, lt, lt, lt, lt, eq, gt, lt, lt, lt, lt, lt, lt, un, un}, // 12 + {gt, gt, lt, lt, lt, lt, lt, lt, lt, lt, lt, lt, lt, eq, lt, lt, lt, lt, lt, lt, un, un}, // 13 + {gt, gt, gt, gt, gt, gt, gt, gt, gt, gt, lt, lt, gt, gt, eq, lt, gt, gt, lt, lt, un, un}, // 14 + {gt, gt, gt, gt, gt, gt, gt, gt, gt, gt, lt, lt, gt, gt, gt, eq, gt, gt, lt, lt, un, un}, // 15 + {gt, gt, gt, gt, gt, gt, gt, gt, gt, gt, lt, lt, gt, gt, lt, lt, eq, gt, lt, lt, un, un}, // 16 + {gt, gt, gt, gt, gt, gt, gt, gt, gt, gt, lt, lt, gt, gt, lt, lt, lt, eq, lt, lt, un, un}, // 17 + {gt, gt, gt, gt, gt, gt, gt, gt, gt, gt, gt, gt, gt, gt, gt, gt, gt, gt, eq, lt, un, un}, // 18 + {gt, gt, gt, gt, gt, gt, gt, gt, gt, gt, gt, gt, gt, gt, gt, gt, gt, gt, gt, eq, un, un}, // 19 + {un, un, un, un, un, un, un, un, un, un, un, un, un, un, un, un, un, un, un, un, un, un}, // 20 + {un, un, un, un, un, un, un, un, un, un, un, un, un, un, un, un, un, un, un, un, un, un}, // 21 + }; + + REQUIRE(expected.size() == expected_eq.size()); + REQUIRE(expected.size() == expected_lt.size()); + for (size_t i = 0; i < expected.size(); ++i) + { + REQUIRE(expected[i].size() == expected_eq[i].size()); + REQUIRE(expected[i].size() == expected_lt[i].size()); + for (size_t j = 0; j < expected[i].size(); ++j) + { + CAPTURE(i) + CAPTURE(j) + CHECK(std::is_eq(expected[i][j]) == expected_eq[i][j]); + CHECK(std::is_lt(expected[i][j]) == expected_lt[i][j]); + } + } + + REQUIRE(expected.size() == j_values.size()); for (size_t i = 0; i < j_values.size(); ++i) { + REQUIRE(expected[i].size() == j_values.size()); for (size_t j = 0; j < j_values.size(); ++j) { CAPTURE(i) CAPTURE(j) - // check definition - CHECK( (j_values[i] >= j_values[j]) == !(j_values[i] < j_values[j]) ); + CHECK((j_values[i] <=> j_values[j]) == expected[i][j]); // *NOPAD* } } } +#endif + } + +#if JSON_USE_LEGACY_DISCARDED_VALUE_COMPARISON + SECTION("parser callback regression") + { + SECTION("filter specific element") + { + const auto* s_object = R"( + { + "foo": 2, + "bar": { + "baz": 1 + } + } + )"; + const auto* s_array = R"( + [1,2,[3,4,5],4,5] + )"; + + json j_object = json::parse(s_object, [](int /*unused*/, json::parse_event_t /*unused*/, const json & j) noexcept + { + // filter all number(2) elements + return j != json(2); + }); + + CHECK (j_object == json({{"bar", {{"baz", 1}}}})); + + json j_array = json::parse(s_array, [](int /*unused*/, json::parse_event_t /*unused*/, const json & j) noexcept + { + return j != json(2); + }); + + CHECK (j_array == json({1, {3, 4, 5}, 4, 5})); + } } +#endif }