861 lines
18 KiB
Plaintext
861 lines
18 KiB
Plaintext
|
//
|
||
|
// Copyright (c) 2019 Vinnie Falco (vinnie.falco@gmail.com)
|
||
|
//
|
||
|
// Distributed under the Boost Software License, Version 1.0. (See accompanying
|
||
|
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
|
||
|
//
|
||
|
// Official repository: https://github.com/boostorg/json
|
||
|
//
|
||
|
|
||
|
#ifndef BOOST_JSON_IMPL_OBJECT_IPP
|
||
|
#define BOOST_JSON_IMPL_OBJECT_IPP
|
||
|
|
||
|
#include <boost/json/object.hpp>
|
||
|
#include <boost/json/detail/digest.hpp>
|
||
|
#include <boost/json/detail/except.hpp>
|
||
|
#include <boost/json/detail/hash_combine.hpp>
|
||
|
#include <algorithm>
|
||
|
#include <cmath>
|
||
|
#include <cstdlib>
|
||
|
#include <cstring>
|
||
|
#include <new>
|
||
|
#include <stdexcept>
|
||
|
#include <type_traits>
|
||
|
|
||
|
BOOST_JSON_NS_BEGIN
|
||
|
namespace detail {
|
||
|
|
||
|
template<class CharRange>
|
||
|
std::pair<key_value_pair*, std::size_t>
|
||
|
find_in_object(
|
||
|
object const& obj,
|
||
|
CharRange key) noexcept
|
||
|
{
|
||
|
BOOST_ASSERT(obj.t_->capacity > 0);
|
||
|
if(obj.t_->is_small())
|
||
|
{
|
||
|
auto it = &(*obj.t_)[0];
|
||
|
auto const last =
|
||
|
&(*obj.t_)[obj.t_->size];
|
||
|
for(;it != last; ++it)
|
||
|
if( key == it->key() )
|
||
|
return { it, 0 };
|
||
|
return { nullptr, 0 };
|
||
|
}
|
||
|
std::pair<
|
||
|
key_value_pair*,
|
||
|
std::size_t> result;
|
||
|
BOOST_ASSERT(obj.t_->salt != 0);
|
||
|
result.second = detail::digest(key.begin(), key.end(), obj.t_->salt);
|
||
|
auto i = obj.t_->bucket(
|
||
|
result.second);
|
||
|
while(i != object::null_index_)
|
||
|
{
|
||
|
auto& v = (*obj.t_)[i];
|
||
|
if( key == v.key() )
|
||
|
{
|
||
|
result.first = &v;
|
||
|
return result;
|
||
|
}
|
||
|
i = access::next(v);
|
||
|
}
|
||
|
result.first = nullptr;
|
||
|
return result;
|
||
|
}
|
||
|
|
||
|
|
||
|
template
|
||
|
std::pair<key_value_pair*, std::size_t>
|
||
|
find_in_object<string_view>(
|
||
|
object const& obj,
|
||
|
string_view key) noexcept;
|
||
|
|
||
|
} // namespace detail
|
||
|
|
||
|
//----------------------------------------------------------
|
||
|
|
||
|
constexpr object::table::table() = default;
|
||
|
|
||
|
// empty objects point here
|
||
|
BOOST_JSON_REQUIRE_CONST_INIT
|
||
|
object::table object::empty_;
|
||
|
|
||
|
std::size_t
|
||
|
object::table::
|
||
|
digest(string_view key) const noexcept
|
||
|
{
|
||
|
BOOST_ASSERT(salt != 0);
|
||
|
return detail::digest(
|
||
|
key.begin(), key.end(), salt);
|
||
|
}
|
||
|
|
||
|
auto
|
||
|
object::table::
|
||
|
bucket(std::size_t hash) noexcept ->
|
||
|
index_t&
|
||
|
{
|
||
|
return reinterpret_cast<
|
||
|
index_t*>(&(*this)[capacity])[
|
||
|
hash % capacity];
|
||
|
}
|
||
|
|
||
|
auto
|
||
|
object::table::
|
||
|
bucket(string_view key) noexcept ->
|
||
|
index_t&
|
||
|
{
|
||
|
return bucket(digest(key));
|
||
|
}
|
||
|
|
||
|
void
|
||
|
object::table::
|
||
|
clear() noexcept
|
||
|
{
|
||
|
BOOST_ASSERT(! is_small());
|
||
|
// initialize buckets
|
||
|
std::memset(
|
||
|
reinterpret_cast<index_t*>(
|
||
|
&(*this)[capacity]),
|
||
|
0xff, // null_index_
|
||
|
capacity * sizeof(index_t));
|
||
|
}
|
||
|
|
||
|
object::table*
|
||
|
object::table::
|
||
|
allocate(
|
||
|
std::size_t capacity,
|
||
|
std::uintptr_t salt,
|
||
|
storage_ptr const& sp)
|
||
|
{
|
||
|
BOOST_STATIC_ASSERT(
|
||
|
alignof(key_value_pair) >=
|
||
|
alignof(index_t));
|
||
|
BOOST_ASSERT(capacity > 0);
|
||
|
BOOST_ASSERT(capacity <= max_size());
|
||
|
table* p;
|
||
|
if(capacity <= detail::small_object_size_)
|
||
|
{
|
||
|
p = reinterpret_cast<
|
||
|
table*>(sp->allocate(
|
||
|
sizeof(table) + capacity *
|
||
|
sizeof(key_value_pair)));
|
||
|
p->capacity = static_cast<
|
||
|
std::uint32_t>(capacity);
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
p = reinterpret_cast<
|
||
|
table*>(sp->allocate(
|
||
|
sizeof(table) + capacity * (
|
||
|
sizeof(key_value_pair) +
|
||
|
sizeof(index_t))));
|
||
|
p->capacity = static_cast<
|
||
|
std::uint32_t>(capacity);
|
||
|
p->clear();
|
||
|
}
|
||
|
if(salt)
|
||
|
{
|
||
|
p->salt = salt;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
// VFALCO This would be better if it
|
||
|
// was random, but maybe this
|
||
|
// is good enough.
|
||
|
p->salt = reinterpret_cast<
|
||
|
std::uintptr_t>(p);
|
||
|
}
|
||
|
return p;
|
||
|
}
|
||
|
|
||
|
//----------------------------------------------------------
|
||
|
|
||
|
void
|
||
|
object::
|
||
|
revert_construct::
|
||
|
destroy() noexcept
|
||
|
{
|
||
|
obj_->destroy();
|
||
|
}
|
||
|
|
||
|
//----------------------------------------------------------
|
||
|
|
||
|
void
|
||
|
object::
|
||
|
revert_insert::
|
||
|
destroy() noexcept
|
||
|
{
|
||
|
obj_->destroy(
|
||
|
&(*obj_->t_)[size_],
|
||
|
obj_->end());
|
||
|
}
|
||
|
|
||
|
//----------------------------------------------------------
|
||
|
//
|
||
|
// Construction
|
||
|
//
|
||
|
//----------------------------------------------------------
|
||
|
|
||
|
object::
|
||
|
object(detail::unchecked_object&& uo)
|
||
|
: sp_(uo.storage())
|
||
|
{
|
||
|
if(uo.size() == 0)
|
||
|
{
|
||
|
t_ = &empty_;
|
||
|
return;
|
||
|
}
|
||
|
// should already be checked
|
||
|
BOOST_ASSERT(
|
||
|
uo.size() <= max_size());
|
||
|
t_ = table::allocate(
|
||
|
uo.size(), 0, sp_);
|
||
|
|
||
|
// insert all elements, keeping
|
||
|
// the last of any duplicate keys.
|
||
|
auto dest = begin();
|
||
|
auto src = uo.release();
|
||
|
auto const end = src + 2 * uo.size();
|
||
|
if(t_->is_small())
|
||
|
{
|
||
|
t_->size = 0;
|
||
|
while(src != end)
|
||
|
{
|
||
|
access::construct_key_value_pair(
|
||
|
dest, pilfer(src[0]), pilfer(src[1]));
|
||
|
src += 2;
|
||
|
auto result = detail::find_in_object(*this, dest->key());
|
||
|
if(! result.first)
|
||
|
{
|
||
|
++dest;
|
||
|
++t_->size;
|
||
|
continue;
|
||
|
}
|
||
|
// handle duplicate
|
||
|
auto& v = *result.first;
|
||
|
// don't bother to check if
|
||
|
// storage deallocate is trivial
|
||
|
v.~key_value_pair();
|
||
|
// trivial relocate
|
||
|
std::memcpy(
|
||
|
static_cast<void*>(&v),
|
||
|
dest, sizeof(v));
|
||
|
}
|
||
|
return;
|
||
|
}
|
||
|
while(src != end)
|
||
|
{
|
||
|
access::construct_key_value_pair(
|
||
|
dest, pilfer(src[0]), pilfer(src[1]));
|
||
|
src += 2;
|
||
|
auto& head = t_->bucket(dest->key());
|
||
|
auto i = head;
|
||
|
for(;;)
|
||
|
{
|
||
|
if(i == null_index_)
|
||
|
{
|
||
|
// end of bucket
|
||
|
access::next(
|
||
|
*dest) = head;
|
||
|
head = static_cast<index_t>(
|
||
|
dest - begin());
|
||
|
++dest;
|
||
|
break;
|
||
|
}
|
||
|
auto& v = (*t_)[i];
|
||
|
if(v.key() != dest->key())
|
||
|
{
|
||
|
i = access::next(v);
|
||
|
continue;
|
||
|
}
|
||
|
|
||
|
// handle duplicate
|
||
|
access::next(*dest) =
|
||
|
access::next(v);
|
||
|
// don't bother to check if
|
||
|
// storage deallocate is trivial
|
||
|
v.~key_value_pair();
|
||
|
// trivial relocate
|
||
|
std::memcpy(
|
||
|
static_cast<void*>(&v),
|
||
|
dest, sizeof(v));
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
t_->size = static_cast<
|
||
|
index_t>(dest - begin());
|
||
|
}
|
||
|
|
||
|
object::
|
||
|
~object() noexcept
|
||
|
{
|
||
|
if(sp_.is_not_shared_and_deallocate_is_trivial())
|
||
|
return;
|
||
|
if(t_->capacity == 0)
|
||
|
return;
|
||
|
destroy();
|
||
|
}
|
||
|
|
||
|
object::
|
||
|
object(
|
||
|
std::size_t min_capacity,
|
||
|
storage_ptr sp)
|
||
|
: sp_(std::move(sp))
|
||
|
, t_(&empty_)
|
||
|
{
|
||
|
reserve(min_capacity);
|
||
|
}
|
||
|
|
||
|
object::
|
||
|
object(object&& other) noexcept
|
||
|
: sp_(other.sp_)
|
||
|
, t_(detail::exchange(
|
||
|
other.t_, &empty_))
|
||
|
{
|
||
|
}
|
||
|
|
||
|
object::
|
||
|
object(
|
||
|
object&& other,
|
||
|
storage_ptr sp)
|
||
|
: sp_(std::move(sp))
|
||
|
{
|
||
|
if(*sp_ == *other.sp_)
|
||
|
{
|
||
|
t_ = detail::exchange(
|
||
|
other.t_, &empty_);
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
t_ = &empty_;
|
||
|
object(other, sp_).swap(*this);
|
||
|
}
|
||
|
|
||
|
object::
|
||
|
object(
|
||
|
object const& other,
|
||
|
storage_ptr sp)
|
||
|
: sp_(std::move(sp))
|
||
|
, t_(&empty_)
|
||
|
{
|
||
|
reserve(other.size());
|
||
|
revert_construct r(*this);
|
||
|
if(t_->is_small())
|
||
|
{
|
||
|
for(auto const& v : other)
|
||
|
{
|
||
|
::new(end())
|
||
|
key_value_pair(v, sp_);
|
||
|
++t_->size;
|
||
|
}
|
||
|
r.commit();
|
||
|
return;
|
||
|
}
|
||
|
for(auto const& v : other)
|
||
|
{
|
||
|
// skip duplicate checking
|
||
|
auto& head =
|
||
|
t_->bucket(v.key());
|
||
|
auto pv = ::new(end())
|
||
|
key_value_pair(v, sp_);
|
||
|
access::next(*pv) = head;
|
||
|
head = t_->size;
|
||
|
++t_->size;
|
||
|
}
|
||
|
r.commit();
|
||
|
}
|
||
|
|
||
|
object::
|
||
|
object(
|
||
|
std::initializer_list<std::pair<
|
||
|
string_view, value_ref>> init,
|
||
|
std::size_t min_capacity,
|
||
|
storage_ptr sp)
|
||
|
: sp_(std::move(sp))
|
||
|
, t_(&empty_)
|
||
|
{
|
||
|
if( min_capacity < init.size())
|
||
|
min_capacity = init.size();
|
||
|
reserve(min_capacity);
|
||
|
revert_construct r(*this);
|
||
|
insert(init);
|
||
|
r.commit();
|
||
|
}
|
||
|
|
||
|
//----------------------------------------------------------
|
||
|
//
|
||
|
// Assignment
|
||
|
//
|
||
|
//----------------------------------------------------------
|
||
|
|
||
|
object&
|
||
|
object::
|
||
|
operator=(object const& other)
|
||
|
{
|
||
|
object tmp(other, sp_);
|
||
|
this->~object();
|
||
|
::new(this) object(pilfer(tmp));
|
||
|
return *this;
|
||
|
}
|
||
|
|
||
|
object&
|
||
|
object::
|
||
|
operator=(object&& other)
|
||
|
{
|
||
|
object tmp(std::move(other), sp_);
|
||
|
this->~object();
|
||
|
::new(this) object(pilfer(tmp));
|
||
|
return *this;
|
||
|
}
|
||
|
|
||
|
object&
|
||
|
object::
|
||
|
operator=(
|
||
|
std::initializer_list<std::pair<
|
||
|
string_view, value_ref>> init)
|
||
|
{
|
||
|
object tmp(init, sp_);
|
||
|
this->~object();
|
||
|
::new(this) object(pilfer(tmp));
|
||
|
return *this;
|
||
|
}
|
||
|
|
||
|
//----------------------------------------------------------
|
||
|
//
|
||
|
// Modifiers
|
||
|
//
|
||
|
//----------------------------------------------------------
|
||
|
|
||
|
void
|
||
|
object::
|
||
|
clear() noexcept
|
||
|
{
|
||
|
if(empty())
|
||
|
return;
|
||
|
if(! sp_.is_not_shared_and_deallocate_is_trivial())
|
||
|
destroy(begin(), end());
|
||
|
if(! t_->is_small())
|
||
|
t_->clear();
|
||
|
t_->size = 0;
|
||
|
}
|
||
|
|
||
|
void
|
||
|
object::
|
||
|
insert(
|
||
|
std::initializer_list<std::pair<
|
||
|
string_view, value_ref>> init)
|
||
|
{
|
||
|
auto const n0 = size();
|
||
|
if(init.size() > max_size() - n0)
|
||
|
detail::throw_length_error(
|
||
|
"object too large",
|
||
|
BOOST_JSON_SOURCE_POS);
|
||
|
reserve(n0 + init.size());
|
||
|
revert_insert r(*this);
|
||
|
if(t_->is_small())
|
||
|
{
|
||
|
for(auto& iv : init)
|
||
|
{
|
||
|
auto result =
|
||
|
detail::find_in_object(*this, iv.first);
|
||
|
if(result.first)
|
||
|
{
|
||
|
// ignore duplicate
|
||
|
continue;
|
||
|
}
|
||
|
::new(end()) key_value_pair(
|
||
|
iv.first,
|
||
|
iv.second.make_value(sp_));
|
||
|
++t_->size;
|
||
|
}
|
||
|
r.commit();
|
||
|
return;
|
||
|
}
|
||
|
for(auto& iv : init)
|
||
|
{
|
||
|
auto& head = t_->bucket(iv.first);
|
||
|
auto i = head;
|
||
|
for(;;)
|
||
|
{
|
||
|
if(i == null_index_)
|
||
|
{
|
||
|
// VFALCO value_ref should construct
|
||
|
// a key_value_pair using placement
|
||
|
auto& v = *::new(end())
|
||
|
key_value_pair(
|
||
|
iv.first,
|
||
|
iv.second.make_value(sp_));
|
||
|
access::next(v) = head;
|
||
|
head = static_cast<index_t>(
|
||
|
t_->size);
|
||
|
++t_->size;
|
||
|
break;
|
||
|
}
|
||
|
auto& v = (*t_)[i];
|
||
|
if(v.key() == iv.first)
|
||
|
{
|
||
|
// ignore duplicate
|
||
|
break;
|
||
|
}
|
||
|
i = access::next(v);
|
||
|
}
|
||
|
}
|
||
|
r.commit();
|
||
|
}
|
||
|
|
||
|
auto
|
||
|
object::
|
||
|
erase(const_iterator pos) noexcept ->
|
||
|
iterator
|
||
|
{
|
||
|
auto p = begin() + (pos - begin());
|
||
|
if(t_->is_small())
|
||
|
{
|
||
|
p->~value_type();
|
||
|
--t_->size;
|
||
|
auto const pb = end();
|
||
|
if(p != end())
|
||
|
{
|
||
|
// the casts silence warnings
|
||
|
std::memcpy(
|
||
|
static_cast<void*>(p),
|
||
|
static_cast<void const*>(pb),
|
||
|
sizeof(*p));
|
||
|
}
|
||
|
return p;
|
||
|
}
|
||
|
remove(t_->bucket(p->key()), *p);
|
||
|
p->~value_type();
|
||
|
--t_->size;
|
||
|
auto const pb = end();
|
||
|
if(p != end())
|
||
|
{
|
||
|
auto& head = t_->bucket(pb->key());
|
||
|
remove(head, *pb);
|
||
|
// the casts silence warnings
|
||
|
std::memcpy(
|
||
|
static_cast<void*>(p),
|
||
|
static_cast<void const*>(pb),
|
||
|
sizeof(*p));
|
||
|
access::next(*p) = head;
|
||
|
head = static_cast<
|
||
|
index_t>(p - begin());
|
||
|
}
|
||
|
return p;
|
||
|
}
|
||
|
|
||
|
auto
|
||
|
object::
|
||
|
erase(string_view key) noexcept ->
|
||
|
std::size_t
|
||
|
{
|
||
|
auto it = find(key);
|
||
|
if(it == end())
|
||
|
return 0;
|
||
|
erase(it);
|
||
|
return 1;
|
||
|
}
|
||
|
|
||
|
void
|
||
|
object::
|
||
|
swap(object& other)
|
||
|
{
|
||
|
if(*sp_ == *other.sp_)
|
||
|
{
|
||
|
t_ = detail::exchange(
|
||
|
other.t_, t_);
|
||
|
return;
|
||
|
}
|
||
|
object temp1(
|
||
|
std::move(*this),
|
||
|
other.storage());
|
||
|
object temp2(
|
||
|
std::move(other),
|
||
|
this->storage());
|
||
|
other.~object();
|
||
|
::new(&other) object(pilfer(temp1));
|
||
|
this->~object();
|
||
|
::new(this) object(pilfer(temp2));
|
||
|
}
|
||
|
|
||
|
//----------------------------------------------------------
|
||
|
//
|
||
|
// Lookup
|
||
|
//
|
||
|
//----------------------------------------------------------
|
||
|
|
||
|
auto
|
||
|
object::
|
||
|
operator[](string_view key) ->
|
||
|
value&
|
||
|
{
|
||
|
auto const result =
|
||
|
emplace(key, nullptr);
|
||
|
return result.first->value();
|
||
|
}
|
||
|
|
||
|
auto
|
||
|
object::
|
||
|
count(string_view key) const noexcept ->
|
||
|
std::size_t
|
||
|
{
|
||
|
if(find(key) == end())
|
||
|
return 0;
|
||
|
return 1;
|
||
|
}
|
||
|
|
||
|
auto
|
||
|
object::
|
||
|
find(string_view key) noexcept ->
|
||
|
iterator
|
||
|
{
|
||
|
if(empty())
|
||
|
return end();
|
||
|
auto const p =
|
||
|
detail::find_in_object(*this, key).first;
|
||
|
if(p)
|
||
|
return p;
|
||
|
return end();
|
||
|
}
|
||
|
|
||
|
auto
|
||
|
object::
|
||
|
find(string_view key) const noexcept ->
|
||
|
const_iterator
|
||
|
{
|
||
|
if(empty())
|
||
|
return end();
|
||
|
auto const p =
|
||
|
detail::find_in_object(*this, key).first;
|
||
|
if(p)
|
||
|
return p;
|
||
|
return end();
|
||
|
}
|
||
|
|
||
|
bool
|
||
|
object::
|
||
|
contains(
|
||
|
string_view key) const noexcept
|
||
|
{
|
||
|
if(empty())
|
||
|
return false;
|
||
|
return detail::find_in_object(*this, key).first
|
||
|
!= nullptr;
|
||
|
}
|
||
|
|
||
|
value const*
|
||
|
object::
|
||
|
if_contains(
|
||
|
string_view key) const noexcept
|
||
|
{
|
||
|
auto const it = find(key);
|
||
|
if(it != end())
|
||
|
return &it->value();
|
||
|
return nullptr;
|
||
|
}
|
||
|
|
||
|
value*
|
||
|
object::
|
||
|
if_contains(
|
||
|
string_view key) noexcept
|
||
|
{
|
||
|
auto const it = find(key);
|
||
|
if(it != end())
|
||
|
return &it->value();
|
||
|
return nullptr;
|
||
|
}
|
||
|
|
||
|
//----------------------------------------------------------
|
||
|
//
|
||
|
// (private)
|
||
|
//
|
||
|
//----------------------------------------------------------
|
||
|
|
||
|
auto
|
||
|
object::
|
||
|
insert_impl(
|
||
|
pilfered<key_value_pair> p) ->
|
||
|
std::pair<iterator, bool>
|
||
|
{
|
||
|
// caller is responsible
|
||
|
// for preventing aliasing.
|
||
|
reserve(size() + 1);
|
||
|
auto const result =
|
||
|
detail::find_in_object(*this, p.get().key());
|
||
|
if(result.first)
|
||
|
return { result.first, false };
|
||
|
return { insert_impl(
|
||
|
p, result.second), true };
|
||
|
}
|
||
|
|
||
|
key_value_pair*
|
||
|
object::
|
||
|
insert_impl(
|
||
|
pilfered<key_value_pair> p,
|
||
|
std::size_t hash)
|
||
|
{
|
||
|
BOOST_ASSERT(
|
||
|
capacity() > size());
|
||
|
if(t_->is_small())
|
||
|
{
|
||
|
auto const pv = ::new(end())
|
||
|
key_value_pair(p);
|
||
|
++t_->size;
|
||
|
return pv;
|
||
|
}
|
||
|
auto& head =
|
||
|
t_->bucket(hash);
|
||
|
auto const pv = ::new(end())
|
||
|
key_value_pair(p);
|
||
|
access::next(*pv) = head;
|
||
|
head = t_->size;
|
||
|
++t_->size;
|
||
|
return pv;
|
||
|
}
|
||
|
|
||
|
// rehash to at least `n` buckets
|
||
|
void
|
||
|
object::
|
||
|
rehash(std::size_t new_capacity)
|
||
|
{
|
||
|
BOOST_ASSERT(
|
||
|
new_capacity > t_->capacity);
|
||
|
auto t = table::allocate(
|
||
|
growth(new_capacity),
|
||
|
t_->salt, sp_);
|
||
|
if(! empty())
|
||
|
std::memcpy(
|
||
|
static_cast<
|
||
|
void*>(&(*t)[0]),
|
||
|
begin(),
|
||
|
size() * sizeof(
|
||
|
key_value_pair));
|
||
|
t->size = t_->size;
|
||
|
table::deallocate(t_, sp_);
|
||
|
t_ = t;
|
||
|
|
||
|
if(! t_->is_small())
|
||
|
{
|
||
|
// rebuild hash table,
|
||
|
// without dup checks
|
||
|
auto p = end();
|
||
|
index_t i = t_->size;
|
||
|
while(i-- > 0)
|
||
|
{
|
||
|
--p;
|
||
|
auto& head =
|
||
|
t_->bucket(p->key());
|
||
|
access::next(*p) = head;
|
||
|
head = i;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
bool
|
||
|
object::
|
||
|
equal(object const& other) const noexcept
|
||
|
{
|
||
|
if(size() != other.size())
|
||
|
return false;
|
||
|
auto const end_ = other.end();
|
||
|
for(auto e : *this)
|
||
|
{
|
||
|
auto it = other.find(e.key());
|
||
|
if(it == end_)
|
||
|
return false;
|
||
|
if(it->value() != e.value())
|
||
|
return false;
|
||
|
}
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
std::size_t
|
||
|
object::
|
||
|
growth(
|
||
|
std::size_t new_size) const
|
||
|
{
|
||
|
if(new_size > max_size())
|
||
|
detail::throw_length_error(
|
||
|
"object too large",
|
||
|
BOOST_JSON_SOURCE_POS);
|
||
|
std::size_t const old = capacity();
|
||
|
if(old > max_size() - old / 2)
|
||
|
return new_size;
|
||
|
std::size_t const g =
|
||
|
old + old / 2; // 1.5x
|
||
|
if(g < new_size)
|
||
|
return new_size;
|
||
|
return g;
|
||
|
}
|
||
|
|
||
|
void
|
||
|
object::
|
||
|
remove(
|
||
|
index_t& head,
|
||
|
key_value_pair& v) noexcept
|
||
|
{
|
||
|
BOOST_ASSERT(! t_->is_small());
|
||
|
auto const i = static_cast<
|
||
|
index_t>(&v - begin());
|
||
|
if(head == i)
|
||
|
{
|
||
|
head = access::next(v);
|
||
|
return;
|
||
|
}
|
||
|
auto* pn =
|
||
|
&access::next((*t_)[head]);
|
||
|
while(*pn != i)
|
||
|
pn = &access::next((*t_)[*pn]);
|
||
|
*pn = access::next(v);
|
||
|
}
|
||
|
|
||
|
void
|
||
|
object::
|
||
|
destroy() noexcept
|
||
|
{
|
||
|
BOOST_ASSERT(t_->capacity > 0);
|
||
|
BOOST_ASSERT(! sp_.is_not_shared_and_deallocate_is_trivial());
|
||
|
destroy(begin(), end());
|
||
|
table::deallocate(t_, sp_);
|
||
|
}
|
||
|
|
||
|
void
|
||
|
object::
|
||
|
destroy(
|
||
|
key_value_pair* first,
|
||
|
key_value_pair* last) noexcept
|
||
|
{
|
||
|
BOOST_ASSERT(! sp_.is_not_shared_and_deallocate_is_trivial());
|
||
|
while(last != first)
|
||
|
(--last)->~key_value_pair();
|
||
|
}
|
||
|
|
||
|
BOOST_JSON_NS_END
|
||
|
|
||
|
//----------------------------------------------------------
|
||
|
//
|
||
|
// std::hash specialization
|
||
|
//
|
||
|
//----------------------------------------------------------
|
||
|
|
||
|
std::size_t
|
||
|
std::hash<::boost::json::object>::operator()(
|
||
|
::boost::json::object const& jo) const noexcept
|
||
|
{
|
||
|
std::size_t seed = jo.size();
|
||
|
for (const auto& kv_pair : jo) {
|
||
|
auto const hk = ::boost::json::detail::digest(
|
||
|
kv_pair.key().begin(), kv_pair.key().end(), 0);
|
||
|
auto const hkv = ::boost::json::detail::hash_combine(
|
||
|
hk,
|
||
|
std::hash<::boost::json::value>{}(kv_pair.value()));
|
||
|
seed = ::boost::json::detail::hash_combine_commutative(seed, hkv);
|
||
|
}
|
||
|
return seed;
|
||
|
}
|
||
|
|
||
|
//----------------------------------------------------------
|
||
|
|
||
|
|
||
|
#endif
|