From d585b6e86ee66048cfad871093ced4af3a3556a0 Mon Sep 17 00:00:00 2001 From: Eric Wieser Date: Mon, 11 Jul 2022 13:34:49 +0000 Subject: [PATCH 01/11] 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)); +} From 17f5b3f4c2619b3443c01492ea9109e8b1c442a3 Mon Sep 17 00:00:00 2001 From: Eric Wieser Date: Mon, 11 Jul 2022 17:02:00 +0100 Subject: [PATCH 02/11] decltype not typeof It's been too long since I wrote C++... --- include/nlohmann/detail/input/lexer.hpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/include/nlohmann/detail/input/lexer.hpp b/include/nlohmann/detail/input/lexer.hpp index 9809b4e89..f516dd7a2 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 (static_cast(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 (static_cast(value_integer) == x) + if (static_cast(value_integer) == x) { return token_type::value_integer; } From 3d0c1ba3ee3dd780641f96ba9a4ee9f09cbb3896 Mon Sep 17 00:00:00 2001 From: Eric Wieser Date: Mon, 11 Jul 2022 19:36:37 +0100 Subject: [PATCH 03/11] Update unit-custom-integer.cpp --- tests/src/unit-custom-integer.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/tests/src/unit-custom-integer.cpp b/tests/src/unit-custom-integer.cpp index 0e3baf440..101f76cca 100644 --- a/tests/src/unit-custom-integer.cpp +++ b/tests/src/unit-custom-integer.cpp @@ -94,11 +94,11 @@ template class std::numeric_limits> TEST_CASE("custom integer types") { - using json = nlohmann::basic_json < - std::map, std::vector, std::string, bool, - wrapped_int, wrapped_int, double, std::allocator >; + 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]"; - json as_json = json::parse(data.begin(), data.end()); + 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(2)); From fefa98702cb871ddcb26f1995ac9e01eacbb5662 Mon Sep 17 00:00:00 2001 From: Eric Wieser Date: Mon, 11 Jul 2022 21:30:23 +0000 Subject: [PATCH 04/11] roll back some parts of the change --- include/nlohmann/detail/output/serializer.hpp | 13 ++++++------ tests/src/unit-custom-integer.cpp | 20 ++++++------------- 2 files changed, 12 insertions(+), 21 deletions(-) diff --git a/include/nlohmann/detail/output/serializer.hpp b/include/nlohmann/detail/output/serializer.hpp index dc8a5ab50..418bccd45 100644 --- a/include/nlohmann/detail/output/serializer.hpp +++ b/include/nlohmann/detail/output/serializer.hpp @@ -678,7 +678,7 @@ class serializer template ::is_signed, int> = 0> bool is_negative_number(NumberType x) { - return x < NumberType(0); + return x < 0; } template < typename NumberType, enable_if_t < !std::numeric_limits::is_signed, int > = 0 > @@ -721,7 +721,7 @@ class serializer }; // special case for "0" - if (x == NumberType(0)) + if (x == 0) { o->write_character('0'); return; @@ -757,16 +757,15 @@ class serializer // Fast int2ascii implementation inspired by "Fastware" talk by Andrei Alexandrescu // See: https://www.youtube.com/watch?v=o4-CwDo2zpg - const NumberType hundred = 100; - while (abs_value >= hundred) + while (abs_value >= 100) { - const auto digits_index = static_cast((abs_value % hundred)); - abs_value /= hundred; + const auto digits_index = static_cast((abs_value % 100)); + abs_value /= 100; *(--buffer_ptr) = digits_to_99[digits_index][1]; *(--buffer_ptr) = digits_to_99[digits_index][0]; } - if (abs_value >= NumberType(10)) + if (abs_value >= 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 index 0e3baf440..bcf6fa398 100644 --- a/tests/src/unit-custom-integer.cpp +++ b/tests/src/unit-custom-integer.cpp @@ -43,7 +43,7 @@ class wrapped_int return val; } wrapped_int() = default; - wrapped_int(T val) : val(val) {} + /* implicit */ wrapped_int(T val) : val(val) {} bool operator==(const wrapped_int& other) const { @@ -59,37 +59,29 @@ class wrapped_int } bool operator%(const wrapped_int& other) const { - return static_cast(*this) % static_cast(other.val); + return static_cast(*this) % static_cast(other); } wrapped_int& operator/=(const wrapped_int& other) { - if (val != nullptr) - { - *val /= static_cast(other.val); - } + val /= static_cast(other); return *this; } bool operator<(const wrapped_int& other) const { - return static_cast(*this) < static_cast(other.val); + return static_cast(*this) < static_cast(other); } 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); + 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; - static constexpr bool is_specialized = std::numeric_limits::is_specialized; }; TEST_CASE("custom integer types") From 398fdd96dab26bac33dd284f7fb65c638a594ff7 Mon Sep 17 00:00:00 2001 From: Eric Wieser Date: Mon, 11 Jul 2022 21:37:07 +0000 Subject: [PATCH 05/11] docs --- docs/mkdocs/docs/features/types/number_handling.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/docs/mkdocs/docs/features/types/number_handling.md b/docs/mkdocs/docs/features/types/number_handling.md index 03d8c9c69..410689609 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. From c5075348e8d1053acd9583f4539960afaeafa058 Mon Sep 17 00:00:00 2001 From: Eric Wieser Date: Mon, 11 Jul 2022 21:51:00 +0000 Subject: [PATCH 06/11] better test casting --- tests/src/unit-custom-integer.cpp | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/tests/src/unit-custom-integer.cpp b/tests/src/unit-custom-integer.cpp index 9de1fda02..cfb945f9a 100644 --- a/tests/src/unit-custom-integer.cpp +++ b/tests/src/unit-custom-integer.cpp @@ -43,7 +43,11 @@ class wrapped_int return val; } wrapped_int() = default; - /* implicit */ wrapped_int(T val) : val(val) {} + explicit wrapped_int(T val) : val(val) {} + + // allow implicit conversions from anything that `T` allows conversions from + template::value>::type> + wrapped_int(T2 val) : val(val) {} bool operator==(const wrapped_int& other) const { From a11691680d405d34a86d4bbf8f2ae1214b662b6b Mon Sep 17 00:00:00 2001 From: Eric Wieser Date: Wed, 13 Jul 2022 07:45:35 +0100 Subject: [PATCH 07/11] Rename val to m_val --- tests/src/unit-custom-integer.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/src/unit-custom-integer.cpp b/tests/src/unit-custom-integer.cpp index cfb945f9a..3e810f472 100644 --- a/tests/src/unit-custom-integer.cpp +++ b/tests/src/unit-custom-integer.cpp @@ -36,14 +36,14 @@ using nlohmann::json; template class wrapped_int { + T m_val; public: - T val; operator T() const { - return val; + return m_val; } wrapped_int() = default; - explicit wrapped_int(T val) : val(val) {} + explicit wrapped_int(T val) : m_val(val) {} // allow implicit conversions from anything that `T` allows conversions from template::value>::type> From 2f8a6621c6925cdb9630c1800e2cbef57d6f9d54 Mon Sep 17 00:00:00 2001 From: Eric Wieser Date: Wed, 13 Jul 2022 07:46:38 +0100 Subject: [PATCH 08/11] Update unit-custom-integer.cpp --- tests/src/unit-custom-integer.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/src/unit-custom-integer.cpp b/tests/src/unit-custom-integer.cpp index 3e810f472..e5958eaef 100644 --- a/tests/src/unit-custom-integer.cpp +++ b/tests/src/unit-custom-integer.cpp @@ -97,6 +97,6 @@ TEST_CASE("custom integer types") 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(2)); + CHECK(i1 == wrapped_int(2u)); CHECK(i2 == wrapped_int(-3)); } From f77b8e5c418ab71fc2e899eb7b6e5c364a78cc59 Mon Sep 17 00:00:00 2001 From: Eric Wieser Date: Wed, 13 Jul 2022 07:47:57 +0100 Subject: [PATCH 09/11] Update unit-custom-integer.cpp --- tests/src/unit-custom-integer.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/src/unit-custom-integer.cpp b/tests/src/unit-custom-integer.cpp index e5958eaef..eab965548 100644 --- a/tests/src/unit-custom-integer.cpp +++ b/tests/src/unit-custom-integer.cpp @@ -47,7 +47,7 @@ class wrapped_int // allow implicit conversions from anything that `T` allows conversions from template::value>::type> - wrapped_int(T2 val) : val(val) {} + wrapped_int(T2 val) : m_val(val) {} bool operator==(const wrapped_int& other) const { From ad4da95ca15651192a7874cce7e64db2702b5a06 Mon Sep 17 00:00:00 2001 From: Eric Wieser Date: Wed, 13 Jul 2022 06:53:14 +0000 Subject: [PATCH 10/11] restrict implicit constructor --- tests/src/unit-custom-integer.cpp | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/tests/src/unit-custom-integer.cpp b/tests/src/unit-custom-integer.cpp index eab965548..9978f2520 100644 --- a/tests/src/unit-custom-integer.cpp +++ b/tests/src/unit-custom-integer.cpp @@ -45,8 +45,9 @@ class wrapped_int wrapped_int() = default; explicit wrapped_int(T val) : m_val(val) {} - // allow implicit conversions from anything that `T` allows conversions from - template::value>::type> + // 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(val) {} bool operator==(const wrapped_int& other) const @@ -67,7 +68,7 @@ class wrapped_int } wrapped_int& operator/=(const wrapped_int& other) { - val /= static_cast(other); + m_val /= static_cast(other); return *this; } bool operator<(const wrapped_int& other) const From 876de479530cd111ffd08b2957a172d562c3837d Mon Sep 17 00:00:00 2001 From: Eric Wieser Date: Wed, 13 Jul 2022 08:57:31 +0100 Subject: [PATCH 11/11] Squash a warning --- tests/src/unit-custom-integer.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/src/unit-custom-integer.cpp b/tests/src/unit-custom-integer.cpp index 9978f2520..e6c553bf1 100644 --- a/tests/src/unit-custom-integer.cpp +++ b/tests/src/unit-custom-integer.cpp @@ -48,7 +48,7 @@ class wrapped_int // 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(val) {} + wrapped_int(T2 val) : m_val(static_cast(val)) {} bool operator==(const wrapped_int& other) const {