From df27cc3d4cdf9ecb9337524c2af7752967ebf4ca Mon Sep 17 00:00:00 2001 From: Niels Lohmann Date: Sun, 4 Oct 2026 11:46:21 +0200 Subject: [PATCH] Add scalar-on-left overloads for legacy discarded comparisons in C++20 (#5682) 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 b6efd8dbd..e61ce1a3e 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 @@ -81,3 +81,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 1ff12c7f3..b802b4b59 100644 --- a/include/nlohmann/json.hpp +++ b/include/nlohmann/json.hpp @@ -4939,6 +4939,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 9e27f62f0..2adc9978f 100644 --- a/single_include/nlohmann/json.hpp +++ b/single_include/nlohmann/json.hpp @@ -31644,6 +31644,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 febfd9b42..a65d5c1b3 100644 --- a/tests/src/unit-comparison.cpp +++ b/tests/src/unit-comparison.cpp @@ -750,6 +750,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