From 864ac37cdb8cefe34af2d90ea061ac4efc57c2a6 Mon Sep 17 00:00:00 2001 From: Niels Lohmann Date: Tue, 29 Sep 2026 23:16:44 +0200 Subject: [PATCH] Add scalar-on-left overloads for legacy discarded comparisons in C++20 With JSON_USE_LEGACY_DISCARDED_VALUE_COMPARISON=1 and C++20, a scalar on the left-hand side of <= or >= (e.g., `1 <= discarded`) yielded false instead of the documented true. The C++20 legacy block only had member operators, which are only candidates when the basic_json is the left operand; for a scalar on the left, overload resolution picked the candidate rewritten from operator<=>, which does not emulate the legacy behavior. The C++17 branch already has scalar-on-the-left friend overloads for <= and >=; add the equivalent pair to the C++20 legacy block. Added a regression test to tests/src/unit-comparison.cpp covering all four operand orders for both operators. Fixes #5665. Signed-off-by: Niels Lohmann --- ...n_use_legacy_discarded_value_comparison.md | 3 +++ include/nlohmann/json.hpp | 21 ++++++++++++++++++ single_include/nlohmann/json.hpp | 21 ++++++++++++++++++ tests/src/unit-comparison.cpp | 22 +++++++++++++++++++ 4 files changed, 67 insertions(+) diff --git a/docs/mkdocs/docs/api/macros/json_use_legacy_discarded_value_comparison.md b/docs/mkdocs/docs/api/macros/json_use_legacy_discarded_value_comparison.md index f026a9825..a8cae475c 100644 --- a/docs/mkdocs/docs/api/macros/json_use_legacy_discarded_value_comparison.md +++ b/docs/mkdocs/docs/api/macros/json_use_legacy_discarded_value_comparison.md @@ -79,3 +79,6 @@ When the macro is not defined, the library will define it to its default value. ## Version history - Added in version 3.11.0. +- Fixed in version 3.13.0 so `<=` and `>=` also emulate the legacy behavior in C++20 when the JSON value is the + right-hand operand of a scalar comparison; before, only the 3-way-comparison-rewritten candidate was found, which + yielded `#!cpp false` instead of `#!cpp true`. diff --git a/include/nlohmann/json.hpp b/include/nlohmann/json.hpp index 500fcddf2..ac29acdb5 100644 --- a/include/nlohmann/json.hpp +++ b/include/nlohmann/json.hpp @@ -4673,6 +4673,27 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec { return *this >= basic_json(rhs); } + + // a scalar on the left-hand side would otherwise select the candidate + // rewritten from operator<=>, which does not emulate the legacy behavior + + /// @brief comparison: less than or equal + /// @sa https://json.nlohmann.me/api/basic_json/operator_le/ + template + requires std::is_scalar_v + friend bool operator<=(ScalarType lhs, const_reference rhs) noexcept + { + return basic_json(lhs) <= rhs; + } + + /// @brief comparison: greater than or equal + /// @sa https://json.nlohmann.me/api/basic_json/operator_ge/ + template + requires std::is_scalar_v + friend bool operator>=(ScalarType lhs, const_reference rhs) noexcept + { + return basic_json(lhs) >= rhs; + } #endif #else /// @brief comparison: equal diff --git a/single_include/nlohmann/json.hpp b/single_include/nlohmann/json.hpp index 576498738..e0d60b78e 100644 --- a/single_include/nlohmann/json.hpp +++ b/single_include/nlohmann/json.hpp @@ -30754,6 +30754,27 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec { return *this >= basic_json(rhs); } + + // a scalar on the left-hand side would otherwise select the candidate + // rewritten from operator<=>, which does not emulate the legacy behavior + + /// @brief comparison: less than or equal + /// @sa https://json.nlohmann.me/api/basic_json/operator_le/ + template + requires std::is_scalar_v + friend bool operator<=(ScalarType lhs, const_reference rhs) noexcept + { + return basic_json(lhs) <= rhs; + } + + /// @brief comparison: greater than or equal + /// @sa https://json.nlohmann.me/api/basic_json/operator_ge/ + template + requires std::is_scalar_v + friend bool operator>=(ScalarType lhs, const_reference rhs) noexcept + { + return basic_json(lhs) >= rhs; + } #endif #else /// @brief comparison: equal diff --git a/tests/src/unit-comparison.cpp b/tests/src/unit-comparison.cpp index 69c0103c9..fe3dcda11 100644 --- a/tests/src/unit-comparison.cpp +++ b/tests/src/unit-comparison.cpp @@ -749,6 +749,28 @@ TEST_CASE("regression #3868 - heterogeneous comparisons compile under C++20 (P24 CHECK_FALSE(j != i); } } + +#if JSON_USE_LEGACY_DISCARDED_VALUE_COMPARISON +TEST_CASE("regression #5665 - scalar <= discarded and scalar >= discarded in C++20 legacy mode") +{ + // Issue #5665: with a scalar on the left-hand side, <= and >= only had the + // candidate rewritten from operator<=>, which does not emulate the legacy + // discarded-value behavior. Check that scalar-on-the-left now matches the + // other three operand orders. + const json discarded(json::value_t::discarded); + const json one = 1; + + CHECK(discarded <= 1); + CHECK(discarded >= 1); + CHECK(one <= discarded); + CHECK(one >= discarded); + CHECK(1 <= discarded); + CHECK(1 >= discarded); + CHECK(1.5 <= discarded); + CHECK(1.5 >= discarded); +} +#endif + #endif namespace