Compare commits

..
Author SHA1 Message Date
Niels Lohmann c634c24877 Use standard library helpers where available, with C++11 fallbacks
Add detail helpers that call the standard library function when the
library provides it (checked with the __cpp_lib_* feature-test macros, so
no new ABI-tag macros) and a C++11 implementation otherwise:

- detail::clamp (std::clamp): by value, so that static constexpr members
  need no out-of-line definition before C++17; used in json_view's dump()
  buffer sizing, where step() can now be a static constexpr member.
- detail::bit_cast (std::bit_cast): replaces dtoa_impl::reinterpret_bits
  and the memcpy bit copies in the number parser, to_chars, the json_view
  serializer, and binary_writer::to_char_type.
- detail::byteswap (std::byteswap): one implementation instead of the
  binary writer's own byte_swap() overloads and the bswap chain in
  to_chars' store_msb_first().
- count_leading_zeros/count_trailing_zeros: two 32-bit _BitScanReverse/
  _BitScanForward calls for MSVC x86/ARM32 instead of the loop, and
  std::countl_zero/countr_zero for compilers without builtins.
- conjunction, disjunction, negation, bool_constant, and uncvref_t map to
  their std counterparts in C++17/C++20.

Builtins and intrinsics stay ahead of the <bit> functions, as MSVC's
std::countl_zero checks for LZCNT at run time unless AVX2 is enabled.

The generated code is unchanged with Clang (C++11/20/23, arm64 and
x86-64) and GCC 16 for parsing, dumping, and the binary formats. The
binary reader keeps its own byte_swap() and the writer its memcpy calls:
the smaller code from detail::byteswap/std::bit_cast there made GCC 16
inline get_number()/write_number() differently, which slowed reading by
10-18 % and writing by about 1 %.

Signed-off-by: Niels Lohmann <mail@nlohmann.me>
2026-10-21 15:25:22 +02:00
11 changed files with 448 additions and 194 deletions

No files matched your search

