Make the value lifecycle inline-friendly

Signed-off-by: Niels Lohmann <mail@nlohmann.me>
This commit is contained in:
Niels Lohmann committed 2026-10-15 11:46:15 +02:00
1 parent ebab8a11cc
commit 106065e62d
4 files changed
+586 -104

No files matched your search

+146 -52
View File
@@ -585,26 +585,12 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
switch (t)
{
case value_t::object:
{
object = create<object_t>();
break;
}
case value_t::array:
{
array = create<array_t>();
break;
}
case value_t::string:
{
string = create<string_t>("");
break;
}
case value_t::binary:
{
binary = create<binary_t>();
// allocating: kept out of line, so the scalar cases below inline
init_owned(t);
break;
}
@@ -651,6 +637,34 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
}
}
JSON_HEDLEY_NEVER_INLINE
void init_owned(value_t t)
{
switch (t)
{
case value_t::object:
object = create<object_t>();
break;
case value_t::array:
array = create<array_t>();
break;
case value_t::string:
string = create<string_t>("");
break;
case value_t::binary:
binary = create<binary_t>();
break;
case value_t::null:
case value_t::boolean:
case value_t::number_integer:
case value_t::number_unsigned:
case value_t::number_float:
case value_t::discarded:
default:
break;
}
}
/// constructor for strings
json_value(const string_t& value) : string(create<string_t>(value)) {}
@@ -911,7 +925,18 @@ public:
}
}
// only strings, binaries, arrays and objects own memory; keep the
// check for them inline so destroying a scalar is a compare and a
// branch, and leave the actual work to an out-of-line function
void destroy(value_t t)
{
if (JSON_HEDLEY_UNLIKELY(t == value_t::string || t == value_t::binary || t == value_t::object || t == value_t::array))
{
destroy_owned(t);
}
}
JSON_HEDLEY_NEVER_INLINE void destroy_owned(value_t t)
{
switch (t)
{
@@ -2559,15 +2584,15 @@ public:
// check of passed value is valid
other.assert_invariant();
if (m_data.m_type == value_t::object || m_data.m_type == value_t::array)
if (JSON_HEDLEY_LIKELY(m_data.m_type != value_t::object && m_data.m_type != value_t::array
&& m_data.m_type != value_t::string && m_data.m_type != value_t::binary))
{
// copying the container directly would call this constructor again
// for every element, once per nesting level
copy_structured(other);
// null, boolean, numbers, discarded: plain copy of the union
m_data.m_value = other.m_data.m_value;
}
else
{
copy_leaf_value(other, *this);
copy_owning(other);
}
assert_invariant();
@@ -2799,6 +2824,22 @@ public:
// value access //
//////////////////
/// out-of-line part of the copy constructor for owning types
JSON_HEDLEY_NEVER_INLINE
void copy_owning(const basic_json& other)
{
if (m_data.m_type == value_t::object || m_data.m_type == value_t::array)
{
// copying the container directly would call this constructor again
// for every element, once per nesting level
copy_structured(other);
}
else
{
copy_leaf_value(other, *this);
}
}
/// get a pointer to the value (object)
object_t* get_impl_ptr(object_t* /*unused*/) noexcept
{
@@ -5173,6 +5214,20 @@ public:
/// @name lexicographical comparison operators
/// @{
// the same-type cases for non-owning scalars, shared by JSON_IMPLEMENT_OPERATOR
// and JSON_IMPLEMENT_SCALAR_FAST
#define JSON_IMPLEMENT_SCALAR_CASES(op, null_result) \
case value_t::null: \
return (null_result); \
case value_t::boolean: \
return (lhs.m_data.m_value.boolean) op (rhs.m_data.m_value.boolean); \
case value_t::number_integer: \
return (lhs.m_data.m_value.number_integer) op (rhs.m_data.m_value.number_integer); \
case value_t::number_unsigned: \
return (lhs.m_data.m_value.number_unsigned) op (rhs.m_data.m_value.number_unsigned); \
case value_t::number_float: \
return (lhs.m_data.m_value.number_float) op (rhs.m_data.m_value.number_float);
// note parentheses around operands are necessary; see
// https://github.com/nlohmann/json/issues/1530
// Mixed signed/unsigned integer comparisons check whether the signed value
@@ -5209,24 +5264,11 @@ public:
return (*lhs.m_data.m_value.object) op (*rhs.m_data.m_value.object); \
} \
\
case value_t::null: \
return (null_result); \
\
JSON_IMPLEMENT_SCALAR_CASES(op, null_result) \
\
case value_t::string: \
return (*lhs.m_data.m_value.string) op (*rhs.m_data.m_value.string); \
\
case value_t::boolean: \
return (lhs.m_data.m_value.boolean) op (rhs.m_data.m_value.boolean); \
\
case value_t::number_integer: \
return (lhs.m_data.m_value.number_integer) op (rhs.m_data.m_value.number_integer); \
\
case value_t::number_unsigned: \
return (lhs.m_data.m_value.number_unsigned) op (rhs.m_data.m_value.number_unsigned); \
\
case value_t::number_float: \
return (lhs.m_data.m_value.number_float) op (rhs.m_data.m_value.number_float); \
\
case value_t::binary: \
return (*lhs.m_data.m_value.binary) op (*rhs.m_data.m_value.binary); \
\
@@ -5270,6 +5312,24 @@ public:
\
return (default_result);
// inline fast path for two values of the same non-owning scalar type; anything
// else falls through to the out-of-line general comparison
#define JSON_IMPLEMENT_SCALAR_FAST(op, null_result) \
if (lhs.m_data.m_type == rhs.m_data.m_type) \
{ \
switch (lhs.m_data.m_type) \
{ \
JSON_IMPLEMENT_SCALAR_CASES(op, null_result) \
case value_t::object: \
case value_t::array: \
case value_t::string: \
case value_t::binary: \
case value_t::discarded: \
default: \
break; \
} \
}
JSON_PRIVATE_UNLESS_TESTED:
// returns true if:
// - any operand is NaN and the other operand is of number type
@@ -5297,6 +5357,46 @@ public:
return compares_unordered(*this, rhs, inverse);
}
// out-of-line general comparisons; the operators below handle same-type
// scalars inline and fall through to these
JSON_HEDLEY_NEVER_INLINE
static bool equal_general(const_reference lhs, const_reference rhs) noexcept
{
#ifdef __GNUC__
JSON_HEDLEY_DIAGNOSTIC_PUSH
JSON_HEDLEY_PRAGMA(GCC diagnostic ignored "-Wfloat-equal")
#endif
JSON_IMPLEMENT_OPERATOR( ==, true, false, false,
compare_iteratively<false>(lhs, rhs, false) == compare_result::equal, true)
#ifdef __GNUC__
JSON_HEDLEY_DIAGNOSTIC_POP
#endif
}
JSON_HEDLEY_NEVER_INLINE
static bool less_general(const_reference lhs, const_reference rhs) noexcept
{
// default_result is used if we cannot compare values. In that case,
// we compare types. Note we have to call the operator explicitly,
// because MSVC has problems otherwise.
JSON_IMPLEMENT_OPERATOR( <, false, false, operator<(lhs_type, rhs_type),
compare_iteratively<true>(lhs, rhs, true) == compare_result::less, false)
}
#if JSON_HAS_THREE_WAY_COMPARISON
JSON_HEDLEY_NEVER_INLINE
static std::partial_ordering three_way_general(const_reference lhs, const_reference rhs) noexcept // *NOPAD*
{
// default_result is used if we cannot compare values. In that case,
// we compare types.
JSON_IMPLEMENT_OPERATOR(<=>, // *NOPAD*
std::partial_ordering::equivalent,
std::partial_ordering::unordered,
lhs_type <=> rhs_type, // *NOPAD*
to_partial_ordering(compare_iteratively<true>(lhs, rhs, false)), true)
}
#endif
public:
#if JSON_HAS_THREE_WAY_COMPARISON
/// @brief comparison: equal
@@ -5308,11 +5408,11 @@ public:
JSON_HEDLEY_PRAGMA(GCC diagnostic ignored "-Wfloat-equal")
#endif
const_reference lhs = *this;
JSON_IMPLEMENT_OPERATOR( ==, true, false, false,
compare_iteratively<false>(lhs, rhs, false) == compare_result::equal, true)
JSON_IMPLEMENT_SCALAR_FAST( ==, true)
#ifdef __GNUC__
JSON_HEDLEY_DIAGNOSTIC_POP
#endif
return equal_general(lhs, rhs);
}
/// @brief comparison: equal
@@ -5329,13 +5429,8 @@ public:
std::partial_ordering operator<=>(const_reference rhs) const noexcept // *NOPAD*
{
const_reference lhs = *this;
// default_result is used if we cannot compare values. In that case,
// we compare types.
JSON_IMPLEMENT_OPERATOR(<=>, // *NOPAD*
std::partial_ordering::equivalent,
std::partial_ordering::unordered,
lhs_type <=> rhs_type, // *NOPAD*
to_partial_ordering(compare_iteratively<true>(lhs, rhs, false)), true)
JSON_IMPLEMENT_SCALAR_FAST(<=>, std::partial_ordering::equivalent) // *NOPAD*
return three_way_general(lhs, rhs);
}
/// @brief comparison: 3-way
@@ -5423,11 +5518,11 @@ public:
JSON_HEDLEY_DIAGNOSTIC_PUSH
JSON_HEDLEY_PRAGMA(GCC diagnostic ignored "-Wfloat-equal")
#endif
JSON_IMPLEMENT_OPERATOR( ==, true, false, false,
compare_iteratively<false>(lhs, rhs, false) == compare_result::equal, true)
JSON_IMPLEMENT_SCALAR_FAST( ==, true)
#ifdef __GNUC__
JSON_HEDLEY_DIAGNOSTIC_POP
#endif
return equal_general(lhs, rhs);
}
// The friend comparisons with a scalar name the JSON type via decltype of
@@ -5482,11 +5577,8 @@ public:
/// @sa https://json.nlohmann.me/api/basic_json/operator_lt/
friend bool operator<(const_reference lhs, const_reference rhs) noexcept
{
// default_result is used if we cannot compare values. In that case,
// we compare types. Note we have to call the operator explicitly,
// because MSVC has problems otherwise.
JSON_IMPLEMENT_OPERATOR( <, false, false, operator<(lhs_type, rhs_type),
compare_iteratively<true>(lhs, rhs, true) == compare_result::less, false)
JSON_IMPLEMENT_SCALAR_FAST( <, false)
return less_general(lhs, rhs);
}
/// @brief comparison: less than
@@ -5597,6 +5689,8 @@ public:
#endif
#undef JSON_IMPLEMENT_OPERATOR
#undef JSON_IMPLEMENT_SCALAR_FAST
#undef JSON_IMPLEMENT_SCALAR_CASES
/// @}
+146 -52
View File
@@ -30325,26 +30325,12 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
switch (t)
{
case value_t::object:
{
object = create<object_t>();
break;
}
case value_t::array:
{
array = create<array_t>();
break;
}
case value_t::string:
{
string = create<string_t>("");
break;
}
case value_t::binary:
{
binary = create<binary_t>();
// allocating: kept out of line, so the scalar cases below inline
init_owned(t);
break;
}
@@ -30391,6 +30377,34 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
}
}
JSON_HEDLEY_NEVER_INLINE
void init_owned(value_t t)
{
switch (t)
{
case value_t::object:
object = create<object_t>();
break;
case value_t::array:
array = create<array_t>();
break;
case value_t::string:
string = create<string_t>("");
break;
case value_t::binary:
binary = create<binary_t>();
break;
case value_t::null:
case value_t::boolean:
case value_t::number_integer:
case value_t::number_unsigned:
case value_t::number_float:
case value_t::discarded:
default:
break;
}
}
/// constructor for strings
json_value(const string_t& value) : string(create<string_t>(value)) {}
@@ -30651,7 +30665,18 @@ public:
}
}
// only strings, binaries, arrays and objects own memory; keep the
// check for them inline so destroying a scalar is a compare and a
// branch, and leave the actual work to an out-of-line function
void destroy(value_t t)
{
if (JSON_HEDLEY_UNLIKELY(t == value_t::string || t == value_t::binary || t == value_t::object || t == value_t::array))
{
destroy_owned(t);
}
}
JSON_HEDLEY_NEVER_INLINE void destroy_owned(value_t t)
{
switch (t)
{
@@ -32299,15 +32324,15 @@ public:
// check of passed value is valid
other.assert_invariant();
if (m_data.m_type == value_t::object || m_data.m_type == value_t::array)
if (JSON_HEDLEY_LIKELY(m_data.m_type != value_t::object && m_data.m_type != value_t::array
&& m_data.m_type != value_t::string && m_data.m_type != value_t::binary))
{
// copying the container directly would call this constructor again
// for every element, once per nesting level
copy_structured(other);
// null, boolean, numbers, discarded: plain copy of the union
m_data.m_value = other.m_data.m_value;
}
else
{
copy_leaf_value(other, *this);
copy_owning(other);
}
assert_invariant();
@@ -32539,6 +32564,22 @@ public:
// value access //
//////////////////
/// out-of-line part of the copy constructor for owning types
JSON_HEDLEY_NEVER_INLINE
void copy_owning(const basic_json& other)
{
if (m_data.m_type == value_t::object || m_data.m_type == value_t::array)
{
// copying the container directly would call this constructor again
// for every element, once per nesting level
copy_structured(other);
}
else
{
copy_leaf_value(other, *this);
}
}
/// get a pointer to the value (object)
object_t* get_impl_ptr(object_t* /*unused*/) noexcept
{
@@ -34913,6 +34954,20 @@ public:
/// @name lexicographical comparison operators
/// @{
// the same-type cases for non-owning scalars, shared by JSON_IMPLEMENT_OPERATOR
// and JSON_IMPLEMENT_SCALAR_FAST
#define JSON_IMPLEMENT_SCALAR_CASES(op, null_result) \
case value_t::null: \
return (null_result); \
case value_t::boolean: \
return (lhs.m_data.m_value.boolean) op (rhs.m_data.m_value.boolean); \
case value_t::number_integer: \
return (lhs.m_data.m_value.number_integer) op (rhs.m_data.m_value.number_integer); \
case value_t::number_unsigned: \
return (lhs.m_data.m_value.number_unsigned) op (rhs.m_data.m_value.number_unsigned); \
case value_t::number_float: \
return (lhs.m_data.m_value.number_float) op (rhs.m_data.m_value.number_float);
// note parentheses around operands are necessary; see
// https://github.com/nlohmann/json/issues/1530
// Mixed signed/unsigned integer comparisons check whether the signed value
@@ -34949,24 +35004,11 @@ public:
return (*lhs.m_data.m_value.object) op (*rhs.m_data.m_value.object); \
} \
\
case value_t::null: \
return (null_result); \
\
JSON_IMPLEMENT_SCALAR_CASES(op, null_result) \
\
case value_t::string: \
return (*lhs.m_data.m_value.string) op (*rhs.m_data.m_value.string); \
\
case value_t::boolean: \
return (lhs.m_data.m_value.boolean) op (rhs.m_data.m_value.boolean); \
\
case value_t::number_integer: \
return (lhs.m_data.m_value.number_integer) op (rhs.m_data.m_value.number_integer); \
\
case value_t::number_unsigned: \
return (lhs.m_data.m_value.number_unsigned) op (rhs.m_data.m_value.number_unsigned); \
\
case value_t::number_float: \
return (lhs.m_data.m_value.number_float) op (rhs.m_data.m_value.number_float); \
\
case value_t::binary: \
return (*lhs.m_data.m_value.binary) op (*rhs.m_data.m_value.binary); \
\
@@ -35010,6 +35052,24 @@ public:
\
return (default_result);
// inline fast path for two values of the same non-owning scalar type; anything
// else falls through to the out-of-line general comparison
#define JSON_IMPLEMENT_SCALAR_FAST(op, null_result) \
if (lhs.m_data.m_type == rhs.m_data.m_type) \
{ \
switch (lhs.m_data.m_type) \
{ \
JSON_IMPLEMENT_SCALAR_CASES(op, null_result) \
case value_t::object: \
case value_t::array: \
case value_t::string: \
case value_t::binary: \
case value_t::discarded: \
default: \
break; \
} \
}
JSON_PRIVATE_UNLESS_TESTED:
// returns true if:
// - any operand is NaN and the other operand is of number type
@@ -35037,6 +35097,46 @@ public:
return compares_unordered(*this, rhs, inverse);
}
// out-of-line general comparisons; the operators below handle same-type
// scalars inline and fall through to these
JSON_HEDLEY_NEVER_INLINE
static bool equal_general(const_reference lhs, const_reference rhs) noexcept
{
#ifdef __GNUC__
JSON_HEDLEY_DIAGNOSTIC_PUSH
JSON_HEDLEY_PRAGMA(GCC diagnostic ignored "-Wfloat-equal")
#endif
JSON_IMPLEMENT_OPERATOR( ==, true, false, false,
compare_iteratively<false>(lhs, rhs, false) == compare_result::equal, true)
#ifdef __GNUC__
JSON_HEDLEY_DIAGNOSTIC_POP
#endif
}
JSON_HEDLEY_NEVER_INLINE
static bool less_general(const_reference lhs, const_reference rhs) noexcept
{
// default_result is used if we cannot compare values. In that case,
// we compare types. Note we have to call the operator explicitly,
// because MSVC has problems otherwise.
JSON_IMPLEMENT_OPERATOR( <, false, false, operator<(lhs_type, rhs_type),
compare_iteratively<true>(lhs, rhs, true) == compare_result::less, false)
}
#if JSON_HAS_THREE_WAY_COMPARISON
JSON_HEDLEY_NEVER_INLINE
static std::partial_ordering three_way_general(const_reference lhs, const_reference rhs) noexcept // *NOPAD*
{
// default_result is used if we cannot compare values. In that case,
// we compare types.
JSON_IMPLEMENT_OPERATOR(<=>, // *NOPAD*
std::partial_ordering::equivalent,
std::partial_ordering::unordered,
lhs_type <=> rhs_type, // *NOPAD*
to_partial_ordering(compare_iteratively<true>(lhs, rhs, false)), true)
}
#endif
public:
#if JSON_HAS_THREE_WAY_COMPARISON
/// @brief comparison: equal
@@ -35048,11 +35148,11 @@ public:
JSON_HEDLEY_PRAGMA(GCC diagnostic ignored "-Wfloat-equal")
#endif
const_reference lhs = *this;
JSON_IMPLEMENT_OPERATOR( ==, true, false, false,
compare_iteratively<false>(lhs, rhs, false) == compare_result::equal, true)
JSON_IMPLEMENT_SCALAR_FAST( ==, true)
#ifdef __GNUC__
JSON_HEDLEY_DIAGNOSTIC_POP
#endif
return equal_general(lhs, rhs);
}
/// @brief comparison: equal
@@ -35069,13 +35169,8 @@ public:
std::partial_ordering operator<=>(const_reference rhs) const noexcept // *NOPAD*
{
const_reference lhs = *this;
// default_result is used if we cannot compare values. In that case,
// we compare types.
JSON_IMPLEMENT_OPERATOR(<=>, // *NOPAD*
std::partial_ordering::equivalent,
std::partial_ordering::unordered,
lhs_type <=> rhs_type, // *NOPAD*
to_partial_ordering(compare_iteratively<true>(lhs, rhs, false)), true)
JSON_IMPLEMENT_SCALAR_FAST(<=>, std::partial_ordering::equivalent) // *NOPAD*
return three_way_general(lhs, rhs);
}
/// @brief comparison: 3-way
@@ -35163,11 +35258,11 @@ public:
JSON_HEDLEY_DIAGNOSTIC_PUSH
JSON_HEDLEY_PRAGMA(GCC diagnostic ignored "-Wfloat-equal")
#endif
JSON_IMPLEMENT_OPERATOR( ==, true, false, false,
compare_iteratively<false>(lhs, rhs, false) == compare_result::equal, true)
JSON_IMPLEMENT_SCALAR_FAST( ==, true)
#ifdef __GNUC__
JSON_HEDLEY_DIAGNOSTIC_POP
#endif
return equal_general(lhs, rhs);
}
// The friend comparisons with a scalar name the JSON type via decltype of
@@ -35222,11 +35317,8 @@ public:
/// @sa https://json.nlohmann.me/api/basic_json/operator_lt/
friend bool operator<(const_reference lhs, const_reference rhs) noexcept
{
// default_result is used if we cannot compare values. In that case,
// we compare types. Note we have to call the operator explicitly,
// because MSVC has problems otherwise.
JSON_IMPLEMENT_OPERATOR( <, false, false, operator<(lhs_type, rhs_type),
compare_iteratively<true>(lhs, rhs, true) == compare_result::less, false)
JSON_IMPLEMENT_SCALAR_FAST( <, false)
return less_general(lhs, rhs);
}
/// @brief comparison: less than
@@ -35337,6 +35429,8 @@ public:
#endif
#undef JSON_IMPLEMENT_OPERATOR
#undef JSON_IMPLEMENT_SCALAR_FAST
#undef JSON_IMPLEMENT_SCALAR_CASES
/// @}
+155
View File
@@ -19,6 +19,7 @@
#include <cctype>
#include <cstdint>
#include <limits>
#include <map>
#include <string>
#include <utility>
@@ -1149,3 +1150,157 @@ TEST_CASE("operator<=> of binary values with a different subtype does not depend
}
}
#endif
TEST_CASE("comparison of scalars (inline fast path)")
{
const json::number_integer_t neg = -1;
const json::number_integer_t pos = 5;
const json::number_unsigned_t big = (std::numeric_limits<json::number_unsigned_t>::max)();
SECTION("same type")
{
CHECK(json(pos) == json(pos));
CHECK(json(pos) != json(neg));
CHECK(json(neg) < json(pos));
CHECK_FALSE(json(pos) < json(neg));
CHECK_FALSE(json(pos) < json(pos));
CHECK(json(pos) <= json(pos));
CHECK(json(1u) == json(1u));
CHECK(json(1u) < json(2u));
CHECK_FALSE(json(2u) < json(1u));
CHECK(json(1.5) == json(1.5));
CHECK(json(1.5) < json(2.5));
CHECK_FALSE(json(2.5) < json(1.5));
CHECK(json(-0.0) == json(0.0));
}
SECTION("mixed number types")
{
// negative signed vs unsigned must not wrap around
CHECK(json(neg) < json(1u));
CHECK(json(1u) > json(neg));
CHECK(json(neg) != json(big));
CHECK(json(neg) < json(big));
CHECK_FALSE(json(big) < json(neg));
CHECK(json(pos) == json(5u));
CHECK(json(5u) == json(pos));
CHECK(json(pos) < json(6u));
CHECK(json(pos) == json(5.0));
CHECK(json(5.0) == json(pos));
CHECK(json(5u) == json(5.0));
CHECK(json(neg) < json(0.5));
CHECK(json(0.5) < json(1u));
}
SECTION("boolean and null")
{
CHECK(json(true) == json(true));
CHECK(json(true) != json(false));
CHECK(json(false) < json(true));
CHECK_FALSE(json(true) < json(false));
CHECK_FALSE(json(true) < json(true));
CHECK(json(nullptr) == json(nullptr));
CHECK_FALSE(json(nullptr) < json(nullptr));
CHECK(json(nullptr) <= json(nullptr));
CHECK(json(nullptr) < json(false));
CHECK(json(nullptr) != json(false));
CHECK(json(nullptr) != json(0));
}
SECTION("string vs number")
{
CHECK(json("5") != json(5));
CHECK(json(5) != json("5"));
CHECK(json(5) < json("5")); // number < string by type order
CHECK_FALSE(json("5") < json(5));
CHECK(json(1.5) < json(""));
CHECK(json("a") == json("a"));
CHECK(json("a") < json("b"));
}
SECTION("NaN")
{
const json nan = std::numeric_limits<json::number_float_t>::quiet_NaN();
const json one = 1.0;
CHECK_FALSE(nan == nan);
CHECK(nan != nan);
CHECK_FALSE(nan < nan);
CHECK_FALSE(nan <= nan);
CHECK_FALSE(nan >= nan);
CHECK_FALSE(nan > nan);
CHECK_FALSE(nan == one);
CHECK_FALSE(one == nan);
CHECK_FALSE(nan < one);
CHECK_FALSE(one < nan);
CHECK_FALSE(nan <= one);
CHECK_FALSE(one <= nan);
CHECK_FALSE(nan >= one);
CHECK_FALSE(one >= nan);
// NaN against other number types
CHECK_FALSE(nan == json(1));
CHECK_FALSE(json(1) == nan);
CHECK_FALSE(nan < json(1u));
CHECK_FALSE(json(1u) < nan);
#if JSON_HAS_THREE_WAY_COMPARISON
CHECK((nan <=> nan) == std::partial_ordering::unordered); // *NOPAD*
CHECK((nan <=> one) == std::partial_ordering::unordered); // *NOPAD*
CHECK((one <=> nan) == std::partial_ordering::unordered); // *NOPAD*
CHECK((one <=> json(2.0)) == std::partial_ordering::less); // *NOPAD*
CHECK((json(2.0) <=> one) == std::partial_ordering::greater); // *NOPAD*
CHECK((one <=> one) == std::partial_ordering::equivalent); // *NOPAD*
CHECK((json(neg) <=> json(1u)) == std::partial_ordering::less); // *NOPAD*
CHECK((json(nullptr) <=> json(nullptr)) == std::partial_ordering::equivalent); // *NOPAD*
CHECK((json(true) <=> json(false)) == std::partial_ordering::greater); // *NOPAD*
#endif
}
SECTION("discarded")
{
const json d(json::value_t::discarded);
CHECK_FALSE(d == d);
CHECK_FALSE(d < d);
CHECK_FALSE(d == json(1));
CHECK_FALSE(json(1) == d);
CHECK_FALSE(d < json(1));
CHECK_FALSE(json(1) < d);
CHECK_FALSE(d == json(nullptr));
CHECK_FALSE(json(nullptr) == d);
#if JSON_HAS_THREE_WAY_COMPARISON
CHECK((d <=> d) == std::partial_ordering::unordered); // *NOPAD*
CHECK((d <=> json(1)) == std::partial_ordering::unordered); // *NOPAD*
CHECK((json(1) <=> d) == std::partial_ordering::unordered); // *NOPAD*
#endif
}
SECTION("scalar overloads")
{
CHECK(json(5) == 5);
CHECK(5 == json(5));
CHECK(json(5) < 6);
CHECK(4 < json(5));
CHECK(json(1.5) == 1.5);
CHECK(json(true) == true);
CHECK(json(5) != 6);
CHECK(json(1u) == 1.0);
}
SECTION("structured values still compare")
{
CHECK(json({1, 2}) == json({1, 2}));
CHECK(json({1, 2}) < json({1, 3}));
CHECK(json({{"a", 1}}) == json({{"a", 1}}));
CHECK(json::binary({1, 2}) == json::binary({1, 2}));
CHECK(json(1) < json::array());
CHECK(json::object() < json::array());
}
}
+139
View File
@@ -8,6 +8,9 @@
#include "doctest_compatibility.h"
#include <string>
#include <vector>
#include <nlohmann/json.hpp>
using nlohmann::json;
@@ -184,3 +187,139 @@ TEST_CASE("other constructors and destructor")
}
}
}
TEST_CASE("copy constructor and destructor across value types")
{
SECTION("copy of a scalar is independent of the source")
{
const json src_null = nullptr;
const json src_bool = true;
const json src_int = -7;
const json src_uint = 7u;
const json src_float = 2.5;
json c_null(src_null);
json c_bool(src_bool);
json c_int(src_int);
json c_uint(src_uint);
json c_float(src_float);
CHECK(c_null.is_null());
CHECK(c_bool == true);
CHECK(c_int == -7);
CHECK(c_uint == 7u);
CHECK(c_float == 2.5);
c_int = 8;
c_float = 3.5;
CHECK(src_int == -7);
CHECK(src_float == 2.5);
}
SECTION("copy of an owning value, then mutate the copy")
{
const json src_string = "hello";
const json src_array = {1, 2, 3};
const json src_object = {{"a", 1}, {"b", {{"c", 2}}}};
const json src_binary = json::binary({1, 2, 3});
json c_string(src_string);
json c_array(src_array);
json c_object(src_object);
json c_binary(src_binary);
CHECK(c_string == src_string);
CHECK(c_array == src_array);
CHECK(c_object == src_object);
CHECK(c_binary == src_binary);
c_string.get_ref<json::string_t&>() += " world";
c_array.push_back(4);
c_object["b"]["c"] = 3;
c_object["d"] = 4;
c_binary.get_binary().push_back(4);
CHECK(src_string == "hello");
CHECK(src_array.size() == 3);
CHECK(src_object["b"]["c"] == 2);
CHECK_FALSE(src_object.contains("d"));
CHECK(src_binary.get_binary().size() == 3);
CHECK(c_string == "hello world");
CHECK(c_array.size() == 4);
CHECK(c_object["b"]["c"] == 3);
CHECK(c_binary.get_binary().size() == 4);
}
SECTION("copy keeps parent pointers valid (JSON_DIAGNOSTICS)")
{
const json src = {{"a", {1, 2}}, {"b", "x"}};
json c(src);
c["a"].push_back(3);
CHECK(c["a"].size() == 3);
CHECK(src["a"].size() == 2);
}
SECTION("vector of mixed types: build, copy, and destroy")
{
{
std::vector<json> v;
for (int i = 0; i < 70; ++i)
{
switch (i % 9)
{
case 0:
v.emplace_back(nullptr);
break;
case 1:
v.emplace_back(i % 2 == 0);
break;
case 2:
v.emplace_back(-i);
break;
case 3:
v.emplace_back(static_cast<json::number_unsigned_t>(i));
break;
case 4:
v.emplace_back(i * 0.5);
break;
case 5:
v.emplace_back(std::string(static_cast<std::size_t>(i), 'x'));
break;
case 6:
v.emplace_back(json::array({i, "s", nullptr}));
break;
case 7:
v.emplace_back(json::object({{"k", i}}));
break;
default:
v.emplace_back(json::binary({1, 2, 3}));
break;
}
}
REQUIRE(v.size() == 70);
std::vector<json> copy(v);
CHECK(copy == v);
// mutating the copy leaves the original alone
copy[5] = "changed";
copy[6].push_back(1);
CHECK(copy != v);
CHECK(v[5] == std::string(5, 'x'));
CHECK(v[6].size() == 3);
v.erase(v.begin(), v.begin() + 10);
CHECK(v.size() == 60);
}
// destroying the discarded and default-constructed values
std::vector<json> w(4);
w.emplace_back(json::value_t::discarded);
w.emplace_back(json::value_t::object);
w.emplace_back(json::value_t::array);
w.emplace_back(json::value_t::string);
w.emplace_back(json::value_t::binary);
CHECK(w.size() == 9);
}
}