diff --git a/include/nlohmann/detail/bit_ops.hpp b/include/nlohmann/detail/bit_ops.hpp index c9424a6d1..fdc9c3435 100644 --- a/include/nlohmann/detail/bit_ops.hpp +++ b/include/nlohmann/detail/bit_ops.hpp @@ -14,11 +14,24 @@ #include // __umulh, _umul128, _BitScanForward64, _BitScanReverse64 #endif +#ifdef _MSC_VER + #include // _byteswap_ushort, _byteswap_ulong, _byteswap_uint64 +#endif +#if defined(_MSC_VER) && !defined(__clang__) && (defined(_M_IX86) || defined(_M_ARM)) + #include // _BitScanForward, _BitScanReverse +#endif + #include // JSON_HEDLEY_ALWAYS_INLINE, NLOHMANN_JSON_NAMESPACE_BEGIN +#if defined(__cpp_lib_bit_cast) || defined(__cpp_lib_bitops) || defined(__cpp_lib_byteswap) + #include // bit_cast, byteswap, countl_zero, countr_zero +#endif + // Portable bit-level helpers for the number and string scanners. They use -// compiler builtins or platform-specific intrinsics where available and plain -// C++ otherwise, so they work regardless of byte order. +// compiler builtins or platform-specific intrinsics where available, the +// C++20 functions next, and plain C++ otherwise, so they work regardless +// of byte order. (The builtins and intrinsics come first: MSVC's +// std::countl_zero checks for LZCNT at run time unless AVX2 is enabled.) NLOHMANN_JSON_NAMESPACE_BEGIN namespace detail @@ -33,6 +46,16 @@ inline int count_leading_zeros(std::uint64_t x) noexcept unsigned long index = 0; // NOLINT(runtime/int): the type _BitScan*64 takes _BitScanReverse64(&index, x); return 63 - static_cast(index); +#elif defined(_MSC_VER) && (defined(_M_IX86) || defined(_M_ARM)) + unsigned long index = 0; // NOLINT(runtime/int): the type _BitScan* takes + if (_BitScanReverse(&index, static_cast(x >> 32u))) // NOLINT(runtime/int) + { + return 31 - static_cast(index); + } + _BitScanReverse(&index, static_cast(x)); // NOLINT(runtime/int) + return 63 - static_cast(index); +#elif defined(__cpp_lib_bitops) + return std::countl_zero(x); #else int n = 0; for (int shift = 32; shift != 0; shift >>= 1) @@ -56,6 +79,16 @@ inline int count_trailing_zeros(std::uint64_t x) noexcept unsigned long index = 0; // NOLINT(runtime/int): the type _BitScan*64 takes _BitScanForward64(&index, x); return static_cast(index); +#elif defined(_MSC_VER) && (defined(_M_IX86) || defined(_M_ARM)) + unsigned long index = 0; // NOLINT(runtime/int): the type _BitScan* takes + if (_BitScanForward(&index, static_cast(x))) // NOLINT(runtime/int) + { + return static_cast(index); + } + _BitScanForward(&index, static_cast(x >> 32u)); // NOLINT(runtime/int) + return 32 + static_cast(index); +#elif defined(__cpp_lib_bitops) + return std::countr_zero(x); #else int n = 0; for (int shift = 32; shift != 0; shift >>= 1) @@ -70,6 +103,67 @@ inline int count_trailing_zeros(std::uint64_t x) noexcept #endif } +/// the object representation of @a from as a @a To (std::bit_cast in C++20) +template +JSON_HEDLEY_ALWAYS_INLINE To bit_cast(const From& from) noexcept +{ + static_assert(sizeof(To) == sizeof(From), "bit_cast needs types of the same size"); +#if defined(__cpp_lib_bit_cast) + return std::bit_cast(from); +#else + To to; + std::memcpy(&to, &from, sizeof(To)); + return to; +#endif +} + +/// x with its bytes in reverse order (std::byteswap in C++23); compilers lower +/// it to a single bswap/rev/movbe +inline std::uint16_t byteswap(std::uint16_t x) noexcept +{ +#if defined(__GNUC__) || defined(__clang__) + return __builtin_bswap16(x); +#elif defined(_MSC_VER) + return _byteswap_ushort(x); +#elif defined(__cpp_lib_byteswap) + return std::byteswap(x); +#else + return static_cast((x >> 8u) | (x << 8u)); +#endif +} + +/// x with its bytes in reverse order (std::byteswap in C++23) +inline std::uint32_t byteswap(std::uint32_t x) noexcept +{ +#if defined(__GNUC__) || defined(__clang__) + return __builtin_bswap32(x); +#elif defined(_MSC_VER) + return _byteswap_ulong(x); +#elif defined(__cpp_lib_byteswap) + return std::byteswap(x); +#else + return ((x & 0x000000FFu) << 24u) | ((x & 0x0000FF00u) << 8u) + | ((x & 0x00FF0000u) >> 8u) | ((x & 0xFF000000u) >> 24u); +#endif +} + +/// x with its bytes in reverse order (std::byteswap in C++23) +inline std::uint64_t byteswap(std::uint64_t x) noexcept +{ +#if defined(__GNUC__) || defined(__clang__) + return __builtin_bswap64(x); +#elif defined(_MSC_VER) + return _byteswap_uint64(x); +#elif defined(__cpp_lib_byteswap) + return std::byteswap(x); +#else + x = ((x & 0x00000000FFFFFFFFull) << 32u) | ((x & 0xFFFFFFFF00000000ull) >> 32u); + x = ((x & 0x0000FFFF0000FFFFull) << 16u) | ((x & 0xFFFF0000FFFF0000ull) >> 16u); + x = ((x & 0x00FF00FF00FF00FFull) << 8u) | ((x & 0xFF00FF00FF00FF00ull) >> 8u); + return x; +#endif +} + /// the 128-bit product of two 64-bit numbers struct uint128_parts { diff --git a/include/nlohmann/detail/conversions/to_chars.hpp b/include/nlohmann/detail/conversions/to_chars.hpp index 48f86cf91..6cf81bf3e 100644 --- a/include/nlohmann/detail/conversions/to_chars.hpp +++ b/include/nlohmann/detail/conversions/to_chars.hpp @@ -18,9 +18,6 @@ #include // numeric_limits #include // conditional -#ifdef _MSC_VER - #include // _byteswap_uint64 -#endif // SSE2 (every x86-64 CPU) and NEON (every 64-bit Arm CPU) convert the 16 // digits of a double at once @@ -37,6 +34,7 @@ #define JSON_DTOA_NEON 0 #endif +#include #include #include @@ -66,16 +64,6 @@ For a detailed description of the algorithm see: namespace dtoa_impl { -template -Target reinterpret_bits(const Source source) -{ - static_assert(sizeof(Target) == sizeof(Source), "size mismatch"); - - Target target; - std::memcpy(&target, &source, sizeof(Source)); - return target; -} - struct diyfp // f * 2^e { static constexpr int kPrecision = 64; // = q @@ -230,7 +218,7 @@ boundaries compute_boundaries(FloatType value) using bits_type = typename std::conditional::type; - const auto bits = static_cast(reinterpret_bits(value)); + const auto bits = static_cast(bit_cast(value)); const std::uint64_t E = bits >> (kPrecision - 1); const std::uint64_t F = bits & (kHiddenBit - 1); @@ -1084,14 +1072,11 @@ inline std::uint64_t eight_digit_bytes(std::uint64_t abcdefgh) noexcept /// the byte stores once this is inlined) inline void store_msb_first(char* p, std::uint64_t v) noexcept { -#if defined(__BYTE_ORDER__) && defined(__ORDER_LITTLE_ENDIAN__) && __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__ - v = __builtin_bswap64(v); +#if (defined(__BYTE_ORDER__) && defined(__ORDER_LITTLE_ENDIAN__) && __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__) || defined(_MSC_VER) // (MSVC: little-endian on all its targets) + v = byteswap(v); std::memcpy(p, &v, sizeof(v)); #elif defined(__BYTE_ORDER__) && defined(__ORDER_BIG_ENDIAN__) && __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__ std::memcpy(p, &v, sizeof(v)); -#elif defined(_MSC_VER) // (little-endian on all its targets) - v = _byteswap_uint64(v); - std::memcpy(p, &v, sizeof(v)); #else for (unsigned i = 0; i < 8; ++i) { @@ -1462,8 +1447,7 @@ char* write_positive_zmij(char* first, const char* last, FloatType value) { static_assert(is_binary64::value, "internal error: the conversion of Zmij needs IEEE 754 binary64 numbers"); - std::uint64_t bits = 0; - std::memcpy(&bits, &value, sizeof(bits)); + const auto bits = bit_cast(value); const zmij::shortest_decimal d = zmij::to_shortest(bits); if (JSON_HEDLEY_LIKELY(last - first >= 41)) { diff --git a/include/nlohmann/detail/input/number_parse.hpp b/include/nlohmann/detail/input/number_parse.hpp index 8a9a9f30a..dce0105de 100644 --- a/include/nlohmann/detail/input/number_parse.hpp +++ b/include/nlohmann/detail/input/number_parse.hpp @@ -16,7 +16,6 @@ #include // size_t #include // int64_t, uint64_t #include // strtof, strtod, strtold -#include // memcpy #include // numeric_limits #include // string #include // conditional, integral_constant, is_signed, true_type, false_type @@ -758,15 +757,14 @@ std::uint64_t digit_comparison(const char* first, const char* last, std::uint64_ inline void float_from_bits(std::uint64_t bits, double& value) noexcept { static_assert(sizeof(double) == sizeof(std::uint64_t), "double must have 64 bits"); - std::memcpy(&value, &bits, sizeof(value)); + value = bit_cast(bits); } /// the float with the IEEE-754 bits @a bits (the lower 32) inline void float_from_bits(std::uint64_t bits, float& value) noexcept { static_assert(sizeof(float) == sizeof(std::uint32_t), "float must have 32 bits"); - const auto bits32 = static_cast(bits); - std::memcpy(&value, &bits32, sizeof(bits32)); + value = bit_cast(static_cast(bits)); } /// the powers of ten that are exact in binary64 (up to 10^22) diff --git a/include/nlohmann/detail/meta/cpp_future.hpp b/include/nlohmann/detail/meta/cpp_future.hpp index 3e012b341..2dd24e0f7 100644 --- a/include/nlohmann/detail/meta/cpp_future.hpp +++ b/include/nlohmann/detail/meta/cpp_future.hpp @@ -10,17 +10,26 @@ #pragma once #include // size_t -#include // conditional, enable_if, false_type, integral_constant, is_constructible, is_integral, is_same, remove_cv, remove_reference, true_type +#include // conditional, enable_if, false_type, integral_constant, is_arithmetic, is_constructible, is_integral, is_same, remove_cv, remove_reference, true_type #include // index_sequence, make_index_sequence, index_sequence_for #include +#ifdef JSON_HAS_CPP_17 + #include // clamp +#endif + NLOHMANN_JSON_NAMESPACE_BEGIN namespace detail { -template -using uncvref_t = typename std::remove_cv::type>::type; +#ifdef __cpp_lib_remove_cvref + template + using uncvref_t = std::remove_cvref_t; +#else + template + using uncvref_t = typename std::remove_cv::type>::type; +#endif #ifdef JSON_HAS_CPP_14 @@ -160,5 +169,18 @@ struct static_const constexpr T static_const::value; #endif +/// @a v bounded to [@a lo, @a hi] (requires !(hi < lo)); like std::clamp, +/// but by value, so that static constexpr members can be passed without an +/// out-of-line definition before C++17 +template::value, int> = 0> +constexpr T clamp(T v, T lo, T hi) noexcept +{ +#ifdef __cpp_lib_clamp + return std::clamp(v, lo, hi); +#else + return v < lo ? lo : (hi < v ? hi : v); +#endif +} + } // namespace detail NLOHMANN_JSON_NAMESPACE_END diff --git a/include/nlohmann/detail/meta/type_traits.hpp b/include/nlohmann/detail/meta/type_traits.hpp index 397bb6287..387691b6b 100644 --- a/include/nlohmann/detail/meta/type_traits.hpp +++ b/include/nlohmann/detail/meta/type_traits.hpp @@ -313,6 +313,14 @@ struct char_traits : std::char_traits // is_ functions // /////////////////// +#ifdef __cpp_lib_logical_traits + +using std::conjunction; +using std::disjunction; +using std::negation; + +#else + // https://en.cppreference.com/w/cpp/types/conjunction template struct conjunction : std::true_type { }; template struct conjunction : B { }; @@ -330,6 +338,8 @@ struct disjunction // https://en.cppreference.com/w/cpp/types/negation template struct negation : std::integral_constant < bool, !B::value > { }; +#endif + // Reimplementation of is_constructible and is_default_constructible, due to them being broken for // std::pair and std::tuple until LWG 2367 fix (see https://cplusplus.github.io/LWG/lwg-defects.html#2367). // This causes compile errors in e.g., Clang 3.5 or GCC 4.9. @@ -1060,8 +1070,12 @@ constexpr bool value_in_range_of(T val) return value_in_range_of_impl1::test(val); } -template -using bool_constant = std::integral_constant; +#ifdef __cpp_lib_bool_constant + using std::bool_constant; +#else + template + using bool_constant = std::integral_constant; +#endif // 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 diff --git a/include/nlohmann/detail/output/binary_writer.hpp b/include/nlohmann/detail/output/binary_writer.hpp index 981035165..916307b89 100644 --- a/include/nlohmann/detail/output/binary_writer.hpp +++ b/include/nlohmann/detail/output/binary_writer.hpp @@ -19,10 +19,6 @@ #include // move #include // vector -#ifdef _MSC_VER - #include // _byteswap_ushort, _byteswap_ulong, _byteswap_uint64 -#endif - #include #include #include @@ -2073,7 +2069,7 @@ class binary_writer { out[0] = to_char_type(first_byte); const unsigned unused_bits = 64u - ((8u << size_class) >> 1u); - const std::uint64_t tail = is_little_endian ? byte_swap(value) >> unused_bits : value << unused_bits; + const std::uint64_t tail = is_little_endian ? byteswap(value) >> unused_bits : value << unused_bits; std::memcpy(out + 1, &tail, sizeof(tail)); return ((std::size_t{1} << size_class) >> 1u) + 1; } @@ -3517,53 +3513,14 @@ class binary_writer // Utility functions // /////////////////////// - // single-instruction byte swaps (compilers lower these to bswap/rev/movbe); - // used to emit big-endian numbers without a per-byte std::reverse loop - static std::uint16_t byte_swap(std::uint16_t x) noexcept - { -#if defined(__GNUC__) || defined(__clang__) - return __builtin_bswap16(x); -#elif defined(_MSC_VER) - return _byteswap_ushort(x); -#else - return static_cast((x >> 8) | (x << 8)); -#endif - } - - static std::uint32_t byte_swap(std::uint32_t x) noexcept - { -#if defined(__GNUC__) || defined(__clang__) - return __builtin_bswap32(x); -#elif defined(_MSC_VER) - return _byteswap_ulong(x); -#else - return ((x & 0x000000FFu) << 24) | ((x & 0x0000FF00u) << 8) - | ((x & 0x00FF0000u) >> 8) | ((x & 0xFF000000u) >> 24); -#endif - } - - static std::uint64_t byte_swap(std::uint64_t x) noexcept - { -#if defined(__GNUC__) || defined(__clang__) - return __builtin_bswap64(x); -#elif defined(_MSC_VER) - return _byteswap_uint64(x); -#else - x = ((x & 0x00000000FFFFFFFFull) << 32) | ((x & 0xFFFFFFFF00000000ull) >> 32); - x = ((x & 0x0000FFFF0000FFFFull) << 16) | ((x & 0xFFFF0000FFFF0000ull) >> 16); - x = ((x & 0x00FF00FF00FF00FFull) << 8) | ((x & 0xFF00FF00FF00FF00ull) >> 8); - return x; -#endif - } - /*! @brief reverse the bytes of a buffer by byte-swapping it as UIntType Loading the buffer into an unsigned integer of the same width and swapping that is what lets the compiler emit a single bswap/rev/movbe; reversing the buffer element by element does not reliably get there (clang keeps a scalar - shuffle). The two memcpy calls are the only portable way to reinterpret the - bytes and are folded away by every optimizer. + shuffle). (The two memcpy calls, rather than bit_cast, keep GCC from + inlining differently in C++20, which made writing slower.) */ template static void byte_swap_buffer(std::array& a) noexcept @@ -3571,11 +3528,11 @@ class binary_writer static_assert(sizeof(UIntType) == N, "swap width must match the buffer size"); UIntType v{}; std::memcpy(&v, a.data(), sizeof(v)); - v = byte_swap(v); + v = byteswap(v); std::memcpy(a.data(), &v, sizeof(v)); } - // reverse the bytes of a fixed-size buffer; a single byte_swap() for the + // reverse the bytes of a fixed-size buffer; a single byteswap() for the // common 2/4/8-byte number payloads, std::reverse for any other size static void reverse_bytes(std::array& a) noexcept { @@ -3700,9 +3657,7 @@ class binary_writer #endif static_assert(sizeof(std::uint8_t) == sizeof(CharType), "size of CharType must be equal to std::uint8_t"); - CharType result; - std::memcpy(&result, &x, sizeof(x)); - return result; + return bit_cast(x); } template(64)), step())); + out.resize(detail::clamp(estimate, std::size_t{64}, step)); return out; } @@ -124,7 +120,7 @@ class output_buffer const auto used = static_cast(m_pos - m_out.data()); // (a step does not go beyond the reserved estimate, so that a good // estimate is never copied to a larger allocation) - const std::size_t size = (std::max)((std::min)(m_out.size() + step(), m_out.capacity()), used + n + 256); + const std::size_t size = (std::max)((std::min)(m_out.size() + step, m_out.capacity()), used + n + 256); if (size > m_out.capacity()) { m_out.reserve((std::max)(m_out.capacity() * 2, size)); @@ -794,8 +790,7 @@ class view_serializer static char* write_double_value_at(char* w, double x) { // (from the bits: without the checks of to_chars()) - std::uint64_t bits = 0; - std::memcpy(&bits, &x, sizeof(bits)); + auto bits = bit_cast(x); if (NLOHMANN_VIEW_UNLIKELY((bits & 0x7FF0000000000000u) == 0x7FF0000000000000u)) { return write_text_at(w, "null", 4); diff --git a/single_include/nlohmann/json.hpp b/single_include/nlohmann/json.hpp index 46093f374..1ac4a9095 100644 --- a/single_include/nlohmann/json.hpp +++ b/single_include/nlohmann/json.hpp @@ -3678,18 +3678,27 @@ NLOHMANN_JSON_NAMESPACE_END #include // size_t -#include // conditional, enable_if, false_type, integral_constant, is_constructible, is_integral, is_same, remove_cv, remove_reference, true_type +#include // conditional, enable_if, false_type, integral_constant, is_arithmetic, is_constructible, is_integral, is_same, remove_cv, remove_reference, true_type #include // index_sequence, make_index_sequence, index_sequence_for // #include +#ifdef JSON_HAS_CPP_17 + #include // clamp +#endif + NLOHMANN_JSON_NAMESPACE_BEGIN namespace detail { -template -using uncvref_t = typename std::remove_cv::type>::type; +#ifdef __cpp_lib_remove_cvref + template + using uncvref_t = std::remove_cvref_t; +#else + template + using uncvref_t = typename std::remove_cv::type>::type; +#endif #ifdef JSON_HAS_CPP_14 @@ -3829,6 +3838,19 @@ struct static_const constexpr T static_const::value; #endif +/// @a v bounded to [@a lo, @a hi] (requires !(hi < lo)); like std::clamp, +/// but by value, so that static constexpr members can be passed without an +/// out-of-line definition before C++17 +template::value, int> = 0> +constexpr T clamp(T v, T lo, T hi) noexcept +{ +#ifdef __cpp_lib_clamp + return std::clamp(v, lo, hi); +#else + return v < lo ? lo : (hi < v ? hi : v); +#endif +} + } // namespace detail NLOHMANN_JSON_NAMESPACE_END @@ -4426,6 +4448,14 @@ struct char_traits : std::char_traits // is_ functions // /////////////////// +#ifdef __cpp_lib_logical_traits + +using std::conjunction; +using std::disjunction; +using std::negation; + +#else + // https://en.cppreference.com/w/cpp/types/conjunction template struct conjunction : std::true_type { }; template struct conjunction : B { }; @@ -4443,6 +4473,8 @@ struct disjunction // https://en.cppreference.com/w/cpp/types/negation template struct negation : std::integral_constant < bool, !B::value > { }; +#endif + // Reimplementation of is_constructible and is_default_constructible, due to them being broken for // std::pair and std::tuple until LWG 2367 fix (see https://cplusplus.github.io/LWG/lwg-defects.html#2367). // This causes compile errors in e.g., Clang 3.5 or GCC 4.9. @@ -5173,8 +5205,12 @@ constexpr bool value_in_range_of(T val) return value_in_range_of_impl1::test(val); } -template -using bool_constant = std::integral_constant; +#ifdef __cpp_lib_bool_constant + using std::bool_constant; +#else + template + using bool_constant = std::integral_constant; +#endif // 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 @@ -9680,12 +9716,25 @@ NLOHMANN_JSON_NAMESPACE_END #include // __umulh, _umul128, _BitScanForward64, _BitScanReverse64 #endif +#ifdef _MSC_VER + #include // _byteswap_ushort, _byteswap_ulong, _byteswap_uint64 +#endif +#if defined(_MSC_VER) && !defined(__clang__) && (defined(_M_IX86) || defined(_M_ARM)) + #include // _BitScanForward, _BitScanReverse +#endif + // #include // JSON_HEDLEY_ALWAYS_INLINE, NLOHMANN_JSON_NAMESPACE_BEGIN +#if defined(__cpp_lib_bit_cast) || defined(__cpp_lib_bitops) || defined(__cpp_lib_byteswap) + #include // bit_cast, byteswap, countl_zero, countr_zero +#endif + // Portable bit-level helpers for the number and string scanners. They use -// compiler builtins or platform-specific intrinsics where available and plain -// C++ otherwise, so they work regardless of byte order. +// compiler builtins or platform-specific intrinsics where available, the +// C++20 functions next, and plain C++ otherwise, so they work regardless +// of byte order. (The builtins and intrinsics come first: MSVC's +// std::countl_zero checks for LZCNT at run time unless AVX2 is enabled.) NLOHMANN_JSON_NAMESPACE_BEGIN namespace detail @@ -9700,6 +9749,16 @@ inline int count_leading_zeros(std::uint64_t x) noexcept unsigned long index = 0; // NOLINT(runtime/int): the type _BitScan*64 takes _BitScanReverse64(&index, x); return 63 - static_cast(index); +#elif defined(_MSC_VER) && (defined(_M_IX86) || defined(_M_ARM)) + unsigned long index = 0; // NOLINT(runtime/int): the type _BitScan* takes + if (_BitScanReverse(&index, static_cast(x >> 32u))) // NOLINT(runtime/int) + { + return 31 - static_cast(index); + } + _BitScanReverse(&index, static_cast(x)); // NOLINT(runtime/int) + return 63 - static_cast(index); +#elif defined(__cpp_lib_bitops) + return std::countl_zero(x); #else int n = 0; for (int shift = 32; shift != 0; shift >>= 1) @@ -9723,6 +9782,16 @@ inline int count_trailing_zeros(std::uint64_t x) noexcept unsigned long index = 0; // NOLINT(runtime/int): the type _BitScan*64 takes _BitScanForward64(&index, x); return static_cast(index); +#elif defined(_MSC_VER) && (defined(_M_IX86) || defined(_M_ARM)) + unsigned long index = 0; // NOLINT(runtime/int): the type _BitScan* takes + if (_BitScanForward(&index, static_cast(x))) // NOLINT(runtime/int) + { + return static_cast(index); + } + _BitScanForward(&index, static_cast(x >> 32u)); // NOLINT(runtime/int) + return 32 + static_cast(index); +#elif defined(__cpp_lib_bitops) + return std::countr_zero(x); #else int n = 0; for (int shift = 32; shift != 0; shift >>= 1) @@ -9737,6 +9806,67 @@ inline int count_trailing_zeros(std::uint64_t x) noexcept #endif } +/// the object representation of @a from as a @a To (std::bit_cast in C++20) +template +JSON_HEDLEY_ALWAYS_INLINE To bit_cast(const From& from) noexcept +{ + static_assert(sizeof(To) == sizeof(From), "bit_cast needs types of the same size"); +#if defined(__cpp_lib_bit_cast) + return std::bit_cast(from); +#else + To to; + std::memcpy(&to, &from, sizeof(To)); + return to; +#endif +} + +/// x with its bytes in reverse order (std::byteswap in C++23); compilers lower +/// it to a single bswap/rev/movbe +inline std::uint16_t byteswap(std::uint16_t x) noexcept +{ +#if defined(__GNUC__) || defined(__clang__) + return __builtin_bswap16(x); +#elif defined(_MSC_VER) + return _byteswap_ushort(x); +#elif defined(__cpp_lib_byteswap) + return std::byteswap(x); +#else + return static_cast((x >> 8u) | (x << 8u)); +#endif +} + +/// x with its bytes in reverse order (std::byteswap in C++23) +inline std::uint32_t byteswap(std::uint32_t x) noexcept +{ +#if defined(__GNUC__) || defined(__clang__) + return __builtin_bswap32(x); +#elif defined(_MSC_VER) + return _byteswap_ulong(x); +#elif defined(__cpp_lib_byteswap) + return std::byteswap(x); +#else + return ((x & 0x000000FFu) << 24u) | ((x & 0x0000FF00u) << 8u) + | ((x & 0x00FF0000u) >> 8u) | ((x & 0xFF000000u) >> 24u); +#endif +} + +/// x with its bytes in reverse order (std::byteswap in C++23) +inline std::uint64_t byteswap(std::uint64_t x) noexcept +{ +#if defined(__GNUC__) || defined(__clang__) + return __builtin_bswap64(x); +#elif defined(_MSC_VER) + return _byteswap_uint64(x); +#elif defined(__cpp_lib_byteswap) + return std::byteswap(x); +#else + x = ((x & 0x00000000FFFFFFFFull) << 32u) | ((x & 0xFFFFFFFF00000000ull) >> 32u); + x = ((x & 0x0000FFFF0000FFFFull) << 16u) | ((x & 0xFFFF0000FFFF0000ull) >> 16u); + x = ((x & 0x00FF00FF00FF00FFull) << 8u) | ((x & 0xFF00FF00FF00FF00ull) >> 8u); + return x; +#endif +} + /// the 128-bit product of two 64-bit numbers struct uint128_parts { @@ -9817,7 +9947,6 @@ NLOHMANN_JSON_NAMESPACE_END #include // size_t #include // int64_t, uint64_t #include // strtof, strtod, strtold -#include // memcpy #include // numeric_limits #include // string #include // conditional, integral_constant, is_signed, true_type, false_type @@ -10938,15 +11067,14 @@ std::uint64_t digit_comparison(const char* first, const char* last, std::uint64_ inline void float_from_bits(std::uint64_t bits, double& value) noexcept { static_assert(sizeof(double) == sizeof(std::uint64_t), "double must have 64 bits"); - std::memcpy(&value, &bits, sizeof(value)); + value = bit_cast(bits); } /// the float with the IEEE-754 bits @a bits (the lower 32) inline void float_from_bits(std::uint64_t bits, float& value) noexcept { static_assert(sizeof(float) == sizeof(std::uint32_t), "float must have 32 bits"); - const auto bits32 = static_cast(bits); - std::memcpy(&value, &bits32, sizeof(bits32)); + value = bit_cast(static_cast(bits)); } /// the powers of ten that are exact in binary64 (up to 10^22) @@ -23402,10 +23530,6 @@ NLOHMANN_JSON_NAMESPACE_END #include // move #include // vector -#ifdef _MSC_VER - #include // _byteswap_ushort, _byteswap_ulong, _byteswap_uint64 -#endif - // #include // #include @@ -25892,7 +26016,7 @@ class binary_writer { out[0] = to_char_type(first_byte); const unsigned unused_bits = 64u - ((8u << size_class) >> 1u); - const std::uint64_t tail = is_little_endian ? byte_swap(value) >> unused_bits : value << unused_bits; + const std::uint64_t tail = is_little_endian ? byteswap(value) >> unused_bits : value << unused_bits; std::memcpy(out + 1, &tail, sizeof(tail)); return ((std::size_t{1} << size_class) >> 1u) + 1; } @@ -27336,53 +27460,14 @@ class binary_writer // Utility functions // /////////////////////// - // single-instruction byte swaps (compilers lower these to bswap/rev/movbe); - // used to emit big-endian numbers without a per-byte std::reverse loop - static std::uint16_t byte_swap(std::uint16_t x) noexcept - { -#if defined(__GNUC__) || defined(__clang__) - return __builtin_bswap16(x); -#elif defined(_MSC_VER) - return _byteswap_ushort(x); -#else - return static_cast((x >> 8) | (x << 8)); -#endif - } - - static std::uint32_t byte_swap(std::uint32_t x) noexcept - { -#if defined(__GNUC__) || defined(__clang__) - return __builtin_bswap32(x); -#elif defined(_MSC_VER) - return _byteswap_ulong(x); -#else - return ((x & 0x000000FFu) << 24) | ((x & 0x0000FF00u) << 8) - | ((x & 0x00FF0000u) >> 8) | ((x & 0xFF000000u) >> 24); -#endif - } - - static std::uint64_t byte_swap(std::uint64_t x) noexcept - { -#if defined(__GNUC__) || defined(__clang__) - return __builtin_bswap64(x); -#elif defined(_MSC_VER) - return _byteswap_uint64(x); -#else - x = ((x & 0x00000000FFFFFFFFull) << 32) | ((x & 0xFFFFFFFF00000000ull) >> 32); - x = ((x & 0x0000FFFF0000FFFFull) << 16) | ((x & 0xFFFF0000FFFF0000ull) >> 16); - x = ((x & 0x00FF00FF00FF00FFull) << 8) | ((x & 0xFF00FF00FF00FF00ull) >> 8); - return x; -#endif - } - /*! @brief reverse the bytes of a buffer by byte-swapping it as UIntType Loading the buffer into an unsigned integer of the same width and swapping that is what lets the compiler emit a single bswap/rev/movbe; reversing the buffer element by element does not reliably get there (clang keeps a scalar - shuffle). The two memcpy calls are the only portable way to reinterpret the - bytes and are folded away by every optimizer. + shuffle). (The two memcpy calls, rather than bit_cast, keep GCC from + inlining differently in C++20, which made writing slower.) */ template static void byte_swap_buffer(std::array& a) noexcept @@ -27390,11 +27475,11 @@ class binary_writer static_assert(sizeof(UIntType) == N, "swap width must match the buffer size"); UIntType v{}; std::memcpy(&v, a.data(), sizeof(v)); - v = byte_swap(v); + v = byteswap(v); std::memcpy(a.data(), &v, sizeof(v)); } - // reverse the bytes of a fixed-size buffer; a single byte_swap() for the + // reverse the bytes of a fixed-size buffer; a single byteswap() for the // common 2/4/8-byte number payloads, std::reverse for any other size static void reverse_bytes(std::array& a) noexcept { @@ -27519,9 +27604,7 @@ class binary_writer #endif static_assert(sizeof(std::uint8_t) == sizeof(CharType), "size of CharType must be equal to std::uint8_t"); - CharType result; - std::memcpy(&result, &x, sizeof(x)); - return result; + return bit_cast(x); } template // numeric_limits #include // conditional -#ifdef _MSC_VER - #include // _byteswap_uint64 -#endif // SSE2 (every x86-64 CPU) and NEON (every 64-bit Arm CPU) convert the 16 // digits of a double at once @@ -27624,6 +27704,8 @@ NLOHMANN_JSON_NAMESPACE_END #define JSON_DTOA_NEON 0 #endif +// #include + // #include // __ _____ _____ _____ // __| | __| | | | JSON for Modern C++ @@ -27881,16 +27963,6 @@ For a detailed description of the algorithm see: namespace dtoa_impl { -template -Target reinterpret_bits(const Source source) -{ - static_assert(sizeof(Target) == sizeof(Source), "size mismatch"); - - Target target; - std::memcpy(&target, &source, sizeof(Source)); - return target; -} - struct diyfp // f * 2^e { static constexpr int kPrecision = 64; // = q @@ -28045,7 +28117,7 @@ boundaries compute_boundaries(FloatType value) using bits_type = typename std::conditional::type; - const auto bits = static_cast(reinterpret_bits(value)); + const auto bits = static_cast(bit_cast(value)); const std::uint64_t E = bits >> (kPrecision - 1); const std::uint64_t F = bits & (kHiddenBit - 1); @@ -28899,14 +28971,11 @@ inline std::uint64_t eight_digit_bytes(std::uint64_t abcdefgh) noexcept /// the byte stores once this is inlined) inline void store_msb_first(char* p, std::uint64_t v) noexcept { -#if defined(__BYTE_ORDER__) && defined(__ORDER_LITTLE_ENDIAN__) && __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__ - v = __builtin_bswap64(v); +#if (defined(__BYTE_ORDER__) && defined(__ORDER_LITTLE_ENDIAN__) && __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__) || defined(_MSC_VER) // (MSVC: little-endian on all its targets) + v = byteswap(v); std::memcpy(p, &v, sizeof(v)); #elif defined(__BYTE_ORDER__) && defined(__ORDER_BIG_ENDIAN__) && __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__ std::memcpy(p, &v, sizeof(v)); -#elif defined(_MSC_VER) // (little-endian on all its targets) - v = _byteswap_uint64(v); - std::memcpy(p, &v, sizeof(v)); #else for (unsigned i = 0; i < 8; ++i) { @@ -29277,8 +29346,7 @@ char* write_positive_zmij(char* first, const char* last, FloatType value) { static_assert(is_binary64::value, "internal error: the conversion of Zmij needs IEEE 754 binary64 numbers"); - std::uint64_t bits = 0; - std::memcpy(&bits, &value, sizeof(bits)); + const auto bits = bit_cast(value); const zmij::shortest_decimal d = zmij::to_shortest(bits); if (JSON_HEDLEY_LIKELY(last - first >= 41)) { diff --git a/single_include/nlohmann/json_view.hpp b/single_include/nlohmann/json_view.hpp index dfefa2633..2d9ea4acc 100644 --- a/single_include/nlohmann/json_view.hpp +++ b/single_include/nlohmann/json_view.hpp @@ -6166,17 +6166,13 @@ class output_buffer } private: - /// the size of a growth step (a function: std::min() takes a reference, - /// which a static constexpr member does not have before C++17) - static constexpr std::size_t step() noexcept - { - return 0x10000; - } + /// the size of a growth step + static constexpr std::size_t step = 0x10000; static StringType& sized(StringType& out, std::size_t estimate) { out.reserve(estimate); - out.resize((std::min)((std::max)(estimate, static_cast(64)), step())); + out.resize(detail::clamp(estimate, std::size_t{64}, step)); return out; } @@ -6185,7 +6181,7 @@ class output_buffer const auto used = static_cast(m_pos - m_out.data()); // (a step does not go beyond the reserved estimate, so that a good // estimate is never copied to a larger allocation) - const std::size_t size = (std::max)((std::min)(m_out.size() + step(), m_out.capacity()), used + n + 256); + const std::size_t size = (std::max)((std::min)(m_out.size() + step, m_out.capacity()), used + n + 256); if (size > m_out.capacity()) { m_out.reserve((std::max)(m_out.capacity() * 2, size)); @@ -6855,8 +6851,7 @@ class view_serializer static char* write_double_value_at(char* w, double x) { // (from the bits: without the checks of to_chars()) - std::uint64_t bits = 0; - std::memcpy(&bits, &x, sizeof(bits)); + auto bits = bit_cast(x); if (NLOHMANN_VIEW_UNLIKELY((bits & 0x7FF0000000000000u) == 0x7FF0000000000000u)) { return write_text_at(w, "null", 4); diff --git a/tests/src/unit-stl_helpers.cpp b/tests/src/unit-stl_helpers.cpp new file mode 100644 index 000000000..05c1e21bf --- /dev/null +++ b/tests/src/unit-stl_helpers.cpp @@ -0,0 +1,129 @@ +// __ _____ _____ _____ +// __| | __| | | | JSON for Modern C++ (supporting code) +// | | |__ | | | | | | version 3.12.0 +// |_____|_____|_____|_|___| https://github.com/nlohmann/json +// +// SPDX-FileCopyrightText: 2013-2026 Niels Lohmann +// SPDX-License-Identifier: MIT + +#include "doctest_compatibility.h" + +#include + +#include +#include +#include +#include + +// The detail helpers that stand in for standard library functions of newer +// standards: each uses the standard function where the library has it and a +// C++11 implementation otherwise, so this file is also built with +// JSON_HAS_CPP_17, JSON_HAS_CPP_20, and JSON_HAS_CPP_23 to test both. + +namespace +{ +struct with_constexpr_member +{ + static constexpr std::size_t lower = 64; // no out-of-line definition +}; +} // namespace + +TEST_CASE("detail::clamp") +{ + using nlohmann::detail::clamp; + + SECTION("integers") + { + CHECK(clamp(5, 1, 10) == 5); + CHECK(clamp(0, 1, 10) == 1); + CHECK(clamp(11, 1, 10) == 10); + CHECK(clamp(1, 1, 10) == 1); + CHECK(clamp(10, 1, 10) == 10); + CHECK(clamp(7, 7, 7) == 7); + CHECK(clamp(-5, -3, 3) == -3); + CHECK(clamp(std::size_t{0}, std::size_t{64}, std::size_t{0x10000}) == 64); + CHECK(clamp(std::size_t{0x20000}, std::size_t{64}, std::size_t{0x10000}) == 0x10000); + } + + SECTION("floating-point numbers") + { + CHECK(clamp(0.5, 0.0, 1.0) == 0.5); + CHECK(clamp(-0.5, 0.0, 1.0) == 0.0); + CHECK(clamp(1.5, 0.0, 1.0) == 1.0); + CHECK(clamp(-std::numeric_limits::infinity(), 0.0, 1.0) == 0.0); + } + + SECTION("by value: a static constexpr member needs no definition") + { + CHECK(clamp(std::size_t{1}, with_constexpr_member::lower, std::size_t{100}) == 64); + } + + SECTION("constexpr and noexcept") + { + static_assert(clamp(3, 1, 2) == 2, "clamp must be constexpr"); + static_assert(noexcept(clamp(3, 1, 2)), "clamp must be noexcept"); + } +} + +TEST_CASE("detail::bit_cast") +{ + using nlohmann::detail::bit_cast; + + CHECK(bit_cast(1.0) == 0x3FF0000000000000u); + CHECK(bit_cast(-2.0) == 0xC000000000000000u); + CHECK(bit_cast(1.0f) == 0x3F800000u); + CHECK(bit_cast(std::uint64_t{0x4009000000000000u}) == 3.125); + CHECK(bit_cast(std::uint32_t{0xC0490000u}) == -3.140625f); + CHECK(bit_cast(std::uint8_t{0xFF}) == -1); + + // to and from byte arrays + const auto bytes = bit_cast>(std::uint32_t{0x01020304u}); + CHECK(bit_cast(bytes) == 0x01020304u); + + static_assert(noexcept(bit_cast(1.0)), "bit_cast must be noexcept"); +} + +TEST_CASE("detail::byteswap") +{ + using nlohmann::detail::byteswap; + + CHECK(byteswap(std::uint16_t{0x0102u}) == 0x0201u); + CHECK(byteswap(std::uint16_t{0xFF00u}) == 0x00FFu); + CHECK(byteswap(std::uint32_t{0x01020304u}) == 0x04030201u); + CHECK(byteswap(std::uint32_t{0xFF000080u}) == 0x800000FFu); + CHECK(byteswap(std::uint64_t{0x0102030405060708u}) == 0x0807060504030201u); + CHECK(byteswap(std::uint64_t{0xFF00000000000080u}) == 0x80000000000000FFu); + + // an involution + for (std::uint64_t x : + { + std::uint64_t{0}, std::uint64_t{1}, std::uint64_t{0x123456789ABCDEF0u}, (std::numeric_limits::max)() + }) + { + CHECK(byteswap(byteswap(x)) == x); + CHECK(byteswap(byteswap(static_cast(x))) == static_cast(x)); + CHECK(byteswap(byteswap(static_cast(x))) == static_cast(x)); + } + + static_assert(std::is_same < decltype(byteswap(std::uint16_t{})), std::uint16_t >::value, "byteswap keeps the type"); + static_assert(std::is_same < decltype(byteswap(std::uint32_t{})), std::uint32_t >::value, "byteswap keeps the type"); + static_assert(std::is_same < decltype(byteswap(std::uint64_t{})), std::uint64_t >::value, "byteswap keeps the type"); +} + +TEST_CASE("detail::count_leading_zeros and count_trailing_zeros") +{ + using nlohmann::detail::count_leading_zeros; + using nlohmann::detail::count_trailing_zeros; + + // a single bit, and that bit with all lower (resp. higher) bits set: both + // halves of the 32-bit fallbacks + for (int k = 0; k < 64; ++k) + { + const std::uint64_t bit = std::uint64_t{1} << k; + CAPTURE(k) + CHECK(count_leading_zeros(bit) == 63 - k); + CHECK(count_leading_zeros(bit | (bit - 1)) == 63 - k); + CHECK(count_trailing_zeros(bit) == k); + CHECK(count_trailing_zeros(bit | ~(bit | (bit - 1))) == k); + } +} diff --git a/tests/src/unit-to_chars.cpp b/tests/src/unit-to_chars.cpp index fa24477e0..f8863a5a9 100644 --- a/tests/src/unit-to_chars.cpp +++ b/tests/src/unit-to_chars.cpp @@ -13,7 +13,7 @@ #include "doctest_compatibility.h" #include -using nlohmann::detail::dtoa_impl::reinterpret_bits; +using nlohmann::detail::bit_cast; #include #include @@ -44,7 +44,7 @@ float make_float(uint32_t sign_bit, uint32_t biased_exponent, uint32_t significa bits |= biased_exponent << 23; bits |= significand; - return reinterpret_bits(bits); + return bit_cast(bits); } // ldexp -- convert f * 2^e to IEEE single precision @@ -81,7 +81,7 @@ float make_float(uint64_t f, int e) : static_cast(e + kExponentBias); const uint64_t bits = (f & kSignificandMask) | (biased_exponent << kPhysicalSignificandSize); - return reinterpret_bits(static_cast(bits)); + return bit_cast(static_cast(bits)); } double make_double(uint64_t sign_bit, uint64_t biased_exponent, uint64_t significand) @@ -96,7 +96,7 @@ double make_double(uint64_t sign_bit, uint64_t biased_exponent, uint64_t signifi bits |= biased_exponent << 52; bits |= significand; - return reinterpret_bits(bits); + return bit_cast(bits); } // ldexp -- convert f * 2^e to IEEE double precision @@ -133,7 +133,7 @@ double make_double(uint64_t f, int e) : static_cast(e + kExponentBias); const uint64_t bits = (f & kSignificandMask) | (biased_exponent << kPhysicalSignificandSize); - return reinterpret_bits(bits); + return bit_cast(bits); } } // namespace @@ -652,7 +652,7 @@ bool reads_back(const std::string& digits, int e, double v) { const std::string text = digits + "e" + std::to_string(e); // (compared bit for bit: v is positive and finite, and -Wfloat-equal) - return reinterpret_bits(parse_double(text)) == reinterpret_bits(v); + return bit_cast(parse_double(text)) == bit_cast(v); } /// Check the representation of a positive finite double: it reads back as @@ -665,7 +665,7 @@ void check_shortest(double v) CAPTURE(text) CHECK(parse_double(text) == v); // the layout is that of format_buffer() for the digits of Zmij - const auto sd = nlohmann::detail::zmij::to_shortest(reinterpret_bits(v)); + const auto sd = nlohmann::detail::zmij::to_shortest(bit_cast(v)); const std::uint64_t significand = sd.has_digit ? (sd.integral * 10) + sd.digit : sd.integral; int exponent = sd.has_digit ? sd.exponent : sd.exponent + 1; std::string significand_digits = std::to_string(significand); @@ -779,7 +779,7 @@ TEST_CASE("shortest digits of doubles") for (int i = 0; i < 100000; ++i) { const std::uint64_t bits = rng() & 0x7FFFFFFFFFFFFFFFu; - const auto v = reinterpret_bits(bits); + const auto v = bit_cast(bits); if (std::isfinite(v) && bits != 0) { check_shortest(v);