diff --git a/include/boost/json/impl/object.hpp b/include/boost/json/impl/object.hpp index 1e5c83fab..85e7494b2 100644 --- a/include/boost/json/impl/object.hpp +++ b/include/boost/json/impl/object.hpp @@ -410,6 +410,17 @@ emplace( result.second), true }; } +template +auto +object:: +stable_insert(const_iterator pos, P&& p) -> + std::pair +{ + key_value_pair v( + std::forward

(p), sp_); + return stable_insert_impl(pos, pilfer(v)); +} + //---------------------------------------------------------- // // (private) diff --git a/include/boost/json/impl/object.ipp b/include/boost/json/impl/object.ipp index 1e395c0e3..7eeb9435d 100644 --- a/include/boost/json/impl/object.ipp +++ b/include/boost/json/impl/object.ipp @@ -70,6 +70,28 @@ find_in_object( object const& obj, string_view key) noexcept; +// caller must ensure no aliasing +template +void trivial_relocate(T* src, T* dst) noexcept +{ + // the casts silence warnings + std::memcpy( + static_cast(dst), + static_cast(src), + sizeof(T)); +} + +// caller must ensure n != 0 +template +void trivial_relocate_n(T* src, std::size_t n, T* dst) noexcept +{ + // the casts silence warnings + std::memmove( + static_cast(dst), + static_cast(src), + sizeof(T) * n); +} + } // namespace detail //---------------------------------------------------------- @@ -510,11 +532,7 @@ erase(const_iterator pos) noexcept -> { return do_erase(pos, [this](iterator p) { - // the casts silence warnings - std::memcpy( - static_cast(p), - static_cast(end()), - sizeof(*p)); + detail::trivial_relocate(end(), p); }, [this](iterator p) { reindex_relocate(end(), p); @@ -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(p), - static_cast(p + 1), - sizeof(*p) * (end() - p)); + detail::trivial_relocate_n(p + 1, end() - p, p); }, [this](iterator p) { for (; p != end(); ++p) @@ -723,6 +737,69 @@ insert_impl( return pv; } +auto +object:: +stable_insert_impl( + const_iterator pos, + pilfered pair) -> + std::pair +{ + // 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 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(p - begin()); + ++t_->size; + return pv; +} + // rehash to at least `n` buckets void object:: @@ -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(dst), - static_cast(src), - sizeof(*dst)); + detail::trivial_relocate(src, dst); access::next(*dst) = head; head = static_cast< index_t>(dst - begin()); diff --git a/include/boost/json/object.hpp b/include/boost/json/object.hpp index 2922079c2..9e8cfa660 100644 --- a/include/boost/json/object.hpp +++ b/include/boost/json/object.hpp @@ -1119,6 +1119,53 @@ class object std::pair 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 + @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::value>::type +#endif + > + std::pair + stable_insert(const_iterator pos, P&& p); + /** Erase an element Remove the element pointed to by `pos`, which must @@ -1592,6 +1639,19 @@ class object pilfered p, std::size_t hash); + BOOST_JSON_DECL + std::pair + stable_insert_impl( + const_iterator pos, + pilfered pair); + + BOOST_JSON_DECL + key_value_pair* + stable_insert_impl( + std::ptrdiff_t index, + pilfered pair, + std::size_t hash); + BOOST_JSON_DECL void rehash(std::size_t new_capacity); diff --git a/test/object.cpp b/test/object.cpp index 8933d0e0f..43736b4f5 100644 --- a/test/object.cpp +++ b/test/object.cpp @@ -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