From d585b6e86ee66048cfad871093ced4af3a3556a0 Mon Sep 17 00:00:00 2001 From: Eric Wieser Date: Mon, 11 Jul 2022 13:34:49 +0000 Subject: [PATCH] Allow custom number types to be used The previous type traits were not possible to extend with custom types, as attempting to do so is undefined behavior. --- .../nlohmann/detail/conversions/from_json.hpp | 2 +- include/nlohmann/detail/input/lexer.hpp | 4 +- include/nlohmann/detail/meta/type_traits.hpp | 4 +- include/nlohmann/detail/output/serializer.hpp | 19 ++-- single_include/nlohmann/json.hpp | 29 ++--- tests/src/unit-custom-integer.cpp | 106 ++++++++++++++++++ 6 files changed, 136 insertions(+), 28 deletions(-) create mode 100644 tests/src/unit-custom-integer.cpp diff --git a/include/nlohmann/detail/conversions/from_json.hpp b/include/nlohmann/detail/conversions/from_json.hpp index ed4e6de5f..fcdd37dc0 100644 --- a/include/nlohmann/detail/conversions/from_json.hpp +++ b/include/nlohmann/detail/conversions/from_json.hpp @@ -50,7 +50,7 @@ inline void from_json(const BasicJsonType& j, typename std::nullptr_t& n) // 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 d0c063a1c..9809b4e89 100644 --- a/include/nlohmann/detail/input/lexer.hpp +++ b/include/nlohmann/detail/input/lexer.hpp @@ -1250,7 +1250,7 @@ scan_number_done: if (errno == 0) { value_unsigned = static_cast(x); - if (value_unsigned == x) + if (static_cast(value_unsigned) == x) { return token_type::value_unsigned; } @@ -1266,7 +1266,7 @@ scan_number_done: if (errno == 0) { 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 f87d11d90..d5954d4b1 100644 --- a/include/nlohmann/detail/meta/type_traits.hpp +++ b/include/nlohmann/detail/meta/type_traits.hpp @@ -432,8 +432,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 b6349ea8f..dc8a5ab50 100644 --- a/include/nlohmann/detail/output/serializer.hpp +++ b/include/nlohmann/detail/output/serializer.hpp @@ -675,13 +675,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; @@ -697,7 +697,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, @@ -721,7 +721,7 @@ class serializer }; // special case for "0" - if (x == 0) + if (x == NumberType(0)) { o->write_character('0'); return; @@ -757,15 +757,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/single_include/nlohmann/json.hpp b/single_include/nlohmann/json.hpp index cffbd7dce..08acdbbc1 100644 --- a/single_include/nlohmann/json.hpp +++ b/single_include/nlohmann/json.hpp @@ -3627,8 +3627,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? @@ -4302,7 +4302,7 @@ inline void from_json(const BasicJsonType& j, typename std::nullptr_t& n) // 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) @@ -8193,7 +8193,7 @@ scan_number_done: if (errno == 0) { value_unsigned = static_cast(x); - if (value_unsigned == x) + if (static_cast(value_unsigned) == x) { return token_type::value_unsigned; } @@ -8209,7 +8209,7 @@ scan_number_done: if (errno == 0) { value_integer = static_cast(x); - if (value_integer == x) + if (static_cast(value_integer) == x) { return token_type::value_integer; } @@ -17948,13 +17948,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; @@ -17970,7 +17970,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, @@ -17994,7 +17994,7 @@ class serializer }; // special case for "0" - if (x == 0) + if (x == NumberType(0)) { o->write_character('0'); return; @@ -18030,15 +18030,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..0e3baf440 --- /dev/null +++ b/tests/src/unit-custom-integer.cpp @@ -0,0 +1,106 @@ +/* + __ _____ _____ _____ + __| | __| | | | 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 +{ + public: + T val; + operator T() const + { + return val; + } + wrapped_int() = default; + wrapped_int(T val) : val(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.val); + } + wrapped_int& operator/=(const wrapped_int& other) + { + if (val != nullptr) + { + *val /= static_cast(other.val); + } + return *this; + } + bool operator<(const wrapped_int& other) const + { + return static_cast(*this) < static_cast(other.val); + } + bool operator<=(const wrapped_int& other) const + { + return static_cast(*this) <= static_cast(other.val); + } + + friend void swap(wrapped_int& self, wrapped_int& other) + { + swap(self.val, other.val); + } +}; + +template class std::numeric_limits> +{ + public: + static constexpr bool is_signed = std::numeric_limits::is_signed; + static constexpr bool is_integer = std::numeric_limits::is_integer; + static constexpr bool is_specialized = std::numeric_limits::is_specialized; +}; + +TEST_CASE("custom integer types") +{ + using 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]"; + json as_json = json::parse(data.begin(), data.end()); + wrapped_int i1 = as_json[1]; + wrapped_int i2 = as_json[2]; + CHECK(i1 == wrapped_int(2)); + CHECK(i2 == wrapped_int(-3)); +}