Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

object::stable_insert #765

Open
wants to merge 1 commit into
base: develop
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions include/boost/json/impl/object.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -410,6 +410,17 @@ emplace(
result.second), true };
}

template<class P, class>
auto
object::
stable_insert(const_iterator pos, P&& p) ->
std::pair<iterator, bool>
{
key_value_pair v(
std::forward<P>(p), sp_);
return stable_insert_impl(pos, pilfer(v));
}

//----------------------------------------------------------
//
// (private)
Expand Down
103 changes: 88 additions & 15 deletions include/boost/json/impl/object.ipp
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,28 @@ find_in_object<string_view>(
object const& obj,
string_view key) noexcept;

// caller must ensure no aliasing
template<class T>
void trivial_relocate(T* src, T* dst) noexcept
{
// the casts silence warnings
std::memcpy(
static_cast<void*>(dst),
static_cast<void const*>(src),
sizeof(T));
}

// caller must ensure n != 0
template<class T>
void trivial_relocate_n(T* src, std::size_t n, T* dst) noexcept
{
// the casts silence warnings
std::memmove(
static_cast<void*>(dst),
static_cast<void const*>(src),
sizeof(T) * n);
}

} // namespace detail

//----------------------------------------------------------
Expand Down Expand Up @@ -510,11 +532,7 @@ erase(const_iterator pos) noexcept ->
{
return do_erase(pos,
[this](iterator p) {
// the casts silence warnings
std::memcpy(
static_cast<void*>(p),
static_cast<void const*>(end()),
sizeof(*p));
detail::trivial_relocate(end(), p);
},
[this](iterator p) {
reindex_relocate(end(), p);
Expand All @@ -540,11 +558,7 @@ stable_erase(const_iterator pos) noexcept ->
{
return do_erase(pos,
[this](iterator p) {
// the casts silence warnings
std::memmove(
static_cast<void*>(p),
static_cast<void const*>(p + 1),
sizeof(*p) * (end() - p));
detail::trivial_relocate_n(p + 1, end() - p, p);
},
[this](iterator p) {
for (; p != end(); ++p)
Expand Down Expand Up @@ -723,6 +737,69 @@ insert_impl(
return pv;
}

auto
object::
stable_insert_impl(
const_iterator pos,
pilfered<key_value_pair> pair) ->
std::pair<iterator, bool>
{
// caller is responsible
// for preventing aliasing.
// index required in case of reallocation
std::ptrdiff_t const index = pos - begin();
if (capacity() == 0)
{
// we know that the value does not exist
// and find_in_object requires non-zero capcaity
reserve(size() + 1);
}
auto const result =
detail::find_in_object(*this, pair.get().key());
if(result.first)
return { result.first, false };
reserve(size() + 1);
return { this->stable_insert_impl(
index, pair, result.second), true };
}

key_value_pair*
object::
stable_insert_impl(
std::ptrdiff_t index,
pilfered<key_value_pair> pair,
std::size_t hash)
{
BOOST_ASSERT(
capacity() > size());
auto p = begin() + index;

if(t_->is_small())
{
if (p != end())
{
detail::trivial_relocate_n(p, end() - p, p + 1);
}
auto const pv = ::new(p)
key_value_pair(pair);
++t_->size;
return pv;
}

for (auto it = end(); it != p; --it)
{
reindex_relocate(it - 1, it);
}
auto& head =
t_->bucket(hash);
auto const pv = ::new(p)
key_value_pair(pair);
access::next(*pv) = head;
head = static_cast<index_t>(p - begin());
++t_->size;
return pv;
}

// rehash to at least `n` buckets
void
object::
Expand Down Expand Up @@ -879,11 +956,7 @@ reindex_relocate(
BOOST_ASSERT(! t_->is_small());
auto& head = t_->bucket(src->key());
remove(head, *src);
// the casts silence warnings
std::memcpy(
static_cast<void*>(dst),
static_cast<void const*>(src),
sizeof(*dst));
detail::trivial_relocate(src, dst);
access::next(*dst) = head;
head = static_cast<
index_t>(dst - begin());
Expand Down
60 changes: 60 additions & 0 deletions include/boost/json/object.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -1119,6 +1119,53 @@ class object
std::pair<iterator, bool>
emplace(string_view key, Arg&& arg);

/** Insert elements.

Inserts `p` if `this->contains(value_type(p).key())` is `false`.
@ref value_type must be constructible from `p`.
The relative order of all elements is preserved.

If the insertion occurs and results in a rehashing
of the container, all iterators and references are invalidated.
Otherwise, they are not affected.
Rehashing occurs only if the new number of elements
is greater than @ref capacity().

@par Constraints
@code
std::is_constructible_v<value_type, P>
@endcode

@par Complexity
Average case linear in @ref size().

@par Exception Safety
Strong guarantee.
Calls to `memory_resource::allocate` may throw.

@param pos Iterator before which the content will be inserted.
`pos` may be the end() iterator.

@param p The value to insert.

@throw std::length_error key is too long.

@throw std::length_error @ref size() >= max_size().

@return A pair where `first` is an iterator
to the existing or inserted element, and `second`
is `true` if the insertion took place or `false` otherwise.
*/
template<class P
#ifndef BOOST_JSON_DOCS
,class = typename std::enable_if<
std::is_constructible<key_value_pair,
P, storage_ptr>::value>::type
#endif
>
std::pair<iterator, bool>
stable_insert(const_iterator pos, P&& p);

/** Erase an element

Remove the element pointed to by `pos`, which must
Expand Down Expand Up @@ -1592,6 +1639,19 @@ class object
pilfered<key_value_pair> p,
std::size_t hash);

BOOST_JSON_DECL
std::pair<iterator, bool>
stable_insert_impl(
const_iterator pos,
pilfered<key_value_pair> pair);

BOOST_JSON_DECL
key_value_pair*
stable_insert_impl(
std::ptrdiff_t index,
pilfered<key_value_pair> pair,
std::size_t hash);

BOOST_JSON_DECL
void
rehash(std::size_t new_capacity);
Expand Down
94 changes: 94 additions & 0 deletions test/object.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1152,6 +1152,100 @@ class object_test
}
}

// stable_insert(pos, P&&)
{
fail_loop([&](storage_ptr const& sp)
{
object o(sp);
auto result = o.stable_insert(o.begin(),
std::make_pair("x", 1));
BOOST_TEST(result.second);
BOOST_TEST(result.first->key() == "x");
BOOST_TEST(result.first->value().as_int64() == 1);
});

fail_loop([&](storage_ptr const& sp)
{
object o(sp);
auto const p = std::make_pair("x", 1);
auto result = o.stable_insert(o.end(), p);
BOOST_TEST(result.second);
BOOST_TEST(result.first->key() == "x");
BOOST_TEST(result.first->value().as_int64() == 1);
});

fail_loop([&](storage_ptr const& sp)
{
object o({
{"a", 1},
{"b", 2},
{"c", 3}}, sp);
auto const result = o.stable_insert(o.begin(),
std::make_pair("b", 4));
BOOST_TEST(
result.first->value().as_int64() == 2);
BOOST_TEST(! result.second);
});

fail_loop([&](storage_ptr const& sp)
{
object o({
{"a", 1},
{"b", 2},
{"c", 3}}, sp);
auto const result = o.stable_insert(o.end(),
std::make_pair("d", 4));
BOOST_TEST(result.second);
BOOST_TEST(result.first == o.begin() + 3);
BOOST_TEST(
result.first->value().as_int64() == 4);
});

// insert child
{
object o = {
{ "k1", 1 },
{ "k2", 2 },
{ "k3", 3 } };

auto& v = o["k2"];
o.stable_insert(o.begin() + 1, std::pair<
string_view, value&>(
"k4", v));
BOOST_TEST(serialize(o) ==
R"({"k1":1,"k4":2,"k2":2,"k3":3})");
}
// insert existing key
{
object o = {
{ "k1", 1 },
{ "k2", 2 },
{ "k3", 3 } };
auto b = o.begin();
auto const result = o.stable_insert(o.begin(), std::make_pair("k3", 4));
BOOST_TEST(!result.second);
BOOST_TEST(result.first == o.begin() + 2);
BOOST_TEST(
result.first->value().as_int64() == 3);
BOOST_TEST(b == o.begin());
}
// large
{
object o(i1_);
auto const result = o.stable_insert(o.begin(),
std::make_pair("-1", -1));
BOOST_TEST(result.second);
BOOST_TEST(result.first == o.begin());
BOOST_TEST(
result.first->value().as_int64() == -1);
BOOST_TEST(serialize(o) ==
R"({"-1":-1,"0":0,"1":1,"2":2,"3":3,"4":4,)"
R"("5":5,"6":6,"7":7,"8":8,"9":9,)"
R"("10":10,"11":11,"12":12,"13":13,"14":14,)"
R"("15":15,"16":16,"17":17,"18":18,"19":19})");
}
}

// erase(pos)
{
// small
Expand Down