Make comparisons with scalars noexcept only when the conversion is

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 11:23:58 +02:00
committed by GitHub
parent 4d46bce4e1
commit c0694f7966
11 changed files with 126 additions and 54 deletions
+21
View File
@@ -1764,6 +1764,12 @@ TEST_CASE("Strict JSON to enum mapping")
CHECK(json("herz").get<strict_cards>() == strict_cards::herz);
CHECK(json("karo").get<strict_cards>() == strict_cards::karo);
// comparison of enum and json
CHECK(strict_cards::kreuz == json("kreuz"));
CHECK(strict_cards::pik == json("pik"));
CHECK(strict_cards::herz == json("herz"));
CHECK(strict_cards::karo == json("karo"));
// invalid json -> exception thrown
json _;
CHECK_THROWS_WITH_AS(_ = json("what?").get<strict_cards>(), "[json.exception.out_of_range.410] enum value out of range for strict_cards: \"what?\"", json::out_of_range&);
@@ -1771,6 +1777,12 @@ TEST_CASE("Strict JSON to enum mapping")
// conversion of unmapped enum -> exception thrown
CHECK_THROWS_WITH_AS(json(strict_cards::andere), "[json.exception.out_of_range.410] enum value out of range for strict_cards", json::out_of_range&);
// comparing an unmapped enum with json throws the same exception
// (the scalar comparison operators used to be noexcept, so this
// called std::terminate)
CHECK_THROWS_WITH_AS(static_cast<void>(strict_cards::andere == json("andere")), "[json.exception.out_of_range.410] enum value out of range for strict_cards", json::out_of_range&);
CHECK_THROWS_WITH_AS(static_cast<void>(json("andere") != strict_cards::andere), "[json.exception.out_of_range.410] enum value out of range for strict_cards", json::out_of_range&);
// invalid UTF-8 -> out_of_range.410, not the type_error.316 thrown while building the
// message (regression test for #5667); such strings can reach get<Enum>() unvalidated,
// e.g. from from_cbor()/from_msgpack() (#5529)
@@ -1792,12 +1804,21 @@ TEST_CASE("Strict JSON to enum mapping")
CHECK(json("completed").get<StrictTaskState>() == STRICT_TS_COMPLETED);
CHECK(json().get<StrictTaskState>() == STRICT_TS_INVALID);
// comparison of enum and json
CHECK(STRICT_TS_STOPPED == json("stopped"));
CHECK(STRICT_TS_RUNNING == json("running"));
CHECK(STRICT_TS_COMPLETED == json("completed"));
CHECK(STRICT_TS_INVALID == json());
// invalid json -> exception thrown
json _;
CHECK_THROWS_WITH_AS(_ = json("what?").get<StrictTaskState>(), "[json.exception.out_of_range.410] enum value out of range for StrictTaskState: \"what?\"", json::out_of_range&);
// conversion of unmapped enum -> exception thrown
CHECK_THROWS_WITH_AS(json(STRICT_TS_OTHER), "[json.exception.out_of_range.410] enum value out of range for StrictTaskState", json::out_of_range&);
// comparing an unmapped enum with json throws the same exception
CHECK_THROWS_WITH_AS(static_cast<void>(STRICT_TS_OTHER < json("x")), "[json.exception.out_of_range.410] enum value out of range for StrictTaskState", json::out_of_range&);
}
}
+9
View File
@@ -54,6 +54,15 @@ static_assert(noexcept(json(pod {})), "");
static_assert(noexcept(std::declval<json>().get<pod>()), "");
static_assert(!noexcept(std::declval<json>().get<pod_bis>()), "");
static_assert(noexcept(json(pod{})), "");
// comparing with a scalar is noexcept exactly when converting the scalar is
static_assert(noexcept(std::declval<const json&>() == 1), "");
static_assert(noexcept(1 != std::declval<const json&>()), "");
static_assert(noexcept(std::declval<const json&>() < 2.5), "");
static_assert(noexcept(nullptr == std::declval<const json&>()), "");
static_assert(!noexcept(std::declval<const json&>() == "foo"), "");
static_assert(!noexcept("foo" >= std::declval<const json&>()), "");
static_assert(noexcept(std::declval<const json&>() == std::declval<const json&>()), "");
} // namespace
TEST_CASE("noexcept")