123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661 |
- /* Fast open-addressing hash table.
- *
- * Copyright 2022-2024 Joaquin M Lopez Munoz.
- * Copyright 2023 Christian Mazakas.
- * Copyright 2024 Braden Ganetsky.
- * 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)
- *
- * See https://www.boost.org/libs/unordered for library home page.
- */
- #ifndef BOOST_UNORDERED_DETAIL_FOA_TABLE_HPP
- #define BOOST_UNORDERED_DETAIL_FOA_TABLE_HPP
- #include <boost/assert.hpp>
- #include <boost/config.hpp>
- #include <boost/config/workaround.hpp>
- #include <boost/core/serialization.hpp>
- #include <boost/unordered/detail/foa/core.hpp>
- #include <boost/unordered/detail/serialize_tracked_address.hpp>
- #include <boost/unordered/detail/type_traits.hpp>
- #include <cstddef>
- #include <iterator>
- #include <memory>
- #include <type_traits>
- #include <utility>
- namespace boost{
- namespace unordered{
- namespace detail{
- namespace foa{
- /* use plain integrals for group metadata storage */
- template<typename Integral>
- struct plain_integral
- {
- operator Integral()const{return n;}
- void operator=(Integral m){n=m;}
- #if BOOST_WORKAROUND(BOOST_GCC,>=50000 && BOOST_GCC<60000)
- void operator|=(Integral m){n=static_cast<Integral>(n|m);}
- void operator&=(Integral m){n=static_cast<Integral>(n&m);}
- #else
- void operator|=(Integral m){n|=m;}
- void operator&=(Integral m){n&=m;}
- #endif
- Integral n;
- };
- struct plain_size_control
- {
- std::size_t ml;
- std::size_t size;
- };
- template<typename,typename,typename,typename>
- class table;
- /* table_iterator keeps two pointers:
- *
- * - A pointer p to the element slot.
- * - A pointer pc to the n-th byte of the associated group metadata, where n
- * is the position of the element in the group.
- *
- * A simpler solution would have been to keep a pointer p to the element, a
- * pointer pg to the group, and the position n, but that would increase
- * sizeof(table_iterator) by 4/8 bytes. In order to make this compact
- * representation feasible, it is required that group objects are aligned
- * to their size, so that we can recover pg and n as
- *
- * - n = pc%sizeof(group)
- * - pg = pc-n
- *
- * (for explanatory purposes pg and pc are treated above as if they were memory
- * addresses rather than pointers).
- *
- * p = nullptr is conventionally used to mark end() iterators.
- */
- /* internal conversion from const_iterator to iterator */
- struct const_iterator_cast_tag{};
- template<typename TypePolicy,typename GroupPtr,bool Const>
- class table_iterator
- {
- using group_pointer_traits=boost::pointer_traits<GroupPtr>;
- using type_policy=TypePolicy;
- using table_element_type=typename type_policy::element_type;
- using group_type=typename group_pointer_traits::element_type;
- using table_element_pointer=
- typename group_pointer_traits::template rebind<table_element_type>;
- using char_pointer=
- typename group_pointer_traits::template rebind<unsigned char>;
- static constexpr auto N=group_type::N;
- static constexpr auto regular_layout=group_type::regular_layout;
- public:
- using difference_type=std::ptrdiff_t;
- using value_type=typename type_policy::value_type;
- using pointer=
- typename std::conditional<Const,value_type const*,value_type*>::type;
- using reference=
- typename std::conditional<Const,value_type const&,value_type&>::type;
- using iterator_category=std::forward_iterator_tag;
- using element_type=
- typename std::conditional<Const,value_type const,value_type>::type;
- table_iterator():pc_{nullptr},p_{nullptr}{};
- template<bool Const2,typename std::enable_if<!Const2>::type* =nullptr>
- table_iterator(const table_iterator<TypePolicy,GroupPtr,Const2>& x):
- pc_{x.pc_},p_{x.p_}{}
- table_iterator(
- const_iterator_cast_tag, const table_iterator<TypePolicy,GroupPtr,true>& x):
- pc_{x.pc_},p_{x.p_}{}
- inline reference operator*()const noexcept
- {return type_policy::value_from(*p());}
- inline pointer operator->()const noexcept
- {return std::addressof(type_policy::value_from(*p()));}
- inline table_iterator& operator++()noexcept{increment();return *this;}
- inline table_iterator operator++(int)noexcept
- {auto x=*this;increment();return x;}
- friend inline bool operator==(
- const table_iterator& x,const table_iterator& y)
- {return x.p()==y.p();}
- friend inline bool operator!=(
- const table_iterator& x,const table_iterator& y)
- {return !(x==y);}
- private:
- template<typename,typename,bool> friend class table_iterator;
- template<typename> friend class table_erase_return_type;
- template<typename,typename,typename,typename> friend class table;
- table_iterator(group_type* pg,std::size_t n,const table_element_type* ptet):
- pc_{to_pointer<char_pointer>(
- reinterpret_cast<unsigned char*>(const_cast<group_type*>(pg))+n)},
- p_{to_pointer<table_element_pointer>(const_cast<table_element_type*>(ptet))}
- {}
- unsigned char* pc()const noexcept{return boost::to_address(pc_);}
- table_element_type* p()const noexcept{return boost::to_address(p_);}
- inline void increment()noexcept
- {
- BOOST_ASSERT(p()!=nullptr);
- increment(std::integral_constant<bool,regular_layout>{});
- }
- inline void increment(std::true_type /* regular layout */)noexcept
- {
- using diff_type=
- typename boost::pointer_traits<char_pointer>::difference_type;
- for(;;){
- ++p_;
- if(reinterpret_cast<uintptr_t>(pc())%sizeof(group_type)==N-1){
- pc_+=static_cast<diff_type>(sizeof(group_type)-(N-1));
- break;
- }
- ++pc_;
- if(!group_type::is_occupied(pc()))continue;
- if(BOOST_UNLIKELY(group_type::is_sentinel(pc())))p_=nullptr;
- return;
- }
- for(;;){
- int mask=reinterpret_cast<group_type*>(pc())->match_occupied();
- if(mask!=0){
- auto n=unchecked_countr_zero(mask);
- if(BOOST_UNLIKELY(reinterpret_cast<group_type*>(pc())->is_sentinel(n))){
- p_=nullptr;
- }
- else{
- pc_+=static_cast<diff_type>(n);
- p_+=static_cast<diff_type>(n);
- }
- return;
- }
- pc_+=static_cast<diff_type>(sizeof(group_type));
- p_+=static_cast<diff_type>(N);
- }
- }
- inline void increment(std::false_type /* interleaved */)noexcept
- {
- using diff_type=
- typename boost::pointer_traits<char_pointer>::difference_type;
- std::size_t n0=reinterpret_cast<uintptr_t>(pc())%sizeof(group_type);
- pc_-=static_cast<diff_type>(n0);
- int mask=(
- reinterpret_cast<group_type*>(pc())->match_occupied()>>(n0+1))<<(n0+1);
- if(!mask){
- do{
- pc_+=sizeof(group_type);
- p_+=N;
- }
- while((mask=reinterpret_cast<group_type*>(pc())->match_occupied())==0);
- }
- auto n=unchecked_countr_zero(mask);
- if(BOOST_UNLIKELY(reinterpret_cast<group_type*>(pc())->is_sentinel(n))){
- p_=nullptr;
- }
- else{
- pc_+=static_cast<diff_type>(n);
- p_-=static_cast<diff_type>(n0);
- p_+=static_cast<diff_type>(n);
- }
- }
- template<typename Archive>
- friend void serialization_track(Archive& ar,const table_iterator& x)
- {
- if(x.p()){
- track_address(ar,x.pc_);
- track_address(ar,x.p_);
- }
- }
- friend class boost::serialization::access;
- template<typename Archive>
- void serialize(Archive& ar,unsigned int)
- {
- if(!p())pc_=nullptr;
- serialize_tracked_address(ar,pc_);
- serialize_tracked_address(ar,p_);
- }
- char_pointer pc_=nullptr;
- table_element_pointer p_=nullptr;
- };
- /* Returned by table::erase([const_]iterator) to avoid iterator increment
- * if discarded.
- */
- template<typename Iterator>
- class table_erase_return_type;
- template<typename TypePolicy,typename GroupPtr,bool Const>
- class table_erase_return_type<table_iterator<TypePolicy,GroupPtr,Const>>
- {
- using iterator=table_iterator<TypePolicy,GroupPtr,Const>;
- using const_iterator=table_iterator<TypePolicy,GroupPtr,true>;
- public:
- /* can't delete it because VS in pre-C++17 mode needs to see it for RVO */
- table_erase_return_type(const table_erase_return_type&);
- operator iterator()const noexcept
- {
- auto it=pos;
- it.increment(); /* valid even if *it was erased */
- return iterator(const_iterator_cast_tag{},it);
- }
- template<
- bool dependent_value=false,
- typename std::enable_if<!Const||dependent_value>::type* =nullptr
- >
- operator const_iterator()const noexcept{return this->operator iterator();}
- private:
- template<typename,typename,typename,typename> friend class table;
- table_erase_return_type(const_iterator pos_):pos{pos_}{}
- table_erase_return_type& operator=(const table_erase_return_type&)=delete;
- const_iterator pos;
- };
- /* foa::table interface departs in a number of ways from that of C++ unordered
- * associative containers because it's not for end-user consumption
- * (boost::unordered_(flat|node)_(map|set) wrappers complete it as
- * appropriate).
- *
- * The table supports two main modes of operation: flat and node-based. In the
- * flat case, buckets directly store elements. For node-based, buckets store
- * pointers to individually heap-allocated elements.
- *
- * For both flat and node-based:
- *
- * - begin() is not O(1).
- * - No bucket API.
- * - Load factor is fixed and can't be set by the user.
- *
- * For flat only:
- *
- * - value_type must be moveable.
- * - Pointer stability is not kept under rehashing.
- * - No extract API.
- *
- * try_emplace, erase and find support heterogeneous lookup by default,
- * that is, without checking for any ::is_transparent typedefs --the
- * checking is done by boost::unordered_(flat|node)_(map|set).
- */
- template<typename,typename,typename,typename>
- class concurrent_table; /* concurrent/non-concurrent interop */
- template <typename TypePolicy,typename Hash,typename Pred,typename Allocator>
- using table_core_impl=
- table_core<TypePolicy,group15<plain_integral>,table_arrays,
- plain_size_control,Hash,Pred,Allocator>;
- #include <boost/unordered/detail/foa/ignore_wshadow.hpp>
- #if defined(BOOST_MSVC)
- #pragma warning(push)
- #pragma warning(disable:4714) /* marked as __forceinline not inlined */
- #endif
- template<typename TypePolicy,typename Hash,typename Pred,typename Allocator>
- class table:table_core_impl<TypePolicy,Hash,Pred,Allocator>
- {
- using super=table_core_impl<TypePolicy,Hash,Pred,Allocator>;
- using type_policy=typename super::type_policy;
- using group_type=typename super::group_type;
- using super::N;
- using prober=typename super::prober;
- using arrays_type=typename super::arrays_type;
- using size_ctrl_type=typename super::size_ctrl_type;
- using locator=typename super::locator;
- using compatible_concurrent_table=
- concurrent_table<TypePolicy,Hash,Pred,Allocator>;
- using group_type_pointer=typename boost::pointer_traits<
- typename boost::allocator_pointer<Allocator>::type
- >::template rebind<group_type>;
- friend compatible_concurrent_table;
- public:
- using key_type=typename super::key_type;
- using init_type=typename super::init_type;
- using value_type=typename super::value_type;
- using element_type=typename super::element_type;
- private:
- static constexpr bool has_mutable_iterator=
- !std::is_same<key_type,value_type>::value;
- public:
- using hasher=typename super::hasher;
- using key_equal=typename super::key_equal;
- using allocator_type=typename super::allocator_type;
- using pointer=typename super::pointer;
- using const_pointer=typename super::const_pointer;
- using reference=typename super::reference;
- using const_reference=typename super::const_reference;
- using size_type=typename super::size_type;
- using difference_type=typename super::difference_type;
- using const_iterator=table_iterator<type_policy,group_type_pointer,true>;
- using iterator=typename std::conditional<
- has_mutable_iterator,
- table_iterator<type_policy,group_type_pointer,false>,
- const_iterator>::type;
- using erase_return_type=table_erase_return_type<iterator>;
- #if defined(BOOST_UNORDERED_ENABLE_STATS)
- using stats=typename super::stats;
- #endif
- table(
- std::size_t n=default_bucket_count,const Hash& h_=Hash(),
- const Pred& pred_=Pred(),const Allocator& al_=Allocator()):
- super{n,h_,pred_,al_}
- {}
- table(const table& x)=default;
- table(table&& x)=default;
- table(const table& x,const Allocator& al_):super{x,al_}{}
- table(table&& x,const Allocator& al_):super{std::move(x),al_}{}
- table(compatible_concurrent_table&& x):
- table(std::move(x),x.exclusive_access()){}
- ~table()=default;
- table& operator=(const table& x)=default;
- table& operator=(table&& x)=default;
- using super::get_allocator;
- iterator begin()noexcept
- {
- iterator it{this->arrays.groups(),0,this->arrays.elements()};
- if(this->arrays.elements()&&
- !(this->arrays.groups()[0].match_occupied()&0x1))++it;
- return it;
- }
- const_iterator begin()const noexcept
- {return const_cast<table*>(this)->begin();}
- iterator end()noexcept{return {};}
- const_iterator end()const noexcept{return const_cast<table*>(this)->end();}
- const_iterator cbegin()const noexcept{return begin();}
- const_iterator cend()const noexcept{return end();}
- using super::empty;
- using super::size;
- using super::max_size;
- template<typename... Args>
- BOOST_FORCEINLINE std::pair<iterator,bool> emplace(Args&&... args)
- {
- alloc_cted_insert_type<type_policy,Allocator,Args...> x(
- this->al(),std::forward<Args>(args)...);
- return emplace_impl(type_policy::move(x.value()));
- }
- /* Optimization for value_type and init_type, to avoid constructing twice */
- template <typename T>
- BOOST_FORCEINLINE typename std::enable_if<
- detail::is_similar_to_any<T, value_type, init_type>::value,
- std::pair<iterator, bool> >::type
- emplace(T&& x)
- {
- return emplace_impl(std::forward<T>(x));
- }
- /* Optimizations for maps for (k,v) to avoid eagerly constructing value */
- template <typename K, typename V>
- BOOST_FORCEINLINE
- typename std::enable_if<is_emplace_kv_able<table, K>::value,
- std::pair<iterator, bool> >::type
- emplace(K&& k, V&& v)
- {
- alloc_cted_or_fwded_key_type<type_policy, Allocator, K&&> x(
- this->al(), std::forward<K>(k));
- return emplace_impl(
- try_emplace_args_t{}, x.move_or_fwd(), std::forward<V>(v));
- }
- template<typename Key,typename... Args>
- BOOST_FORCEINLINE std::pair<iterator,bool> try_emplace(
- Key&& x,Args&&... args)
- {
- return emplace_impl(
- try_emplace_args_t{},std::forward<Key>(x),std::forward<Args>(args)...);
- }
- BOOST_FORCEINLINE std::pair<iterator,bool>
- insert(const init_type& x){return emplace_impl(x);}
- BOOST_FORCEINLINE std::pair<iterator,bool>
- insert(init_type&& x){return emplace_impl(std::move(x));}
- /* template<typename=void> tilts call ambiguities in favor of init_type */
- template<typename=void>
- BOOST_FORCEINLINE std::pair<iterator,bool>
- insert(const value_type& x){return emplace_impl(x);}
- template<typename=void>
- BOOST_FORCEINLINE std::pair<iterator,bool>
- insert(value_type&& x){return emplace_impl(std::move(x));}
- template<typename T=element_type>
- BOOST_FORCEINLINE
- typename std::enable_if<
- !std::is_same<T,value_type>::value,
- std::pair<iterator,bool>
- >::type
- insert(element_type&& x){return emplace_impl(std::move(x));}
- template<
- bool dependent_value=false,
- typename std::enable_if<
- has_mutable_iterator||dependent_value>::type* =nullptr
- >
- erase_return_type erase(iterator pos)noexcept
- {return erase(const_iterator(pos));}
- BOOST_FORCEINLINE
- erase_return_type erase(const_iterator pos)noexcept
- {
- super::erase(pos.pc(),pos.p());
- return {pos};
- }
- template<typename Key>
- BOOST_FORCEINLINE
- auto erase(Key&& x) -> typename std::enable_if<
- !std::is_convertible<Key,iterator>::value&&
- !std::is_convertible<Key,const_iterator>::value, std::size_t>::type
- {
- auto it=find(x);
- if(it!=end()){
- erase(it);
- return 1;
- }
- else return 0;
- }
- void swap(table& x)
- noexcept(noexcept(std::declval<super&>().swap(std::declval<super&>())))
- {
- super::swap(x);
- }
- using super::clear;
- element_type extract(const_iterator pos)
- {
- BOOST_ASSERT(pos!=end());
- erase_on_exit e{*this,pos};
- (void)e;
- return std::move(*pos.p());
- }
- // TODO: should we accept different allocator too?
- template<typename Hash2,typename Pred2>
- void merge(table<TypePolicy,Hash2,Pred2,Allocator>& x)
- {
- x.for_all_elements([&,this](group_type* pg,unsigned int n,element_type* p){
- erase_on_exit e{x,{pg,n,p}};
- if(!emplace_impl(type_policy::move(*p)).second)e.rollback();
- });
- }
- template<typename Hash2,typename Pred2>
- void merge(table<TypePolicy,Hash2,Pred2,Allocator>&& x){merge(x);}
- using super::hash_function;
- using super::key_eq;
- template<typename Key>
- BOOST_FORCEINLINE iterator find(const Key& x)
- {
- return make_iterator(super::find(x));
- }
- template<typename Key>
- BOOST_FORCEINLINE const_iterator find(const Key& x)const
- {
- return const_cast<table*>(this)->find(x);
- }
- using super::capacity;
- using super::load_factor;
- using super::max_load_factor;
- using super::max_load;
- using super::rehash;
- using super::reserve;
- #if defined(BOOST_UNORDERED_ENABLE_STATS)
- using super::get_stats;
- using super::reset_stats;
- #endif
- template<typename Predicate>
- friend std::size_t erase_if(table& x,Predicate& pr)
- {
- using value_reference=typename std::conditional<
- std::is_same<key_type,value_type>::value,
- const_reference,
- reference
- >::type;
- std::size_t s=x.size();
- x.for_all_elements(
- [&](group_type* pg,unsigned int n,element_type* p){
- if(pr(const_cast<value_reference>(type_policy::value_from(*p)))){
- x.super::erase(pg,n,p);
- }
- });
- return std::size_t(s-x.size());
- }
- friend bool operator==(const table& x,const table& y)
- {
- return static_cast<const super&>(x)==static_cast<const super&>(y);
- }
- friend bool operator!=(const table& x,const table& y){return !(x==y);}
- private:
- template<typename ArraysType>
- table(compatible_concurrent_table&& x,arrays_holder<ArraysType,Allocator>&& ah):
- super{
- std::move(x.h()),std::move(x.pred()),std::move(x.al()),
- [&x]{return arrays_type{
- x.arrays.groups_size_index,x.arrays.groups_size_mask,
- to_pointer<group_type_pointer>(
- reinterpret_cast<group_type*>(x.arrays.groups())),
- x.arrays.elements_};},
- size_ctrl_type{x.size_ctrl.ml,x.size_ctrl.size}}
- {
- compatible_concurrent_table::arrays_type::delete_group_access(x.al(),x.arrays);
- x.arrays=ah.release();
- x.size_ctrl.ml=x.initial_max_load();
- x.size_ctrl.size=0;
- BOOST_UNORDERED_SWAP_STATS(this->cstats,x.cstats);
- }
- template<typename ExclusiveLockGuard>
- table(compatible_concurrent_table&& x,ExclusiveLockGuard):
- table(std::move(x),x.make_empty_arrays())
- {}
- struct erase_on_exit
- {
- erase_on_exit(table& x_,const_iterator it_):x(x_),it(it_){}
- ~erase_on_exit(){if(!rollback_)x.erase(it);}
- void rollback(){rollback_=true;}
- table& x;
- const_iterator it;
- bool rollback_=false;
- };
- static inline iterator make_iterator(const locator& l)noexcept
- {
- return {l.pg,l.n,l.p};
- }
- template<typename... Args>
- BOOST_FORCEINLINE std::pair<iterator,bool> emplace_impl(Args&&... args)
- {
- const auto &k=this->key_from(std::forward<Args>(args)...);
- auto hash=this->hash_for(k);
- auto pos0=this->position_for(hash);
- auto loc=super::find(k,pos0,hash);
- if(loc){
- return {make_iterator(loc),false};
- }
- if(BOOST_LIKELY(this->size_ctrl.size<this->size_ctrl.ml)){
- return {
- make_iterator(
- this->unchecked_emplace_at(pos0,hash,std::forward<Args>(args)...)),
- true
- };
- }
- else{
- return {
- make_iterator(
- this->unchecked_emplace_with_rehash(
- hash,std::forward<Args>(args)...)),
- true
- };
- }
- }
- };
- #if defined(BOOST_MSVC)
- #pragma warning(pop) /* C4714 */
- #endif
- #include <boost/unordered/detail/foa/restore_wshadow.hpp>
- } /* namespace foa */
- } /* namespace detail */
- } /* namespace unordered */
- } /* namespace boost */
- #endif
|