Allow the same type for number_integer_t and number_unsigned_t (#5815)

* 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>

* Use json::with_integers_t in the test and the docs

Signed-off-by: Niels Lohmann <mail@nlohmann.me>

* ✅ skip patch test on GCC 5 to avoid internal compiler error

Signed-off-by: Niels Lohmann <mail@nlohmann.me>

* Fix CI: name the unsigned overload type through template parameters

basic_json used number_unsigned_overload_t<basic_json> while the class was
still incomplete. MSVC rejected it as a recursive alias declaration and GCC
4.9, 5 and 7 crashed with an internal compiler error. basic_json now uses
number_unsigned_overload_type<NumberIntegerType, NumberUnsignedType>, which
also makes the GCC 5 workaround in the patch test unnecessary.

Infer reported a null dereference for the number_unsigned case in
from_json, which cannot happen for a basic_json whose number_unsigned_t is
number_integer_t; assert it.

Signed-off-by: Niels Lohmann <mail@nlohmann.me>

* Address review: conjunction/negation trait, astyle-friendly alias, docs table order

Signed-off-by: Niels Lohmann <mail@nlohmann.me>

---------

Signed-off-by: Niels Lohmann <mail@nlohmann.me>
This commit is contained in:
Niels Lohmann authored and GitHub committed 2026-10-15 20:04:42 +02:00
1 parent 6261d1e432
commit ce28c722bc
13 files changed
+460 -92

No files matched your search

@@ -685,6 +685,22 @@ struct is_compatible_integer_type
: is_compatible_integer_type_impl<RealIntegerType,
CompatibleNumberIntegerType> {};
// whether to_json() stores an integer of type CompatibleNumberIntegerType as
// number_integer
template<typename BasicJsonType, typename CompatibleNumberIntegerType, typename = void>
struct is_compatible_number_integer_type
: is_compatible_integer_type<typename BasicJsonType::number_integer_t, CompatibleNumberIntegerType> {};
// if number_unsigned_t is the same type as number_integer_t, unsigned integers
// are stored as number_integer as well (#2573)
template<typename BasicJsonType, typename CompatibleNumberIntegerType>
struct is_compatible_number_integer_type<BasicJsonType, CompatibleNumberIntegerType,
enable_if_t<conjunction<std::is_same<typename BasicJsonType::number_integer_t, typename BasicJsonType::number_unsigned_t>,
std::is_integral<CompatibleNumberIntegerType>,
std::is_unsigned<CompatibleNumberIntegerType>,
negation<std::is_same<bool, CompatibleNumberIntegerType>>>::value>>
: std::is_constructible<typename BasicJsonType::number_integer_t, CompatibleNumberIntegerType> {};
template<typename BasicJsonType, typename CompatibleType, typename = void>
struct is_compatible_type_impl: std::false_type {};
@@ -1047,6 +1063,25 @@ constexpr bool value_in_range_of(T val)
template<bool Value>
using bool_constant = std::integral_constant<bool, Value>;
// Stands in for number_unsigned_t in overload sets if it is the same type as
// number_integer_t, so that the unsigned overloads do not collide with the
// integer ones. Never constructed. Such a basic_json stores every integer as
// number_integer (#2573).
struct same_as_number_integer_type {};
// NumberUnsignedType, or same_as_number_integer_type if it is the same type as
// NumberIntegerType; basic_json uses this one, as it cannot name its own member
// types through an incomplete basic_json (MSVC reports a recursive alias, and
// old GCC versions crash)
template<typename NumberIntegerType, typename NumberUnsignedType>
using number_unsigned_overload_type = typename std::conditional<std::is_same<NumberIntegerType, NumberUnsignedType>::value,
same_as_number_integer_type, NumberUnsignedType>::type;
// number_unsigned_t of BasicJsonType, or same_as_number_integer_type if it is
// the same type as number_integer_t
template<typename BasicJsonType>
using number_unsigned_overload_t = number_unsigned_overload_type<typename BasicJsonType::number_integer_t, typename BasicJsonType::number_unsigned_t>;
///////////////////////////////////////////////////////////////////////////////
// is_c_string
///////////////////////////////////////////////////////////////////////////////