diff --git a/docs/mkdocs/docs/features/types/number_handling.md b/docs/mkdocs/docs/features/types/number_handling.md index 3dcca76a4..92b0d25d9 100644 --- a/docs/mkdocs/docs/features/types/number_handling.md +++ b/docs/mkdocs/docs/features/types/number_handling.md @@ -311,6 +311,9 @@ The number types can be changed with template parameters. in case of overflow. - The type for unsigned integers must be convertible from `#!c unsigned long long`. The type for floating-point numbers is used in case of overflow. + - Custom `struct`s and `class`es are supported for signed and unsigned integers, providing they are trivially + default-constructible, move-constructible, and destructible. This enables extended- but not dynamic-length + integers. - The types for signed and unsigned integers must be distinct, see [#2573](https://github.com/nlohmann/json/issues/2573). - Only `#!c double`, `#!c float`, and `#!c long double` are supported for floating-point numbers. diff --git a/include/nlohmann/detail/conversions/from_json.hpp b/include/nlohmann/detail/conversions/from_json.hpp index b07be6060..b4d4e9a5f 100644 --- a/include/nlohmann/detail/conversions/from_json.hpp +++ b/include/nlohmann/detail/conversions/from_json.hpp @@ -66,7 +66,7 @@ void from_json(const BasicJsonType& j, std::optional& opt) // overloads for basic_json template parameters template < typename BasicJsonType, typename ArithmeticType, - enable_if_t < std::is_arithmetic::value&& + enable_if_t < std::numeric_limits::is_specialized&& !std::is_same::value, int > = 0 > void get_arithmetic_value(const BasicJsonType& j, ArithmeticType& val) diff --git a/include/nlohmann/detail/input/lexer.hpp b/include/nlohmann/detail/input/lexer.hpp index b1cf3dac4..8aac2c961 100644 --- a/include/nlohmann/detail/input/lexer.hpp +++ b/include/nlohmann/detail/input/lexer.hpp @@ -1261,7 +1261,7 @@ scan_number_done: if (errno != ERANGE) { value_unsigned = static_cast(x); - if (value_unsigned == x) + if (static_cast(value_unsigned) == x) { return token_type::value_unsigned; } @@ -1277,7 +1277,7 @@ scan_number_done: if (errno != ERANGE) { value_integer = static_cast(x); - if (value_integer == x) + if (static_cast(value_integer) == x) { return token_type::value_integer; } diff --git a/include/nlohmann/detail/meta/type_traits.hpp b/include/nlohmann/detail/meta/type_traits.hpp index 678fac569..4a411054a 100644 --- a/include/nlohmann/detail/meta/type_traits.hpp +++ b/include/nlohmann/detail/meta/type_traits.hpp @@ -497,8 +497,8 @@ struct is_compatible_integer_type_impl : std::false_type {}; template struct is_compatible_integer_type_impl < RealIntegerType, CompatibleNumberIntegerType, - enable_if_t < std::is_integral::value&& - std::is_integral::value&& + enable_if_t < std::numeric_limits::is_integer&& + std::numeric_limits::is_integer&& !std::is_same::value >> { // is there an assert somewhere on overflows? diff --git a/include/nlohmann/detail/output/serializer.hpp b/include/nlohmann/detail/output/serializer.hpp index c2823487e..d7a445dc6 100644 --- a/include/nlohmann/detail/output/serializer.hpp +++ b/include/nlohmann/detail/output/serializer.hpp @@ -684,13 +684,13 @@ class serializer } // templates to avoid warnings about useless casts - template ::value, int> = 0> + template ::is_signed, int> = 0> bool is_negative_number(NumberType x) { return x < 0; } - template < typename NumberType, enable_if_t ::value, int > = 0 > + template < typename NumberType, enable_if_t < !std::numeric_limits::is_signed, int > = 0 > bool is_negative_number(NumberType /*unused*/) { return false; @@ -706,7 +706,7 @@ class serializer @tparam NumberType either @a number_integer_t or @a number_unsigned_t */ template < typename NumberType, detail::enable_if_t < - std::is_integral::value || + std::numeric_limits::is_integer || std::is_same::value || std::is_same::value || std::is_same::value, diff --git a/single_include/nlohmann/json.hpp b/single_include/nlohmann/json.hpp index 07dea3c4a..619d43cfa 100644 --- a/single_include/nlohmann/json.hpp +++ b/single_include/nlohmann/json.hpp @@ -4032,8 +4032,8 @@ struct is_compatible_integer_type_impl : std::false_type {}; template struct is_compatible_integer_type_impl < RealIntegerType, CompatibleNumberIntegerType, - enable_if_t < std::is_integral::value&& - std::is_integral::value&& + enable_if_t < std::numeric_limits::is_integer&& + std::numeric_limits::is_integer&& !std::is_same::value >> { // is there an assert somewhere on overflows? @@ -4846,7 +4846,7 @@ void from_json(const BasicJsonType& j, std::optional& opt) // overloads for basic_json template parameters template < typename BasicJsonType, typename ArithmeticType, - enable_if_t < std::is_arithmetic::value&& + enable_if_t < std::numeric_limits::is_specialized&& !std::is_same::value, int > = 0 > void get_arithmetic_value(const BasicJsonType& j, ArithmeticType& val) @@ -8268,7 +8268,7 @@ scan_number_done: if (errno != ERANGE) { value_unsigned = static_cast(x); - if (value_unsigned == x) + if (static_cast(value_unsigned) == x) { return token_type::value_unsigned; } @@ -8284,7 +8284,7 @@ scan_number_done: if (errno != ERANGE) { value_integer = static_cast(x); - if (value_integer == x) + if (static_cast(value_integer) == x) { return token_type::value_integer; } @@ -19390,13 +19390,13 @@ class serializer } // templates to avoid warnings about useless casts - template ::value, int> = 0> + template ::is_signed, int> = 0> bool is_negative_number(NumberType x) { - return x < 0; + return x < NumberType(0); } - template < typename NumberType, enable_if_t ::value, int > = 0 > + template < typename NumberType, enable_if_t < !std::numeric_limits::is_signed, int > = 0 > bool is_negative_number(NumberType /*unused*/) { return false; @@ -19412,7 +19412,7 @@ class serializer @tparam NumberType either @a number_integer_t or @a number_unsigned_t */ template < typename NumberType, detail::enable_if_t < - std::is_integral::value || + std::numeric_limits::is_integer || std::is_same::value || std::is_same::value || std::is_same::value, @@ -19436,7 +19436,7 @@ class serializer }; // special case for "0" - if (x == 0) + if (x == NumberType(0)) { o->write_character('0'); return; @@ -19472,15 +19472,16 @@ class serializer // Fast int2ascii implementation inspired by "Fastware" talk by Andrei Alexandrescu // See: https://www.youtube.com/watch?v=o4-CwDo2zpg - while (abs_value >= 100) + const NumberType hundred = 100; + while (abs_value >= hundred) { - const auto digits_index = static_cast((abs_value % 100)); - abs_value /= 100; + const auto digits_index = static_cast((abs_value % hundred)); + abs_value /= hundred; *(--buffer_ptr) = digits_to_99[digits_index][1]; *(--buffer_ptr) = digits_to_99[digits_index][0]; } - if (abs_value >= 10) + if (abs_value >= NumberType(10)) { const auto digits_index = static_cast(abs_value); *(--buffer_ptr) = digits_to_99[digits_index][1]; diff --git a/tests/src/unit-custom-integer.cpp b/tests/src/unit-custom-integer.cpp new file mode 100644 index 000000000..e6c553bf1 --- /dev/null +++ b/tests/src/unit-custom-integer.cpp @@ -0,0 +1,103 @@ +/* + __ _____ _____ _____ + __| | __| | | | JSON for Modern C++ (test suite) +| | |__ | | | | | | version 3.10.5 +|_____|_____|_____|_|___| https://github.com/nlohmann/json + +Licensed under the MIT License . +SPDX-License-Identifier: MIT +Copyright (c) 2013-2022 Niels Lohmann . + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +*/ + +#include "doctest_compatibility.h" + +#include +using nlohmann::json; + +/// A wrapped integer +template +class wrapped_int +{ + T m_val; + public: + operator T() const + { + return m_val; + } + wrapped_int() = default; + explicit wrapped_int(T val) : m_val(val) {} + + // allow implicit conversions from any builtin types that `T` allows conversions from + template::value && std::is_arithmetic::value>::type> + wrapped_int(T2 val) : m_val(static_cast(val)) {} + + bool operator==(const wrapped_int& other) const + { + return static_cast(*this) == static_cast(other); + } + bool operator<(const int& other) const + { + return static_cast(*this) < other; + } + wrapped_int operator+(const wrapped_int& other) const + { + return static_cast(*this) + static_cast(other); + } + bool operator%(const wrapped_int& other) const + { + return static_cast(*this) % static_cast(other); + } + wrapped_int& operator/=(const wrapped_int& other) + { + m_val /= static_cast(other); + return *this; + } + bool operator<(const wrapped_int& other) const + { + return static_cast(*this) < static_cast(other); + } + bool operator<=(const wrapped_int& other) const + { + return static_cast(*this) <= static_cast(other); + } +}; + +template class std::numeric_limits> +{ + public: + static constexpr bool is_specialized = std::numeric_limits::is_specialized; + static constexpr bool is_signed = std::numeric_limits::is_signed; + static constexpr bool is_integer = std::numeric_limits::is_integer; +}; + +TEST_CASE("custom integer types") +{ + using my_json = nlohmann::basic_json < + std::map, std::vector, std::string, bool, + wrapped_int, wrapped_int, double, std::allocator >; + std::string data = "[1,2,-3,-4]"; + my_json as_json = my_json::parse(data.begin(), data.end()); + wrapped_int i1 = as_json[1]; + wrapped_int i2 = as_json[2]; + CHECK(i1 == wrapped_int(2u)); + CHECK(i2 == wrapped_int(-3)); +}