+96 -2
View File
@@ -14,11 +14,24 @@
#include <intrin0.h> // __umulh, _umul128, _BitScanForward64, _BitScanReverse64 #include <intrin0.h> // __umulh, _umul128, _BitScanForward64, _BitScanReverse64
#endif #endif
#ifdef _MSC_VER
#include <cstdlib> // _byteswap_ushort, _byteswap_ulong, _byteswap_uint64
#endif
#if defined(_MSC_VER) && !defined(__clang__) && (defined(_M_IX86) || defined(_M_ARM))
#include <intrin.h> // _BitScanForward, _BitScanReverse
#endif
#include <nlohmann/detail/macro_scope.hpp> // JSON_HEDLEY_ALWAYS_INLINE, NLOHMANN_JSON_NAMESPACE_BEGIN #include <nlohmann/detail/macro_scope.hpp> // JSON_HEDLEY_ALWAYS_INLINE, NLOHMANN_JSON_NAMESPACE_BEGIN
#if defined(__cpp_lib_bit_cast) || defined(__cpp_lib_bitops) || defined(__cpp_lib_byteswap)
#include <bit> // bit_cast, byteswap, countl_zero, countr_zero
#endif
// Portable bit-level helpers for the number and string scanners. They use // Portable bit-level helpers for the number and string scanners. They use
// compiler builtins or platform-specific intrinsics where available and plain // compiler builtins or platform-specific intrinsics where available, the
// C++ otherwise, so they work regardless of byte order. // C++20 <bit> 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 NLOHMANN_JSON_NAMESPACE_BEGIN
namespace detail 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 unsigned long index = 0; // NOLINT(runtime/int): the type _BitScan*64 takes
_BitScanReverse64(&index, x); _BitScanReverse64(&index, x);
return 63 - static_cast<int>(index); return 63 - static_cast<int>(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<unsigned long>(x >> 32u))) // NOLINT(runtime/int)
{
return 31 - static_cast<int>(index);
}
_BitScanReverse(&index, static_cast<unsigned long>(x)); // NOLINT(runtime/int)
return 63 - static_cast<int>(index);
#elif defined(__cpp_lib_bitops)
return std::countl_zero(x);
#else #else
int n = 0; int n = 0;
for (int shift = 32; shift != 0; shift >>= 1) 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 unsigned long index = 0; // NOLINT(runtime/int): the type _BitScan*64 takes
_BitScanForward64(&index, x); _BitScanForward64(&index, x);
return static_cast<int>(index); return static_cast<int>(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<unsigned long>(x))) // NOLINT(runtime/int)
{
return static_cast<int>(index);
}
_BitScanForward(&index, static_cast<unsigned long>(x >> 32u)); // NOLINT(runtime/int)
return 32 + static_cast<int>(index);
#elif defined(__cpp_lib_bitops)
return std::countr_zero(x);
#else #else
int n = 0; int n = 0;
for (int shift = 32; shift != 0; shift >>= 1) for (int shift = 32; shift != 0; shift >>= 1)
@@ -70,6 +103,67 @@ inline int count_trailing_zeros(std::uint64_t x) noexcept
#endif #endif
} }
/// the object representation of @a from as a @a To (std::bit_cast in C++20)
template<typename To, typename From>
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<To>(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<std::uint16_t>((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 /// the 128-bit product of two 64-bit numbers
struct uint128_parts struct uint128_parts
{ {
@@ -18,9 +18,6 @@
#include <limits> // numeric_limits #include <limits> // numeric_limits
#include <type_traits> // conditional #include <type_traits> // conditional
#ifdef _MSC_VER
#include <cstdlib> // _byteswap_uint64
#endif
// SSE2 (every x86-64 CPU) and NEON (every 64-bit Arm CPU) convert the 16 // SSE2 (every x86-64 CPU) and NEON (every 64-bit Arm CPU) convert the 16
// digits of a double at once // digits of a double at once
@@ -37,6 +34,7 @@
#define JSON_DTOA_NEON 0 #define JSON_DTOA_NEON 0
#endif #endif
#include <nlohmann/detail/bit_ops.hpp>
#include <nlohmann/detail/conversions/zmij.hpp> #include <nlohmann/detail/conversions/zmij.hpp>
#include <nlohmann/detail/macro_scope.hpp> #include <nlohmann/detail/macro_scope.hpp>
@@ -66,16 +64,6 @@ For a detailed description of the algorithm see:
namespace dtoa_impl namespace dtoa_impl
{ {
template<typename Target, typename Source>
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 struct diyfp // f * 2^e
{ {
static constexpr int kPrecision = 64; // = q static constexpr int kPrecision = 64; // = q
@@ -230,7 +218,7 @@ boundaries compute_boundaries(FloatType value)
using bits_type = typename std::conditional<kPrecision == 24, std::uint32_t, std::uint64_t >::type; using bits_type = typename std::conditional<kPrecision == 24, std::uint32_t, std::uint64_t >::type;
const auto bits = static_cast<std::uint64_t>(reinterpret_bits<bits_type>(value)); const auto bits = static_cast<std::uint64_t>(bit_cast<bits_type>(value));
const std::uint64_t E = bits >> (kPrecision - 1); const std::uint64_t E = bits >> (kPrecision - 1);
const std::uint64_t F = bits & (kHiddenBit - 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) /// the byte stores once this is inlined)
inline void store_msb_first(char* p, std::uint64_t v) noexcept 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__ #if (defined(__BYTE_ORDER__) && defined(__ORDER_LITTLE_ENDIAN__) && __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__) || defined(_MSC_VER) // (MSVC: little-endian on all its targets)
v = __builtin_bswap64(v); v = byteswap(v);
std::memcpy(p, &v, sizeof(v)); std::memcpy(p, &v, sizeof(v));
#elif defined(__BYTE_ORDER__) && defined(__ORDER_BIG_ENDIAN__) && __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__ #elif defined(__BYTE_ORDER__) && defined(__ORDER_BIG_ENDIAN__) && __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__
std::memcpy(p, &v, sizeof(v)); 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 #else
for (unsigned i = 0; i < 8; ++i) 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<FloatType>::value, static_assert(is_binary64<FloatType>::value,
"internal error: the conversion of Zmij needs IEEE 754 binary64 numbers"); "internal error: the conversion of Zmij needs IEEE 754 binary64 numbers");
std::uint64_t bits = 0; const auto bits = bit_cast<std::uint64_t>(value);
std::memcpy(&bits, &value, sizeof(bits));
const zmij::shortest_decimal d = zmij::to_shortest(bits); const zmij::shortest_decimal d = zmij::to_shortest(bits);
if (JSON_HEDLEY_LIKELY(last - first >= 41)) if (JSON_HEDLEY_LIKELY(last - first >= 41))
{ {
@@ -16,7 +16,6 @@
#include <cstddef> // size_t #include <cstddef> // size_t
#include <cstdint> // int64_t, uint64_t #include <cstdint> // int64_t, uint64_t
#include <cstdlib> // strtof, strtod, strtold #include <cstdlib> // strtof, strtod, strtold
#include <cstring> // memcpy
#include <limits> // numeric_limits #include <limits> // numeric_limits
#include <string> // string #include <string> // string
#include <type_traits> // conditional, integral_constant, is_signed, true_type, false_type #include <type_traits> // 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 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"); static_assert(sizeof(double) == sizeof(std::uint64_t), "double must have 64 bits");
std::memcpy(&value, &bits, sizeof(value)); value = bit_cast<double>(bits);
} }
/// the float with the IEEE-754 bits @a bits (the lower 32) /// the float with the IEEE-754 bits @a bits (the lower 32)
inline void float_from_bits(std::uint64_t bits, float& value) noexcept 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"); static_assert(sizeof(float) == sizeof(std::uint32_t), "float must have 32 bits");
const auto bits32 = static_cast<std::uint32_t>(bits); value = bit_cast<float>(static_cast<std::uint32_t>(bits));
std::memcpy(&value, &bits32, sizeof(bits32));
} }
/// the powers of ten that are exact in binary64 (up to 10^22) /// the powers of ten that are exact in binary64 (up to 10^22)
+25 -3
View File
@@ -10,17 +10,26 @@
#pragma once #pragma once
#include <cstddef> // size_t #include <cstddef> // size_t
#include <type_traits> // conditional, enable_if, false_type, integral_constant, is_constructible, is_integral, is_same, remove_cv, remove_reference, true_type #include <type_traits> // conditional, enable_if, false_type, integral_constant, is_arithmetic, is_constructible, is_integral, is_same, remove_cv, remove_reference, true_type
#include <utility> // index_sequence, make_index_sequence, index_sequence_for #include <utility> // index_sequence, make_index_sequence, index_sequence_for
#include <nlohmann/detail/macro_scope.hpp> #include <nlohmann/detail/macro_scope.hpp>
#ifdef JSON_HAS_CPP_17
#include <algorithm> // clamp
#endif
NLOHMANN_JSON_NAMESPACE_BEGIN NLOHMANN_JSON_NAMESPACE_BEGIN
namespace detail namespace detail
{ {
template<typename T> #ifdef __cpp_lib_remove_cvref
using uncvref_t = typename std::remove_cv<typename std::remove_reference<T>::type>::type; template<typename T>
using uncvref_t = std::remove_cvref_t<T>;
#else
template<typename T>
using uncvref_t = typename std::remove_cv<typename std::remove_reference<T>::type>::type;
#endif
#ifdef JSON_HAS_CPP_14 #ifdef JSON_HAS_CPP_14
@@ -160,5 +169,18 @@ struct static_const
constexpr T static_const<T>::value; constexpr T static_const<T>::value;
#endif #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<typename T, enable_if_t<std::is_arithmetic<T>::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 } // namespace detail
NLOHMANN_JSON_NAMESPACE_END NLOHMANN_JSON_NAMESPACE_END
+16 -2
View File
@@ -313,6 +313,14 @@ struct char_traits<std::byte> : std::char_traits<char>
// is_ functions // // 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 // https://en.cppreference.com/w/cpp/types/conjunction
template<class...> struct conjunction : std::true_type { }; template<class...> struct conjunction : std::true_type { };
template<class B> struct conjunction<B> : B { }; template<class B> struct conjunction<B> : B { };
@@ -330,6 +338,8 @@ struct disjunction<B, Bn...>
// https://en.cppreference.com/w/cpp/types/negation // https://en.cppreference.com/w/cpp/types/negation
template<class B> struct negation : std::integral_constant < bool, !B::value > { }; template<class B> struct negation : std::integral_constant < bool, !B::value > { };
#endif
// Reimplementation of is_constructible and is_default_constructible, due to them being broken for // 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). // 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. // 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<OfType, T>::test(val); return value_in_range_of_impl1<OfType, T>::test(val);
} }
template<bool Value> #ifdef __cpp_lib_bool_constant
using bool_constant = std::integral_constant<bool, Value>; using std::bool_constant;
#else
template<bool Value>
using bool_constant = std::integral_constant<bool, Value>;
#endif
// Stands in for number_unsigned_t in overload sets if it is the same type as // 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 // number_integer_t, so that the unsigned overloads do not collide with the
@@ -19,10 +19,6 @@
#include <utility> // move #include <utility> // move
#include <vector> // vector #include <vector> // vector
#ifdef _MSC_VER
#include <cstdlib> // _byteswap_ushort, _byteswap_ulong, _byteswap_uint64
#endif
#include <nlohmann/detail/bit_ops.hpp> #include <nlohmann/detail/bit_ops.hpp>
#include <nlohmann/detail/input/binary_reader.hpp> #include <nlohmann/detail/input/binary_reader.hpp>
#include <nlohmann/detail/input/string_scan.hpp> #include <nlohmann/detail/input/string_scan.hpp>
@@ -2073,7 +2069,7 @@ class binary_writer
{ {
out[0] = to_char_type(first_byte); out[0] = to_char_type(first_byte);
const unsigned unused_bits = 64u - ((8u << size_class) >> 1u); 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)); std::memcpy(out + 1, &tail, sizeof(tail));
return ((std::size_t{1} << size_class) >> 1u) + 1; return ((std::size_t{1} << size_class) >> 1u) + 1;
} }
@@ -3517,53 +3513,14 @@ class binary_writer
// Utility functions // // 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<std::uint16_t>((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 @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 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 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 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 shuffle). (The two memcpy calls, rather than bit_cast, keep GCC from
bytes and are folded away by every optimizer. inlining differently in C++20, which made writing slower.)
*/ */
template<typename UIntType, std::size_t N> template<typename UIntType, std::size_t N>
static void byte_swap_buffer(std::array<CharType, N>& a) noexcept static void byte_swap_buffer(std::array<CharType, N>& a) noexcept
@@ -3571,11 +3528,11 @@ class binary_writer
static_assert(sizeof(UIntType) == N, "swap width must match the buffer size"); static_assert(sizeof(UIntType) == N, "swap width must match the buffer size");
UIntType v{}; UIntType v{};
std::memcpy(&v, a.data(), sizeof(v)); std::memcpy(&v, a.data(), sizeof(v));
v = byte_swap(v); v = byteswap(v);
std::memcpy(a.data(), &v, sizeof(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 // common 2/4/8-byte number payloads, std::reverse for any other size
static void reverse_bytes(std::array<CharType, 2>& a) noexcept static void reverse_bytes(std::array<CharType, 2>& a) noexcept
{ {
@@ -3700,9 +3657,7 @@ class binary_writer
#endif #endif
static_assert(sizeof(std::uint8_t) == sizeof(CharType), "size of CharType must be equal to std::uint8_t"); static_assert(sizeof(std::uint8_t) == sizeof(CharType), "size of CharType must be equal to std::uint8_t");
CharType result; return bit_cast<CharType>(x);
std::memcpy(&result, &x, sizeof(x));
return result;
} }
template<typename C = CharType, template<typename C = CharType,
+5 -10
View File
@@ -105,17 +105,13 @@ class output_buffer
} }
private: private:
/// the size of a growth step (a function: std::min() takes a reference, /// the size of a growth step
/// which a static constexpr member does not have before C++17) static constexpr std::size_t step = 0x10000;
static constexpr std::size_t step() noexcept
{
return 0x10000;
}
static StringType& sized(StringType& out, std::size_t estimate) static StringType& sized(StringType& out, std::size_t estimate)
{ {
out.reserve(estimate); out.reserve(estimate);
out.resize((std::min)((std::max)(estimate, static_cast<std::size_t>(64)), step())); out.resize(detail::clamp(estimate, std::size_t{64}, step));
return out; return out;
} }
@@ -124,7 +120,7 @@ class output_buffer
const auto used = static_cast<std::size_t>(m_pos - m_out.data()); const auto used = static_cast<std::size_t>(m_pos - m_out.data());
// (a step does not go beyond the reserved estimate, so that a good // (a step does not go beyond the reserved estimate, so that a good
// estimate is never copied to a larger allocation) // 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()) if (size > m_out.capacity())
{ {
m_out.reserve((std::max)(m_out.capacity() * 2, size)); 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) static char* write_double_value_at(char* w, double x)
{ {
// (from the bits: without the checks of to_chars()) // (from the bits: without the checks of to_chars())
std::uint64_t bits = 0; auto bits = bit_cast<std::uint64_t>(x);
std::memcpy(&bits, &x, sizeof(bits));
if (NLOHMANN_VIEW_UNLIKELY((bits & 0x7FF0000000000000u) == 0x7FF0000000000000u)) if (NLOHMANN_VIEW_UNLIKELY((bits & 0x7FF0000000000000u) == 0x7FF0000000000000u))
{ {
return write_text_at(w, "null", 4); return write_text_at(w, "null", 4);
+151 -83
View File
@@ -3678,18 +3678,27 @@ NLOHMANN_JSON_NAMESPACE_END
#include <cstddef> // size_t #include <cstddef> // size_t
#include <type_traits> // conditional, enable_if, false_type, integral_constant, is_constructible, is_integral, is_same, remove_cv, remove_reference, true_type #include <type_traits> // conditional, enable_if, false_type, integral_constant, is_arithmetic, is_constructible, is_integral, is_same, remove_cv, remove_reference, true_type
#include <utility> // index_sequence, make_index_sequence, index_sequence_for #include <utility> // index_sequence, make_index_sequence, index_sequence_for
// #include <nlohmann/detail/macro_scope.hpp> // #include <nlohmann/detail/macro_scope.hpp>
#ifdef JSON_HAS_CPP_17
#include <algorithm> // clamp
#endif
NLOHMANN_JSON_NAMESPACE_BEGIN NLOHMANN_JSON_NAMESPACE_BEGIN
namespace detail namespace detail
{ {
template<typename T> #ifdef __cpp_lib_remove_cvref
using uncvref_t = typename std::remove_cv<typename std::remove_reference<T>::type>::type; template<typename T>
using uncvref_t = std::remove_cvref_t<T>;
#else
template<typename T>
using uncvref_t = typename std::remove_cv<typename std::remove_reference<T>::type>::type;
#endif
#ifdef JSON_HAS_CPP_14 #ifdef JSON_HAS_CPP_14
@@ -3829,6 +3838,19 @@ struct static_const
constexpr T static_const<T>::value; constexpr T static_const<T>::value;
#endif #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<typename T, enable_if_t<std::is_arithmetic<T>::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 } // namespace detail
NLOHMANN_JSON_NAMESPACE_END NLOHMANN_JSON_NAMESPACE_END
@@ -4426,6 +4448,14 @@ struct char_traits<std::byte> : std::char_traits<char>
// is_ functions // // 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 // https://en.cppreference.com/w/cpp/types/conjunction
template<class...> struct conjunction : std::true_type { }; template<class...> struct conjunction : std::true_type { };
template<class B> struct conjunction<B> : B { }; template<class B> struct conjunction<B> : B { };
@@ -4443,6 +4473,8 @@ struct disjunction<B, Bn...>
// https://en.cppreference.com/w/cpp/types/negation // https://en.cppreference.com/w/cpp/types/negation
template<class B> struct negation : std::integral_constant < bool, !B::value > { }; template<class B> struct negation : std::integral_constant < bool, !B::value > { };
#endif
// Reimplementation of is_constructible and is_default_constructible, due to them being broken for // 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). // 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. // 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<OfType, T>::test(val); return value_in_range_of_impl1<OfType, T>::test(val);
} }
template<bool Value> #ifdef __cpp_lib_bool_constant
using bool_constant = std::integral_constant<bool, Value>; using std::bool_constant;
#else
template<bool Value>
using bool_constant = std::integral_constant<bool, Value>;
#endif
// Stands in for number_unsigned_t in overload sets if it is the same type as // 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 // number_integer_t, so that the unsigned overloads do not collide with the
@@ -9680,12 +9716,25 @@ NLOHMANN_JSON_NAMESPACE_END
#include <intrin0.h> // __umulh, _umul128, _BitScanForward64, _BitScanReverse64 #include <intrin0.h> // __umulh, _umul128, _BitScanForward64, _BitScanReverse64
#endif #endif
#ifdef _MSC_VER
#include <cstdlib> // _byteswap_ushort, _byteswap_ulong, _byteswap_uint64
#endif
#if defined(_MSC_VER) && !defined(__clang__) && (defined(_M_IX86) || defined(_M_ARM))
#include <intrin.h> // _BitScanForward, _BitScanReverse
#endif
// #include <nlohmann/detail/macro_scope.hpp> // #include <nlohmann/detail/macro_scope.hpp>
// JSON_HEDLEY_ALWAYS_INLINE, NLOHMANN_JSON_NAMESPACE_BEGIN // JSON_HEDLEY_ALWAYS_INLINE, NLOHMANN_JSON_NAMESPACE_BEGIN
#if defined(__cpp_lib_bit_cast) || defined(__cpp_lib_bitops) || defined(__cpp_lib_byteswap)
#include <bit> // bit_cast, byteswap, countl_zero, countr_zero
#endif
// Portable bit-level helpers for the number and string scanners. They use // Portable bit-level helpers for the number and string scanners. They use
// compiler builtins or platform-specific intrinsics where available and plain // compiler builtins or platform-specific intrinsics where available, the
// C++ otherwise, so they work regardless of byte order. // C++20 <bit> 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 NLOHMANN_JSON_NAMESPACE_BEGIN
namespace detail 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 unsigned long index = 0; // NOLINT(runtime/int): the type _BitScan*64 takes
_BitScanReverse64(&index, x); _BitScanReverse64(&index, x);
return 63 - static_cast<int>(index); return 63 - static_cast<int>(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<unsigned long>(x >> 32u))) // NOLINT(runtime/int)
{
return 31 - static_cast<int>(index);
}
_BitScanReverse(&index, static_cast<unsigned long>(x)); // NOLINT(runtime/int)
return 63 - static_cast<int>(index);
#elif defined(__cpp_lib_bitops)
return std::countl_zero(x);
#else #else
int n = 0; int n = 0;
for (int shift = 32; shift != 0; shift >>= 1) 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 unsigned long index = 0; // NOLINT(runtime/int): the type _BitScan*64 takes
_BitScanForward64(&index, x); _BitScanForward64(&index, x);
return static_cast<int>(index); return static_cast<int>(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<unsigned long>(x))) // NOLINT(runtime/int)
{
return static_cast<int>(index);
}
_BitScanForward(&index, static_cast<unsigned long>(x >> 32u)); // NOLINT(runtime/int)
return 32 + static_cast<int>(index);
#elif defined(__cpp_lib_bitops)
return std::countr_zero(x);
#else #else
int n = 0; int n = 0;
for (int shift = 32; shift != 0; shift >>= 1) for (int shift = 32; shift != 0; shift >>= 1)
@@ -9737,6 +9806,67 @@ inline int count_trailing_zeros(std::uint64_t x) noexcept
#endif #endif
} }
/// the object representation of @a from as a @a To (std::bit_cast in C++20)
template<typename To, typename From>
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<To>(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<std::uint16_t>((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 /// the 128-bit product of two 64-bit numbers
struct uint128_parts struct uint128_parts
{ {
@@ -9817,7 +9947,6 @@ NLOHMANN_JSON_NAMESPACE_END
#include <cstddef> // size_t #include <cstddef> // size_t
#include <cstdint> // int64_t, uint64_t #include <cstdint> // int64_t, uint64_t
#include <cstdlib> // strtof, strtod, strtold #include <cstdlib> // strtof, strtod, strtold
#include <cstring> // memcpy
#include <limits> // numeric_limits #include <limits> // numeric_limits
#include <string> // string #include <string> // string
#include <type_traits> // conditional, integral_constant, is_signed, true_type, false_type #include <type_traits> // 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 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"); static_assert(sizeof(double) == sizeof(std::uint64_t), "double must have 64 bits");
std::memcpy(&value, &bits, sizeof(value)); value = bit_cast<double>(bits);
} }
/// the float with the IEEE-754 bits @a bits (the lower 32) /// the float with the IEEE-754 bits @a bits (the lower 32)
inline void float_from_bits(std::uint64_t bits, float& value) noexcept 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"); static_assert(sizeof(float) == sizeof(std::uint32_t), "float must have 32 bits");
const auto bits32 = static_cast<std::uint32_t>(bits); value = bit_cast<float>(static_cast<std::uint32_t>(bits));
std::memcpy(&value, &bits32, sizeof(bits32));
} }
/// the powers of ten that are exact in binary64 (up to 10^22) /// the powers of ten that are exact in binary64 (up to 10^22)
@@ -23402,10 +23530,6 @@ NLOHMANN_JSON_NAMESPACE_END
#include <utility> // move #include <utility> // move
#include <vector> // vector #include <vector> // vector
#ifdef _MSC_VER
#include <cstdlib> // _byteswap_ushort, _byteswap_ulong, _byteswap_uint64
#endif
// #include <nlohmann/detail/bit_ops.hpp> // #include <nlohmann/detail/bit_ops.hpp>
// #include <nlohmann/detail/input/binary_reader.hpp> // #include <nlohmann/detail/input/binary_reader.hpp>
@@ -25892,7 +26016,7 @@ class binary_writer
{ {
out[0] = to_char_type(first_byte); out[0] = to_char_type(first_byte);
const unsigned unused_bits = 64u - ((8u << size_class) >> 1u); 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)); std::memcpy(out + 1, &tail, sizeof(tail));
return ((std::size_t{1} << size_class) >> 1u) + 1; return ((std::size_t{1} << size_class) >> 1u) + 1;
} }
@@ -27336,53 +27460,14 @@ class binary_writer
// Utility functions // // 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<std::uint16_t>((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 @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 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 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 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 shuffle). (The two memcpy calls, rather than bit_cast, keep GCC from
bytes and are folded away by every optimizer. inlining differently in C++20, which made writing slower.)
*/ */
template<typename UIntType, std::size_t N> template<typename UIntType, std::size_t N>
static void byte_swap_buffer(std::array<CharType, N>& a) noexcept static void byte_swap_buffer(std::array<CharType, N>& a) noexcept
@@ -27390,11 +27475,11 @@ class binary_writer
static_assert(sizeof(UIntType) == N, "swap width must match the buffer size"); static_assert(sizeof(UIntType) == N, "swap width must match the buffer size");
UIntType v{}; UIntType v{};
std::memcpy(&v, a.data(), sizeof(v)); std::memcpy(&v, a.data(), sizeof(v));
v = byte_swap(v); v = byteswap(v);
std::memcpy(a.data(), &v, sizeof(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 // common 2/4/8-byte number payloads, std::reverse for any other size
static void reverse_bytes(std::array<CharType, 2>& a) noexcept static void reverse_bytes(std::array<CharType, 2>& a) noexcept
{ {
@@ -27519,9 +27604,7 @@ class binary_writer
#endif #endif
static_assert(sizeof(std::uint8_t) == sizeof(CharType), "size of CharType must be equal to std::uint8_t"); static_assert(sizeof(std::uint8_t) == sizeof(CharType), "size of CharType must be equal to std::uint8_t");
CharType result; return bit_cast<CharType>(x);
std::memcpy(&result, &x, sizeof(x));
return result;
} }
template<typename C = CharType, template<typename C = CharType,
@@ -27605,9 +27688,6 @@ NLOHMANN_JSON_NAMESPACE_END
#include <limits> // numeric_limits #include <limits> // numeric_limits
#include <type_traits> // conditional #include <type_traits> // conditional
#ifdef _MSC_VER
#include <cstdlib> // _byteswap_uint64
#endif
// SSE2 (every x86-64 CPU) and NEON (every 64-bit Arm CPU) convert the 16 // SSE2 (every x86-64 CPU) and NEON (every 64-bit Arm CPU) convert the 16
// digits of a double at once // digits of a double at once
@@ -27624,6 +27704,8 @@ NLOHMANN_JSON_NAMESPACE_END
#define JSON_DTOA_NEON 0 #define JSON_DTOA_NEON 0
#endif #endif
// #include <nlohmann/detail/bit_ops.hpp>
// #include <nlohmann/detail/conversions/zmij.hpp> // #include <nlohmann/detail/conversions/zmij.hpp>
// __ _____ _____ _____ // __ _____ _____ _____
// __| | __| | | | JSON for Modern C++ // __| | __| | | | JSON for Modern C++
@@ -27881,16 +27963,6 @@ For a detailed description of the algorithm see:
namespace dtoa_impl namespace dtoa_impl
{ {
template<typename Target, typename Source>
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 struct diyfp // f * 2^e
{ {
static constexpr int kPrecision = 64; // = q static constexpr int kPrecision = 64; // = q
@@ -28045,7 +28117,7 @@ boundaries compute_boundaries(FloatType value)
using bits_type = typename std::conditional<kPrecision == 24, std::uint32_t, std::uint64_t >::type; using bits_type = typename std::conditional<kPrecision == 24, std::uint32_t, std::uint64_t >::type;
const auto bits = static_cast<std::uint64_t>(reinterpret_bits<bits_type>(value)); const auto bits = static_cast<std::uint64_t>(bit_cast<bits_type>(value));
const std::uint64_t E = bits >> (kPrecision - 1); const std::uint64_t E = bits >> (kPrecision - 1);
const std::uint64_t F = bits & (kHiddenBit - 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) /// the byte stores once this is inlined)
inline void store_msb_first(char* p, std::uint64_t v) noexcept 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__ #if (defined(__BYTE_ORDER__) && defined(__ORDER_LITTLE_ENDIAN__) && __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__) || defined(_MSC_VER) // (MSVC: little-endian on all its targets)
v = __builtin_bswap64(v); v = byteswap(v);
std::memcpy(p, &v, sizeof(v)); std::memcpy(p, &v, sizeof(v));
#elif defined(__BYTE_ORDER__) && defined(__ORDER_BIG_ENDIAN__) && __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__ #elif defined(__BYTE_ORDER__) && defined(__ORDER_BIG_ENDIAN__) && __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__
std::memcpy(p, &v, sizeof(v)); 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 #else
for (unsigned i = 0; i < 8; ++i) 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<FloatType>::value, static_assert(is_binary64<FloatType>::value,
"internal error: the conversion of Zmij needs IEEE 754 binary64 numbers"); "internal error: the conversion of Zmij needs IEEE 754 binary64 numbers");
std::uint64_t bits = 0; const auto bits = bit_cast<std::uint64_t>(value);
std::memcpy(&bits, &value, sizeof(bits));
const zmij::shortest_decimal d = zmij::to_shortest(bits); const zmij::shortest_decimal d = zmij::to_shortest(bits);
if (JSON_HEDLEY_LIKELY(last - first >= 41)) if (JSON_HEDLEY_LIKELY(last - first >= 41))
{ {
+5 -10
View File
@@ -6166,17 +6166,13 @@ class output_buffer
} }
private: private:
/// the size of a growth step (a function: std::min() takes a reference, /// the size of a growth step
/// which a static constexpr member does not have before C++17) static constexpr std::size_t step = 0x10000;
static constexpr std::size_t step() noexcept
{
return 0x10000;
}
static StringType& sized(StringType& out, std::size_t estimate) static StringType& sized(StringType& out, std::size_t estimate)
{ {
out.reserve(estimate); out.reserve(estimate);
out.resize((std::min)((std::max)(estimate, static_cast<std::size_t>(64)), step())); out.resize(detail::clamp(estimate, std::size_t{64}, step));
return out; return out;
} }
@@ -6185,7 +6181,7 @@ class output_buffer
const auto used = static_cast<std::size_t>(m_pos - m_out.data()); const auto used = static_cast<std::size_t>(m_pos - m_out.data());
// (a step does not go beyond the reserved estimate, so that a good // (a step does not go beyond the reserved estimate, so that a good
// estimate is never copied to a larger allocation) // 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()) if (size > m_out.capacity())
{ {
m_out.reserve((std::max)(m_out.capacity() * 2, size)); 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) static char* write_double_value_at(char* w, double x)
{ {
// (from the bits: without the checks of to_chars()) // (from the bits: without the checks of to_chars())
std::uint64_t bits = 0; auto bits = bit_cast<std::uint64_t>(x);
std::memcpy(&bits, &x, sizeof(bits));
if (NLOHMANN_VIEW_UNLIKELY((bits & 0x7FF0000000000000u) == 0x7FF0000000000000u)) if (NLOHMANN_VIEW_UNLIKELY((bits & 0x7FF0000000000000u) == 0x7FF0000000000000u))
{ {
return write_text_at(w, "null", 4); return write_text_at(w, "null", 4);
+129
View File
@@ -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 <https://nlohmann.me>
// SPDX-License-Identifier: MIT
#include "doctest_compatibility.h"
#include <nlohmann/json.hpp>
#include <array>
#include <cstdint>
#include <limits>
#include <type_traits>
// 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<double>::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<std::uint64_t>(1.0) == 0x3FF0000000000000u);
CHECK(bit_cast<std::uint64_t>(-2.0) == 0xC000000000000000u);
CHECK(bit_cast<std::uint32_t>(1.0f) == 0x3F800000u);
CHECK(bit_cast<double>(std::uint64_t{0x4009000000000000u}) == 3.125);
CHECK(bit_cast<float>(std::uint32_t{0xC0490000u}) == -3.140625f);
CHECK(bit_cast<std::int8_t>(std::uint8_t{0xFF}) == -1);
// to and from byte arrays
const auto bytes = bit_cast<std::array<std::uint8_t, 4>>(std::uint32_t{0x01020304u});
CHECK(bit_cast<std::uint32_t>(bytes) == 0x01020304u);
static_assert(noexcept(bit_cast<std::uint64_t>(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<std::uint64_t>::max)()
})
{
CHECK(byteswap(byteswap(x)) == x);
CHECK(byteswap(byteswap(static_cast<std::uint32_t>(x))) == static_cast<std::uint32_t>(x));
CHECK(byteswap(byteswap(static_cast<std::uint16_t>(x))) == static_cast<std::uint16_t>(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);
}
}
+8 -8
View File
@@ -13,7 +13,7 @@
#include "doctest_compatibility.h" #include "doctest_compatibility.h"
#include <nlohmann/json.hpp> #include <nlohmann/json.hpp>
using nlohmann::detail::dtoa_impl::reinterpret_bits; using nlohmann::detail::bit_cast;
#include <algorithm> #include <algorithm>
#include <array> #include <array>
@@ -44,7 +44,7 @@ float make_float(uint32_t sign_bit, uint32_t biased_exponent, uint32_t significa
bits |= biased_exponent << 23; bits |= biased_exponent << 23;
bits |= significand; bits |= significand;
return reinterpret_bits<float>(bits); return bit_cast<float>(bits);
} }
// ldexp -- convert f * 2^e to IEEE single precision // ldexp -- convert f * 2^e to IEEE single precision
@@ -81,7 +81,7 @@ float make_float(uint64_t f, int e)
: static_cast<uint64_t>(e + kExponentBias); : static_cast<uint64_t>(e + kExponentBias);
const uint64_t bits = (f & kSignificandMask) | (biased_exponent << kPhysicalSignificandSize); const uint64_t bits = (f & kSignificandMask) | (biased_exponent << kPhysicalSignificandSize);
return reinterpret_bits<float>(static_cast<uint32_t>(bits)); return bit_cast<float>(static_cast<uint32_t>(bits));
} }
double make_double(uint64_t sign_bit, uint64_t biased_exponent, uint64_t significand) 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 |= biased_exponent << 52;
bits |= significand; bits |= significand;
return reinterpret_bits<double>(bits); return bit_cast<double>(bits);
} }
// ldexp -- convert f * 2^e to IEEE double precision // ldexp -- convert f * 2^e to IEEE double precision
@@ -133,7 +133,7 @@ double make_double(uint64_t f, int e)
: static_cast<uint64_t>(e + kExponentBias); : static_cast<uint64_t>(e + kExponentBias);
const uint64_t bits = (f & kSignificandMask) | (biased_exponent << kPhysicalSignificandSize); const uint64_t bits = (f & kSignificandMask) | (biased_exponent << kPhysicalSignificandSize);
return reinterpret_bits<double>(bits); return bit_cast<double>(bits);
} }
} // namespace } // 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); const std::string text = digits + "e" + std::to_string(e);
// (compared bit for bit: v is positive and finite, and -Wfloat-equal) // (compared bit for bit: v is positive and finite, and -Wfloat-equal)
return reinterpret_bits<std::uint64_t>(parse_double(text)) == reinterpret_bits<std::uint64_t>(v); return bit_cast<std::uint64_t>(parse_double(text)) == bit_cast<std::uint64_t>(v);
} }
/// Check the representation of a positive finite double: it reads back as /// Check the representation of a positive finite double: it reads back as
@@ -665,7 +665,7 @@ void check_shortest(double v)
CAPTURE(text) CAPTURE(text)
CHECK(parse_double(text) == v); CHECK(parse_double(text) == v);
// the layout is that of format_buffer() for the digits of Zmij // the layout is that of format_buffer() for the digits of Zmij
const auto sd = nlohmann::detail::zmij::to_shortest(reinterpret_bits<std::uint64_t>(v)); const auto sd = nlohmann::detail::zmij::to_shortest(bit_cast<std::uint64_t>(v));
const std::uint64_t significand = sd.has_digit ? (sd.integral * 10) + sd.digit : sd.integral; 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; int exponent = sd.has_digit ? sd.exponent : sd.exponent + 1;
std::string significand_digits = std::to_string(significand); 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) for (int i = 0; i < 100000; ++i)
{ {
const std::uint64_t bits = rng() & 0x7FFFFFFFFFFFFFFFu; const std::uint64_t bits = rng() & 0x7FFFFFFFFFFFFFFFu;
const auto v = reinterpret_bits<double>(bits); const auto v = bit_cast<double>(bits);
if (std::isfinite(v) && bits != 0) if (std::isfinite(v) && bits != 0)
{ {
check_shortest(v); check_shortest(v);