* 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> * Name the target byte order once (JSON_LITTLE_ENDIAN_TARGET, JSON_BIG_ENDIAN_TARGET) to_chars.hpp and bit_ops.hpp repeated the long __BYTE_ORDER__/_MSC_VER conditions; macro_scope.hpp now defines them once (undefined again in macro_unscope.hpp). Signed-off-by: Niels Lohmann <mail@nlohmann.me> * Explain why the serializer growth bound is not a clamp Signed-off-by: Niels Lohmann <mail@nlohmann.me> --------- Signed-off-by: Niels Lohmann <mail@nlohmann.me>
130 lines
4.6 KiB
C++
130 lines
4.6 KiB
C++
// __ _____ _____ _____
|
|
// __| | __| | | | 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);
|
|
}
|
|
}
|