Allow the same type for number_integer_t and number_unsigned_t

basic_json did not compile with NumberUnsignedType == NumberIntegerType
because overloads on both types collided. Such a basic_json now stores
every integer as number_integer: the colliding overloads take a
placeholder type, unsigned C++ integers are converted to number_integer_t,
and value_t::number_unsigned maps to number_integer.

Also fixes two problems this configuration would hit: the unsigned
integer parser accepted values that wrap around to negative numbers, and
dump() overflowed when computing the absolute value of the minimum.

A repeated NumberFloatType gets a static_assert.

Refs #2573

Signed-off-by: Niels Lohmann <mail@nlohmann.me>
This commit is contained in:
Niels Lohmann committed 2026-10-13 18:57:07 +02:00
1 parent e839bbd1c0
commit 45b837885e
13 files changed
+430 -85

No files matched your search

+24 -8
View File
@@ -500,8 +500,22 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
"NumberUnsignedType must be at least as wide as NumberIntegerType, "
"because it has to hold the absolute value of every NumberIntegerType value");
// NumberUnsignedType may be the same type as NumberIntegerType (#2573), but
// a repeated NumberFloatType would give overloads that collide, so it gets
// a readable diagnostic instead.
static_assert(!std::is_same<NumberFloatType, NumberIntegerType>::value && !std::is_same<NumberFloatType, NumberUnsignedType>::value,
"NumberFloatType must be a different type than NumberIntegerType and NumberUnsignedType");
private:
/// the kind a value of kind @a t is stored as: if number_unsigned_t is the
/// same type as number_integer_t, every integer is stored as number_integer
/// (#2573); every other kind is stored as itself
static constexpr value_t stored_kind(const value_t t) noexcept
{
return (std::is_same<number_integer_t, number_unsigned_t>::value && t == value_t::number_unsigned) ? value_t::number_integer : t;
}
/// helper for exception-safe object creation
template<typename T, typename... Args>
JSON_HEDLEY_RETURNS_NON_NULL
@@ -575,8 +589,8 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
json_value(boolean_t v) noexcept : boolean(v) {}
/// constructor for numbers (integer)
json_value(number_integer_t v) noexcept : number_integer(v) {}
/// constructor for numbers (unsigned)
json_value(number_unsigned_t v) noexcept : number_unsigned(v) {}
/// constructor for numbers (unsigned); takes a placeholder if number_unsigned_t is number_integer_t
json_value(detail::number_unsigned_overload_t<basic_json> v) noexcept : number_unsigned(v) {}
/// constructor for numbers (floating-point)
json_value(number_float_t v) noexcept : number_float(v) {}
/// constructor for empty values of a given type
@@ -2181,7 +2195,7 @@ public:
/// @brief create an empty value with a given type
/// @sa https://json.nlohmann.me/api/basic_json/basic_json/
basic_json(const value_t v)
: m_data(v)
: m_data(stored_kind(v))
{
assert_invariant();
}
@@ -2262,7 +2276,7 @@ public:
{
convert_leaf(val);
}
JSON_ASSERT(m_data.m_type == val.type());
JSON_ASSERT(m_data.m_type == stored_kind(val.type()));
set_parents();
assert_invariant();
@@ -2847,14 +2861,16 @@ public:
return m_data.m_type == value_t::number_integer ? &m_data.m_value.number_integer : nullptr;
}
/// get a pointer to the value (unsigned number)
number_unsigned_t* get_impl_ptr(number_unsigned_t* /*unused*/) noexcept
/// get a pointer to the value (unsigned number); takes a placeholder if
/// number_unsigned_t is number_integer_t (the integer overload handles it then)
number_unsigned_t* get_impl_ptr(detail::number_unsigned_overload_t<basic_json>* /*unused*/) noexcept
{
return is_number_unsigned() ? &m_data.m_value.number_unsigned : nullptr;
}
/// get a pointer to the value (unsigned number)
constexpr const number_unsigned_t* get_impl_ptr(const number_unsigned_t* /*unused*/) const noexcept
/// get a pointer to the value (unsigned number); takes a placeholder if
/// number_unsigned_t is number_integer_t (the integer overload handles it then)
constexpr const number_unsigned_t* get_impl_ptr(const detail::number_unsigned_overload_t<basic_json>* /*unused*/) const noexcept
{
return is_number_unsigned() ? &m_data.m_value.number_unsigned : nullptr;
}