Make comparisons with scalars noexcept only when the conversion is (#5751)

The comparison operators taking a scalar (==, !=, <, <=, >, >=, and
C++20's <=>) convert the scalar to a basic_json and compare, but were
unconditionally noexcept. When that conversion throws, the program
called std::terminate instead of propagating the exception, e.g. when
comparing a json with a string literal under memory pressure
(std::bad_alloc) or with an enum value not mapped by
NLOHMANN_JSON_SERIALIZE_ENUM_STRICT (out_of_range.410). clang-tidy
22.1 reports the latter as bugprone-exception-escape.

Declare the 16 scalar overloads
noexcept(std::is_nothrow_constructible<basic_json, ScalarType>::value):
they stay noexcept for numbers, Booleans, nullptr, and plain enums,
and are noexcept(false) for strings and enums whose to_json may throw.
The comparisons of two basic_json values are unchanged.

Restore the strict-enum comparisons removed from unit-conversions.cpp
in the previous PR, check that comparing an unmapped strict enum now
throws, and pin the new exception specifications in unit-noexcept.cpp.
Document the exception safety of overload (2) on all seven operator
pages. Ran make amalgamate.

Signed-off-by: Niels Lohmann <mail@nlohmann.me>
This commit is contained in:
Niels Lohmann
2026-10-02 17:25:17 +02:00
committed by GitHub
parent 63c10a51fc
commit 6d0867a85f
11 changed files with 125 additions and 53 deletions

View File

@@ -5,10 +5,10 @@
bool operator!=(const_reference lhs, const_reference rhs) noexcept; // (1)
template<typename ScalarType>
bool operator!=(const_reference lhs, const ScalarType rhs) noexcept; // (2)
bool operator!=(const_reference lhs, const ScalarType rhs) noexcept(/* see below */); // (2)
template<typename ScalarType>
bool operator!=(ScalarType lhs, const const_reference rhs) noexcept; // (2)
bool operator!=(ScalarType lhs, const const_reference rhs) noexcept(/* see below */); // (2)
```
1. Compares two JSON values for inequality. Returns `#!cpp !(lhs == rhs)`.
@@ -36,7 +36,12 @@ whether the values `lhs`/`*this` and `rhs` are not equal
## Exception safety
No-throw guarantee: this function never throws exceptions.
1. No-throw guarantee: this function never throws exceptions.
2. No-throw guarantee if converting the scalar to a JSON value cannot throw, as for numbers, Booleans, and
`#!cpp nullptr`; the function is `#!cpp noexcept` exactly in that case. Otherwise, it throws what the conversion
throws, for example `std::bad_alloc` when converting a string, or
[`out_of_range.410`](../../home/exceptions.md#jsonexceptionout_of_range410) for an enum value not mapped by
[`NLOHMANN_JSON_SERIALIZE_ENUM_STRICT`](../macros/nlohmann_json_serialize_enum_strict.md).
## Complexity
@@ -98,3 +103,4 @@ Linear.
member function in version 3.13.0; since C++20, the compiler rewrites `a != b` using `operator==`.
2. Added in version 1.0.0. Changed in version 3.13.0 to remove special-casing for `NaN` and `discarded` values;
`operator!=` now consistently means `!(a == b)`. Since C++20, the compiler rewrites `a != b` using `operator==`.
Made conditionally `#!cpp noexcept` in version 3.13.0; before, a throwing conversion called `std::terminate`.