mirror of
https://github.com/nlohmann/json.git
synced 2026-10-03 13:10:33 +00:00
Copy-construct the base class of a deep copy's elements, not assign it
The bounded-descent copy added by #5389 built the elements of a deep copy (nested past the 128-level bound) by default-constructing them and then having copy_metadata() assign their base class afterwards. That assignment is only instantiated for values nested past the bound, but being called from copy_structured() at all meant it was compiled for every copy, so a CustomBaseClass that is copy-constructible but not move-assignable (for example one with a const data member) no longer let its basic_json be copy-constructed, at any depth. copy_array_level() and copy_object_level() now build each element with a private-tag-selected constructor that copy-constructs the base class (and, under JSON_DIAGNOSTIC_POSITIONS, copies the positions) directly, the same way the copy constructor already builds elements within the 128-level bound. Copying a basic_json is therefore back to requiring only a copy-constructible base class, as documented and as it was before #5389; copy assignment is unchanged and still requires an assignable one. Fixes #5674. Signed-off-by: Niels Lohmann <mail@nlohmann.me>
This commit is contained in:
@@ -333,3 +333,73 @@ TEST_CASE("JSON Visit Node")
|
||||
);
|
||||
CHECK(expected.empty());
|
||||
}
|
||||
|
||||
// A custom base class with a const member: copy-constructible (initializing a
|
||||
// const member works fine), but not copy-/move-assignable (assigning one does
|
||||
// not). Used to check that copy construction never requires more than that.
|
||||
struct const_member_base
|
||||
{
|
||||
const int id = 7; // NOLINT(misc-non-private-member-variables-in-classes)
|
||||
};
|
||||
|
||||
using json_with_const_base = nlohmann::basic_json <
|
||||
std::map,
|
||||
std::vector,
|
||||
std::string,
|
||||
bool,
|
||||
std::int64_t,
|
||||
std::uint64_t,
|
||||
double,
|
||||
std::allocator,
|
||||
nlohmann::adl_serializer,
|
||||
std::vector<std::uint8_t>,
|
||||
const_member_base
|
||||
>;
|
||||
|
||||
// build an array nested @a depth levels deep, with the innermost value 1;
|
||||
// every level is constructed (never assigned), since const_member_base does
|
||||
// not support assignment
|
||||
static json_with_const_base make_nested_array(std::size_t depth)
|
||||
{
|
||||
if (depth == 0)
|
||||
{
|
||||
return json_with_const_base(1);
|
||||
}
|
||||
return json_with_const_base::array({make_nested_array(depth - 1)});
|
||||
}
|
||||
|
||||
TEST_CASE("Regression test for issue #5674 - copy construction must not require an assignable base class")
|
||||
{
|
||||
SECTION("depth 0")
|
||||
{
|
||||
// as in the original bug report: copy construction only, no assignment
|
||||
const json_with_const_base j = {1, 2};
|
||||
const json_with_const_base copy = j; // NOLINT(performance-unnecessary-copy-initialization)
|
||||
|
||||
CHECK(copy.size() == 2);
|
||||
CHECK(copy.id == 7);
|
||||
}
|
||||
|
||||
SECTION("nested deeper than the copy constructor's descent bound")
|
||||
{
|
||||
// beyond nesting_depth_limit() (128) levels, the copy constructor
|
||||
// copies without the call stack (copy_iteratively / copy_array_level),
|
||||
// which used to assign the base class of every element it created
|
||||
const std::size_t depth = 300;
|
||||
|
||||
const json_with_const_base j = make_nested_array(depth);
|
||||
const json_with_const_base copy = j; // NOLINT(performance-unnecessary-copy-initialization)
|
||||
|
||||
const json_with_const_base* c = ©
|
||||
for (std::size_t level = 0; level <= depth; ++level)
|
||||
{
|
||||
CAPTURE(level)
|
||||
REQUIRE(c->id == 7);
|
||||
if (level < depth)
|
||||
{
|
||||
c = &c->at(0);
|
||||
}
|
||||
}
|
||||
CHECK(*c == 1);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user