diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index f45f844d..03a8f51c 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -72,7 +72,8 @@ jobs: compiler: clang-14, cxxstd: '17,20,2b', os: ubuntu-22.04, ccache_key: "san2" } # OSX, clang - - { compiler: clang, cxxstd: '03,11,14,17,2a', os: macos-11, sanitize: yes } + - { compiler: clang, cxxstd: '03,11,14,17,2a', os: macos-11, } + - { compiler: clang, cxxstd: '03,11,14,17,2a', os: macos-12, sanitize: yes } timeout-minutes: 120 runs-on: ${{matrix.os}} diff --git a/include/boost/unordered/detail/foa.hpp b/include/boost/unordered/detail/foa.hpp index 62038f95..61fbf732 100644 --- a/include/boost/unordered/detail/foa.hpp +++ b/include/boost/unordered/detail/foa.hpp @@ -1,6 +1,7 @@ /* Fast open-addressing hash table. * * Copyright 2022-2023 Joaquin M Lopez Munoz. + * Copyright 2023 Christian Mazakas. * 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) @@ -805,12 +806,15 @@ class table; /* internal conversion from const_iterator to iterator */ class const_iterator_cast_tag {}; -template +template class table_iterator { + using type_policy=TypePolicy; + using table_element_type=typename type_policy::element_type; + public: using difference_type=std::ptrdiff_t; - using value_type=Value; + using value_type=typename type_policy::value_type; using pointer= typename std::conditional::type; using reference= @@ -821,14 +825,15 @@ public: table_iterator()=default; template::type* =nullptr> - table_iterator(const table_iterator& x): + table_iterator(const table_iterator& x): pc{x.pc},p{x.p}{} table_iterator( - const_iterator_cast_tag, const table_iterator& x): + const_iterator_cast_tag, const table_iterator& x): pc{x.pc},p{x.p}{} - inline reference operator*()const noexcept{return *p;} - inline pointer operator->()const noexcept{return 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;} @@ -843,9 +848,9 @@ private: template friend class table_iterator; template friend class table; - table_iterator(Group* pg,std::size_t n,const Value* p_): + table_iterator(Group* pg,std::size_t n,const table_element_type* p_): pc{reinterpret_cast(const_cast(pg))+n}, - p{const_cast(p_)} + p{const_cast(p_)} {} inline std::size_t rebase() noexcept @@ -879,8 +884,8 @@ private: } } - unsigned char *pc=nullptr; - Value *p=nullptr; + unsigned char *pc=nullptr; + table_element_type *p=nullptr; }; /* table_arrays controls allocation, initialization and deallocation of @@ -921,7 +926,9 @@ struct table_arrays template static table_arrays new_(Allocator& al,std::size_t n) { - using alloc_traits=boost::allocator_traits; + using storage_allocator= + typename boost::allocator_rebind::type; + using storage_traits=boost::allocator_traits; auto groups_size_index=size_index_for(n); auto groups_size=size_policy::size(groups_size_index); @@ -931,8 +938,9 @@ struct table_arrays arrays.groups=dummy_groups(); } else{ - arrays.elements= - boost::to_address(alloc_traits::allocate(al,buffer_size(groups_size))); + auto sal=storage_allocator(al); + arrays.elements=boost::to_address( + storage_traits::allocate(sal,buffer_size(groups_size))); /* Align arrays.groups to sizeof(group_type). table_iterator critically * depends on such alignment for its increment operation. @@ -956,13 +964,15 @@ struct table_arrays template static void delete_(Allocator& al,table_arrays& arrays)noexcept { - using alloc_traits=boost::allocator_traits; - using pointer=typename alloc_traits::pointer; + using storage_alloc=typename boost::allocator_rebind::type; + using storage_traits=boost::allocator_traits; + using pointer=typename storage_traits::pointer; using pointer_traits=boost::pointer_traits; + auto sal=storage_alloc(al); if(arrays.elements){ - alloc_traits::deallocate( - al,pointer_traits::pointer_to(*arrays.elements), + storage_traits::deallocate( + sal,pointer_traits::pointer_to(*arrays.elements), buffer_size(arrays.groups_size_mask+1)); } } @@ -1101,51 +1111,71 @@ _STL_RESTORE_DEPRECATED_WARNING #pragma warning(disable:4702) #endif +/* We expose the hard-coded max load factor so that tests can use it without + * needing to pull it from an instantiated class template such as the table + * class + */ +constexpr static float const mlf = 0.875f; + /* 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_[map|set] wrappers complete it as appropriate) and, - * more importantly, because of fundamental restrictions imposed by open - * addressing: - * - * - value_type must be moveable. - * - Pointer stability is not kept under rehashing. + * (boost::unordered_[flat|node]_[map|set]) wrappers complete it as + * appropriate). + * + * The table supports two main modes of operation: node-based and flat. In the + * node-based case, buckets store pointers to individually heap-allocated + * elements. For flat, buckets directly store elements. + * + * For both tables: + * * - begin() is not O(1). * - No bucket API. * - Load factor is fixed and can't be set by the user. - * - No extract API. * + * For the inline table: + * + * - value_type must be moveable. + * - Pointer stability is not kept under rehashing. + * - No extract API. + * * The TypePolicy template parameter is used to generate instantiations * suitable for either maps or sets, and introduces non-standard init_type: - * + * * - TypePolicy::key_type and TypePolicy::value_type have the obvious * meaning. + * * - TypePolicy::init_type is the type implicitly converted to when * writing x.insert({...}). For maps, this is std::pair rather * than std::pair so that, for instance, x.insert({"hello",0}) * produces a cheaply moveable std::string&& ("hello") rather than * a copyable const std::string&&. foa::table::insert is extended to accept * both init_type and value_type references. - * - TypePolicy::move(value_type&) returns a temporary object for value - * transfer on rehashing, move copy/assignment, and merge. For maps, this + * + * - TypePolicy::move(element_type&) returns a temporary object for value + * transfer on rehashing, move copy/assignment, and merge. For flat map, this * object is a std::pair, which is generally cheaper to move - * than std::pair&& because of the constness in Key. + * than std::pair&& because of the constness in Key. For + * node-based tables, this is used to transfer ownership of pointer. + * * - TypePolicy::extract returns a const reference to the key part of - * a value of type value_type, init_type or + * a value of type value_type, init_type, element_type or * decltype(TypePolicy::move(...)). - * - * try_emplace, erase and find support heterogenous lookup by default, that is, - * without checking for any ::is_transparent typedefs --the checking is done by - * boost::unordered_flat_[map|set]. - * - * At the moment, we're not supporting allocators with fancy pointers. - * Allocator::pointer must be convertible to/from regular pointers. + * + * - TypePolicy::element_type is the type that table_arrays uses when + * allocating buckets. For flat containers, this is value_type. For node + * containers, this is a strong typedef to value_type*. + * + * - TypePolicy::value_from returns a mutable reference to value_type from + * a given element_type. This is used when elements of the table themselves + * need to be moved, such as during move construction/assignment when + * allocators are unequal and there is no propagation. For all other cases, + * the element_type itself is moved. + * + * try_emplace, erase and find support heterogenous lookup by default, that is, + * without checking for any ::is_transparent typedefs --the checking is done by + * boost::unordered_[flat|node]_[map|set]. */ -/* We pull this out so the tests don't have to rely on a magic constant or - * instantiate the table class template as it can be quite gory. - */ -constexpr static float const mlf = 0.875f; - template class @@ -1174,6 +1204,7 @@ public: using key_type=typename type_policy::key_type; using init_type=typename type_policy::init_type; using value_type=typename type_policy::value_type; + using element_type=typename type_policy::element_type; private: static constexpr bool has_mutable_iterator= @@ -1189,10 +1220,10 @@ public: using const_reference=const value_type&; using size_type=std::size_t; using difference_type=std::ptrdiff_t; - using const_iterator=table_iterator; + using const_iterator=table_iterator; using iterator=typename std::conditional< has_mutable_iterator, - table_iterator, + table_iterator, const_iterator>::type; table( @@ -1243,15 +1274,15 @@ public: /* This works because subsequent x.clear() does not depend on the * elements' values. */ - x.for_all_elements([this](value_type* p){ - unchecked_insert(type_policy::move(*p)); + x.for_all_elements([this](element_type* p){ + unchecked_insert(type_policy::move(type_policy::value_from(*p))); }); } } ~table()noexcept { - for_all_elements([this](value_type* p){ + for_all_elements([this](element_type* p){ destroy_element(p); }); delete_arrays(arrays); @@ -1340,8 +1371,8 @@ public: /* This works because subsequent x.clear() does not depend on the * elements' values. */ - x.for_all_elements([this](value_type* p){ - unchecked_insert(type_policy::move(*p)); + x.for_all_elements([this](element_type* p){ + unchecked_insert(type_policy::move(type_policy::value_from(*p))); }); } } @@ -1375,14 +1406,18 @@ public: template BOOST_FORCEINLINE std::pair emplace(Args&&... args) { - using emplace_type = typename std::conditional< + /* We dispatch based on whether or not the value_type is constructible from + * an rvalue reference of the deduced emplace_type. We do this specifically + * for the case of the node-based containers. To this end, we're able to + * avoid allocating a node when a duplicate element is attempted to be + * inserted. For immovable types, we instead dispatch to the routine that + * unconditionally allocates via `type_policy::construct()`. + */ + return emplace_value( std::is_constructible< - init_type, Args... - >::value, - init_type, - value_type - >::type; - return emplace_impl(emplace_type(std::forward(args)...)); + value_type, + emplace_type&&>{}, + std::forward(args)...); } template @@ -1409,6 +1444,14 @@ public: BOOST_FORCEINLINE std::pair insert(value_type&& x){return emplace_impl(std::move(x));} + template + BOOST_FORCEINLINE + typename std::enable_if< + !std::is_same::value, + std::pair + >::type + insert(element_type&& x){return emplace_impl(std::move(x));} + template< bool dependent_value=false, typename std::enable_if< @@ -1483,14 +1526,21 @@ public: } } + 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 void merge(table& x) { - x.for_all_elements([&,this](group_type* pg,unsigned int n,value_type* p){ - if(emplace_impl(type_policy::move(*p)).second){ - x.erase(iterator{pg,n,p}); - } + 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(); }); } @@ -1550,7 +1600,16 @@ public: private: template friend class table; - using arrays_type=table_arrays; + using arrays_type=table_arrays; + + template + using emplace_type = typename std::conditional< + std::is_constructible< + init_type,Args... + >::value, + init_type, + value_type + >::type; struct clear_on_exit { @@ -1558,6 +1617,18 @@ private: table& x; }; + 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; + }; + Hash& h(){return hash_base::get();} const Hash& h()const{return hash_base::get();} Pred& pred(){return pred_base::get();} @@ -1576,13 +1647,13 @@ private: } template - void construct_element(value_type* p,Args&&... args) + void construct_element(element_type* p,Args&&... args) { - alloc_traits::construct(al(),p,std::forward(args)...); + type_policy::construct(al(),p,std::forward(args)...); } template - void construct_element(value_type* p,try_emplace_args_t,Args&&... args) + void construct_element(element_type* p,try_emplace_args_t,Args&&... args) { construct_element_from_try_emplace_args( p, @@ -1592,9 +1663,9 @@ private: template void construct_element_from_try_emplace_args( - value_type* p,std::false_type,Key&& x,Args&&... args) + element_type* p,std::false_type,Key&& x,Args&&... args) { - alloc_traits::construct( + type_policy::construct( al(),p, std::piecewise_construct, std::forward_as_tuple(std::forward(x)), @@ -1607,21 +1678,21 @@ private: template void construct_element_from_try_emplace_args( - value_type* p,std::true_type,Key&& x) + element_type* p,std::true_type,Key&& x) { - alloc_traits::construct(al(),p,std::forward(x)); + type_policy::construct(al(),p,std::forward(x)); } - void destroy_element(value_type* p)noexcept + void destroy_element(element_type* p)noexcept { - alloc_traits::destroy(al(),p); + type_policy::destroy(al(),p); } struct destroy_element_on_exit { ~destroy_element_on_exit(){this_->destroy_element(p);} - table *this_; - value_type *p; + table *this_; + element_type *p; }; void copy_elements_from(const table& x) @@ -1632,7 +1703,7 @@ private: fast_copy_elements_from(x); } else{ - x.for_all_elements([this](const value_type* p){ + x.for_all_elements([this](const element_type* p){ unchecked_insert(*p); }); } @@ -1657,9 +1728,9 @@ private: bool, #if BOOST_WORKAROUND(BOOST_LIBSTDCXX_VERSION,<50000) /* std::is_trivially_copy_constructible not provided */ - boost::has_trivial_copy::value + boost::has_trivial_copy::value #else - std::is_trivially_copy_constructible::value + std::is_trivially_copy_constructible::value #endif &&( is_std_allocator::value|| @@ -1683,14 +1754,14 @@ private: { std::size_t num_constructed=0; BOOST_TRY{ - x.for_all_elements([&,this](const value_type* p){ + x.for_all_elements([&,this](const element_type* p){ construct_element(arrays.elements+(p-x.arrays.elements),*p); ++num_constructed; }); } BOOST_CATCH(...){ if(num_constructed){ - x.for_all_elements_while([&,this](const value_type* p){ + x.for_all_elements_while([&,this](const element_type* p){ destroy_element(arrays.elements+(p-x.arrays.elements)); return --num_constructed!=0; }); @@ -1760,7 +1831,7 @@ private: return size_policy::position(hash,arrays_.groups_size_index); } - static inline void prefetch_elements(const value_type* p) + static inline void prefetch_elements(const element_type* p) { /* We have experimentally confirmed that ARM architectures get a higher * speedup when around the first half of the element slots in a group are @@ -1821,6 +1892,29 @@ private: #pragma warning(pop) /* C4800 */ #endif + template + BOOST_FORCEINLINE std::pair emplace_value( + std::true_type /* movable value_type */,Args&&... args + ) { + using emplace_type_t = emplace_type; + return emplace_impl(emplace_type_t(std::forward(args)...)); + } + + template + BOOST_FORCEINLINE std::pair emplace_value( + std::false_type /* immovable value_type */,Args&&... args + ) { + alignas(element_type) + unsigned char buf[sizeof(element_type)]; + element_type* p = reinterpret_cast(buf); + + type_policy::construct(al(),p,std::forward(args)...); + destroy_element_on_exit d{this,p}; + (void)d; + + return emplace_impl(type_policy::move(*p)); + } + template BOOST_FORCEINLINE std::pair emplace_impl(Args&&... args) { @@ -1896,20 +1990,20 @@ private: { std::size_t num_destroyed=0; BOOST_TRY{ - for_all_elements([&,this](value_type* p){ + for_all_elements([&,this](element_type* p){ nosize_transfer_element(p,new_arrays_,num_destroyed); }); } BOOST_CATCH(...){ if(num_destroyed){ for_all_elements_while( - [&,this](group_type* pg,unsigned int n,value_type*){ + [&,this](group_type* pg,unsigned int n,element_type*){ recover_slot(pg,n); return --num_destroyed!=0; } ); } - for_all_elements(new_arrays_,[this](value_type* p){ + for_all_elements(new_arrays_,[this](element_type* p){ destroy_element(p); }); delete_arrays(new_arrays_); @@ -1920,7 +2014,7 @@ private: /* either all moved and destroyed or all copied */ BOOST_ASSERT(num_destroyed==size()||num_destroyed==0); if(num_destroyed!=size()){ - for_all_elements([this](value_type* p){ + for_all_elements([this](element_type* p){ destroy_element(p); }); } @@ -1955,18 +2049,19 @@ private: } void nosize_transfer_element( - value_type* p,const arrays_type& arrays_,std::size_t& num_destroyed) + element_type* p,const arrays_type& arrays_,std::size_t& num_destroyed) { nosize_transfer_element( p,hash_for(key_from(*p)),arrays_,num_destroyed, std::integral_constant< /* std::move_if_noexcept semantics */ bool, std::is_nothrow_move_constructible::value|| - !std::is_copy_constructible::value>{}); + !std::is_same::value|| + !std::is_copy_constructible::value>{}); } void nosize_transfer_element( - value_type* p,std::size_t hash,const arrays_type& arrays_, + element_type* p,std::size_t hash,const arrays_type& arrays_, std::size_t& num_destroyed,std::true_type /* ->move */) { /* Destroy p even if an an exception is thrown in the middle of move @@ -1980,12 +2075,12 @@ private: } void nosize_transfer_element( - value_type* p,std::size_t hash,const arrays_type& arrays_, + element_type* p,std::size_t hash,const arrays_type& arrays_, std::size_t& /*num_destroyed*/,std::false_type /* ->copy */) { nosize_unchecked_emplace_at( arrays_,position_for(hash,arrays_),hash, - const_cast(*p)); + const_cast(*p)); } template @@ -2022,8 +2117,8 @@ private: std::size_t erase_if_impl(Predicate pr) { std::size_t s=size(); - for_all_elements([&,this](group_type* pg,unsigned int n,value_type* p){ - if(pr(*p)) erase(iterator{pg,n,p}); + for_all_elements([&,this](group_type* pg,unsigned int n,element_type* p){ + if(pr(type_policy::value_from(*p))) erase(iterator{pg,n,p}); }); return std::size_t(s-size()); } @@ -2038,7 +2133,7 @@ private: static auto for_all_elements(const arrays_type& arrays_,F f) ->decltype(f(nullptr),void()) { - for_all_elements_while(arrays_,[&](value_type* p){f(p);return true;}); + for_all_elements_while(arrays_,[&](element_type* p){f(p);return true;}); } template @@ -2046,7 +2141,7 @@ private: ->decltype(f(nullptr,0,nullptr),void()) { for_all_elements_while( - arrays_,[&](group_type* pg,unsigned int n,value_type* p) + arrays_,[&](group_type* pg,unsigned int n,element_type* p) {f(pg,n,p);return true;}); } @@ -2061,7 +2156,7 @@ private: ->decltype(f(nullptr),void()) { for_all_elements_while( - arrays_,[&](group_type*,unsigned int,value_type* p){return f(p);}); + arrays_,[&](group_type*,unsigned int,element_type* p){return f(p);}); } template diff --git a/include/boost/unordered/detail/foa/node_handle.hpp b/include/boost/unordered/detail/foa/node_handle.hpp new file mode 100644 index 00000000..1f6319db --- /dev/null +++ b/include/boost/unordered/detail/foa/node_handle.hpp @@ -0,0 +1,203 @@ +/* Copyright 2023 Christian Mazakas. + * 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_NODE_HANDLE_HPP +#define BOOST_UNORDERED_DETAIL_FOA_NODE_HANDLE_HPP + +#include +#include + +namespace boost{ +namespace unordered{ +namespace detail{ +namespace foa{ + +template +struct insert_return_type +{ + Iterator position; + bool inserted; + NodeType node; +}; + +template +union opt_storage { + BOOST_ATTRIBUTE_NO_UNIQUE_ADDRESS T t_; + + opt_storage(){} + ~opt_storage(){} +}; + +template +struct node_handle_base +{ + protected: + using type_policy=TypePolicy; + using element_type=typename type_policy::element_type; + + public: + using allocator_type = Allocator; + + private: + using node_value_type=typename type_policy::value_type; + node_value_type* p_=nullptr; + BOOST_ATTRIBUTE_NO_UNIQUE_ADDRESS opt_storage a_; + + protected: + node_value_type& element()noexcept + { + BOOST_ASSERT(!empty()); + return *p_; + } + + node_value_type const& element()const noexcept + { + BOOST_ASSERT(!empty()); + return *p_; + } + + Allocator& al()noexcept + { + BOOST_ASSERT(!empty()); + return a_.t_; + } + + Allocator const& al()const noexcept + { + BOOST_ASSERT(!empty()); + return a_.t_; + } + + void emplace(node_value_type* p,Allocator a) + { + BOOST_ASSERT(empty()); + p_=p; + new(&a_.t_)Allocator(a); + } + + void emplace(element_type&& x,Allocator a) + { + emplace(x.p,a); + x.p=nullptr; + } + + void reset() + { + al().~Allocator(); + p_=nullptr; + } + + public: + constexpr node_handle_base()noexcept{} + + node_handle_base(node_handle_base&& nh) noexcept + { + if (!nh.empty()){ + emplace(nh.p_,nh.al()); + nh.reset(); + } + } + + node_handle_base& operator=(node_handle_base&& nh)noexcept + { + if(this!=&nh){ + if(empty()){ + if(nh.empty()){ /* empty(), nh.empty() */ + /* nothing to do */ + }else{ /* empty(), !nh.empty() */ + emplace(nh.p_,std::move(nh.al())); + nh.reset(); + } + }else{ + if(nh.empty()){ /* !empty(), nh.empty() */ + type_policy::destroy(al(),p_); + reset(); + }else{ /* !empty(), !nh.empty() */ + bool const pocma= + boost::allocator_propagate_on_container_move_assignment< + Allocator>::type::value; + + BOOST_ASSERT(pocma||al()==nh.al()); + + type_policy::destroy(al(),p_); + if(pocma){ + al()=std::move(nh.al()); + } + + p_=nh.p_; + nh.reset(); + } + } + }else{ + if(empty()){ /* empty(), nh.empty() */ + /* nothing to do */ + }else{ /* !empty(), !nh.empty() */ + type_policy::destroy(al(),p_); + reset(); + } + } + return *this; + } + + ~node_handle_base() + { + if(!empty()){ + type_policy::destroy(al(),p_); + reset(); + } + } + + allocator_type get_allocator()const noexcept{return al();} + explicit operator bool()const noexcept{ return !empty();} + BOOST_ATTRIBUTE_NODISCARD bool empty()const noexcept{return p_==nullptr;} + + void swap(node_handle_base& nh) noexcept( + boost::allocator_is_always_equal::type::value|| + boost::allocator_propagate_on_container_swap::type::value) + { + if(this!=&nh){ + if(empty()){ + if(nh.empty()) { + /* nothing to do here */ + } else { + emplace(nh.p_, nh.al()); + nh.reset(); + } + }else{ + if(nh.empty()){ + nh.emplace(p_,al()); + reset(); + }else{ + bool const pocs= + boost::allocator_propagate_on_container_swap< + Allocator>::type::value; + + BOOST_ASSERT(pocs || al()==nh.al()); + + using std::swap; + swap(p_,nh.p_); + if(pocs)swap(al(),nh.al()); + } + } + } + } + + friend + void swap(node_handle_base& lhs,node_handle_base& rhs) + noexcept(noexcept(lhs.swap(rhs))) + { + return lhs.swap(rhs); + } +}; + +} +} +} +} + +#endif // BOOST_UNORDERED_DETAIL_FOA_NODE_HANDLE_HPP diff --git a/include/boost/unordered/unordered_flat_map.hpp b/include/boost/unordered/unordered_flat_map.hpp index 16d4f3bd..d419739b 100644 --- a/include/boost/unordered/unordered_flat_map.hpp +++ b/include/boost/unordered/unordered_flat_map.hpp @@ -32,10 +32,8 @@ namespace boost { #pragma warning(disable : 4714) /* marked as __forceinline not inlined */ #endif - template - class unordered_flat_map - { - struct map_types + namespace detail { + template struct flat_map_types { using key_type = Key; using raw_key_type = typename std::remove_const::type; @@ -45,19 +43,40 @@ namespace boost { using moved_type = std::pair; using value_type = std::pair; + using element_type = value_type; + + static value_type& value_from(element_type& x) { return x; } + template static raw_key_type const& extract(std::pair const& kv) { return kv.first; } - static moved_type move(value_type& x) + static moved_type move(element_type& x) { // TODO: we probably need to launder here return {std::move(const_cast(x.first)), std::move(const_cast(x.second))}; } + + template + static void construct(A& al, element_type* p, Args&&... args) + { + boost::allocator_construct(al, p, std::forward(args)...); + } + + template static void destroy(A& al, element_type* p) noexcept + { + boost::allocator_destroy(al, p); + } }; + } // namespace detail + + template + class unordered_flat_map + { + using map_types = detail::flat_map_types; using table_type = detail::foa::table - class unordered_flat_set - { - struct set_types + namespace detail { + template struct flat_set_types { using key_type = Key; using init_type = Key; using value_type = Key; + static Key const& extract(value_type const& key) { return key; } - static Key&& move(value_type& x) { return std::move(x); } + + using element_type = value_type; + + static Key& value_from(element_type& x) { return x; } + + static element_type&& move(element_type& x) { return std::move(x); } + + template + static void construct(A& al, element_type* p, Args&&... args) + { + boost::allocator_construct(al, p, std::forward(args)...); + } + + template static void destroy(A& al, element_type* p) noexcept + { + boost::allocator_destroy(al, p); + } }; + } // namespace detail + + template + class unordered_flat_set + { + using set_types = detail::flat_set_types; using table_type = detail::foa::table +#if defined(BOOST_HAS_PRAGMA_ONCE) +#pragma once +#endif + +#include +#include +#include +#include + +#include +#include +#include + +#include +#include +#include +#include +#include + +namespace boost { + namespace unordered { + +#if defined(BOOST_MSVC) +#pragma warning(push) +#pragma warning(disable : 4714) /* marked as __forceinline not inlined */ +#endif + + namespace detail { + template struct node_map_types + { + using key_type = Key; + using mapped_type = T; + using raw_key_type = typename std::remove_const::type; + using raw_mapped_type = typename std::remove_const::type; + + using init_type = std::pair; + using value_type = std::pair; + using moved_type = std::pair; + + struct element_type + { + value_type* p; + + /* + * we use a deleted copy constructor here so the type is no longer + * trivially copy-constructible which inhibits our memcpy + * optimizations when copying the tables + */ + element_type() = default; + element_type(element_type const&) = delete; + element_type(element_type&& rhs) noexcept + { + p = rhs.p; + rhs.p = nullptr; + } + }; + + static value_type& value_from(element_type const& x) { return *(x.p); } + + template + static raw_key_type const& extract(std::pair const& kv) + { + return kv.first; + } + + static raw_key_type const& extract(element_type const& kv) + { + return kv.p->first; + } + + static element_type&& move(element_type& x) { return std::move(x); } + static moved_type move(value_type& x) + { + return {std::move(const_cast(x.first)), + std::move(const_cast(x.second))}; + } + + template + static void construct(A&, element_type* p, element_type&& x) noexcept + { + p->p = x.p; + x.p = nullptr; + } + + template + static void construct(A& al, element_type* p, element_type const& copy) + { + construct(al, p, *copy.p); + } + + template + static void construct(A& al, element_type* p, Args&&... args) + { + p->p = boost::to_address(boost::allocator_allocate(al, 1)); + BOOST_TRY + { + boost::allocator_construct(al, p->p, std::forward(args)...); + } + BOOST_CATCH(...) + { + boost::allocator_deallocate(al, + boost::pointer_traits< + typename boost::allocator_pointer::type>::pointer_to(*p->p), + 1); + BOOST_RETHROW + } + BOOST_CATCH_END + } + + template static void destroy(A& al, value_type* p) noexcept + { + boost::allocator_destroy(al, p); + boost::allocator_deallocate(al, + boost::pointer_traits< + typename boost::allocator_pointer::type>::pointer_to(*p), + 1); + } + + template static void destroy(A& al, element_type* p) noexcept + { + if (p->p) { + destroy(al,p->p); + } + } + }; + + template + struct node_map_handle + : public detail::foa::node_handle_base + { + private: + using base_type = + detail::foa::node_handle_base; + + using typename base_type::type_policy; + + template + friend class boost::unordered::unordered_node_map; + + public: + using key_type = typename TypePolicy::key_type; + using mapped_type = typename TypePolicy::mapped_type; + + constexpr node_map_handle() noexcept = default; + node_map_handle(node_map_handle&& nh) noexcept = default; + + node_map_handle& operator=(node_map_handle&&) noexcept = default; + + key_type& key() const + { + BOOST_ASSERT(!this->empty()); + return const_cast(this->element().first); + } + + mapped_type& mapped() const + { + BOOST_ASSERT(!this->empty()); + return const_cast(this->element().second); + } + }; + } // namespace detail + + template + class unordered_node_map + { + using map_types = detail::node_map_types; + + using table_type = detail::foa::table >::type>; + + table_type table_; + + template + typename unordered_node_map::size_type friend erase_if( + unordered_node_map& set, Pred pred); + + public: + using key_type = Key; + using mapped_type = T; + using value_type = typename map_types::value_type; + using init_type = typename map_types::init_type; + using size_type = std::size_t; + using difference_type = std::ptrdiff_t; + using hasher = typename boost::type_identity::type; + using key_equal = typename boost::type_identity::type; + using allocator_type = typename boost::type_identity::type; + using reference = value_type&; + using const_reference = value_type const&; + using pointer = typename boost::allocator_pointer::type; + using const_pointer = + typename boost::allocator_const_pointer::type; + using iterator = typename table_type::iterator; + using const_iterator = typename table_type::const_iterator; + using node_type = detail::node_map_handle::type>; + using insert_return_type = + detail::foa::insert_return_type; + + unordered_node_map() : unordered_node_map(0) {} + + explicit unordered_node_map(size_type n, hasher const& h = hasher(), + key_equal const& pred = key_equal(), + allocator_type const& a = allocator_type()) + : table_(n, h, pred, a) + { + } + + unordered_node_map(size_type n, allocator_type const& a) + : unordered_node_map(n, hasher(), key_equal(), a) + { + } + + unordered_node_map(size_type n, hasher const& h, allocator_type const& a) + : unordered_node_map(n, h, key_equal(), a) + { + } + + template + unordered_node_map( + InputIterator f, InputIterator l, allocator_type const& a) + : unordered_node_map(f, l, size_type(0), hasher(), key_equal(), a) + { + } + + explicit unordered_node_map(allocator_type const& a) + : unordered_node_map(0, a) + { + } + + template + unordered_node_map(Iterator first, Iterator last, size_type n = 0, + hasher const& h = hasher(), key_equal const& pred = key_equal(), + allocator_type const& a = allocator_type()) + : unordered_node_map(n, h, pred, a) + { + this->insert(first, last); + } + + template + unordered_node_map( + Iterator first, Iterator last, size_type n, allocator_type const& a) + : unordered_node_map(first, last, n, hasher(), key_equal(), a) + { + } + + template + unordered_node_map(Iterator first, Iterator last, size_type n, + hasher const& h, allocator_type const& a) + : unordered_node_map(first, last, n, h, key_equal(), a) + { + } + + unordered_node_map(unordered_node_map const& other) : table_(other.table_) + { + } + + unordered_node_map( + unordered_node_map const& other, allocator_type const& a) + : table_(other.table_, a) + { + } + + unordered_node_map(unordered_node_map&& other) + noexcept(std::is_nothrow_move_constructible::value&& + std::is_nothrow_move_constructible::value&& + std::is_nothrow_move_constructible::value) + : table_(std::move(other.table_)) + { + } + + unordered_node_map(unordered_node_map&& other, allocator_type const& al) + : table_(std::move(other.table_), al) + { + } + + unordered_node_map(std::initializer_list ilist, + size_type n = 0, hasher const& h = hasher(), + key_equal const& pred = key_equal(), + allocator_type const& a = allocator_type()) + : unordered_node_map(ilist.begin(), ilist.end(), n, h, pred, a) + { + } + + unordered_node_map( + std::initializer_list il, allocator_type const& a) + : unordered_node_map(il, size_type(0), hasher(), key_equal(), a) + { + } + + unordered_node_map(std::initializer_list init, size_type n, + allocator_type const& a) + : unordered_node_map(init, n, hasher(), key_equal(), a) + { + } + + unordered_node_map(std::initializer_list init, size_type n, + hasher const& h, allocator_type const& a) + : unordered_node_map(init, n, h, key_equal(), a) + { + } + + ~unordered_node_map() = default; + + unordered_node_map& operator=(unordered_node_map const& other) + { + table_ = other.table_; + return *this; + } + + unordered_node_map& operator=(unordered_node_map&& other) noexcept( + noexcept(std::declval() = std::declval())) + { + table_ = std::move(other.table_); + return *this; + } + + allocator_type get_allocator() const noexcept + { + return table_.get_allocator(); + } + + /// Iterators + /// + + iterator begin() noexcept { return table_.begin(); } + const_iterator begin() const noexcept { return table_.begin(); } + const_iterator cbegin() const noexcept { return table_.cbegin(); } + + iterator end() noexcept { return table_.end(); } + const_iterator end() const noexcept { return table_.end(); } + const_iterator cend() const noexcept { return table_.cend(); } + + /// Capacity + /// + + BOOST_ATTRIBUTE_NODISCARD bool empty() const noexcept + { + return table_.empty(); + } + + size_type size() const noexcept { return table_.size(); } + + size_type max_size() const noexcept { return table_.max_size(); } + + /// Modifiers + /// + + void clear() noexcept { table_.clear(); } + + template + BOOST_FORCEINLINE auto insert(Ty&& value) + -> decltype(table_.insert(std::forward(value))) + { + return table_.insert(std::forward(value)); + } + + BOOST_FORCEINLINE std::pair insert(init_type&& value) + { + return table_.insert(std::move(value)); + } + + template + BOOST_FORCEINLINE auto insert(const_iterator, Ty&& value) + -> decltype(table_.insert(std::forward(value)).first) + { + return table_.insert(std::forward(value)).first; + } + + BOOST_FORCEINLINE iterator insert(const_iterator, init_type&& value) + { + return table_.insert(std::move(value)).first; + } + + template + BOOST_FORCEINLINE void insert(InputIterator first, InputIterator last) + { + for (auto pos = first; pos != last; ++pos) { + table_.emplace(*pos); + } + } + + void insert(std::initializer_list ilist) + { + this->insert(ilist.begin(), ilist.end()); + } + + insert_return_type insert(node_type&& nh) + { + if (nh.empty()) { + return {end(), false, node_type{}}; + } + + BOOST_ASSERT(get_allocator() == nh.get_allocator()); + + typename map_types::element_type x; + x.p = std::addressof(nh.element()); + + auto itp = table_.insert(std::move(x)); + if (itp.second) { + nh.reset(); + return {itp.first, true, node_type{}}; + } else { + return {itp.first, false, std::move(nh)}; + } + } + + iterator insert(const_iterator, node_type&& nh) + { + if (nh.empty()) { + return end(); + } + + BOOST_ASSERT(get_allocator() == nh.get_allocator()); + + typename map_types::element_type x; + x.p = std::addressof(nh.element()); + + auto itp = table_.insert(std::move(x)); + if (itp.second) { + nh.reset(); + return itp.first; + } else { + return itp.first; + } + } + + template + std::pair insert_or_assign(key_type const& key, M&& obj) + { + auto ibp = table_.try_emplace(key, std::forward(obj)); + if (ibp.second) { + return ibp; + } + ibp.first->second = std::forward(obj); + return ibp; + } + + template + std::pair insert_or_assign(key_type&& key, M&& obj) + { + auto ibp = table_.try_emplace(std::move(key), std::forward(obj)); + if (ibp.second) { + return ibp; + } + ibp.first->second = std::forward(obj); + return ibp; + } + + template + typename std::enable_if< + boost::unordered::detail::are_transparent::value, + std::pair >::type + insert_or_assign(K&& k, M&& obj) + { + auto ibp = table_.try_emplace(std::forward(k), std::forward(obj)); + if (ibp.second) { + return ibp; + } + ibp.first->second = std::forward(obj); + return ibp; + } + + template + iterator insert_or_assign(const_iterator, key_type const& key, M&& obj) + { + return this->insert_or_assign(key, std::forward(obj)).first; + } + + template + iterator insert_or_assign(const_iterator, key_type&& key, M&& obj) + { + return this->insert_or_assign(std::move(key), std::forward(obj)) + .first; + } + + template + typename std::enable_if< + boost::unordered::detail::are_transparent::value, + iterator>::type + insert_or_assign(const_iterator, K&& k, M&& obj) + { + return this->insert_or_assign(std::forward(k), std::forward(obj)) + .first; + } + + template + BOOST_FORCEINLINE std::pair emplace(Args&&... args) + { + return table_.emplace(std::forward(args)...); + } + + template + BOOST_FORCEINLINE iterator emplace_hint(const_iterator, Args&&... args) + { + return table_.emplace(std::forward(args)...).first; + } + + template + BOOST_FORCEINLINE std::pair try_emplace( + key_type const& key, Args&&... args) + { + return table_.try_emplace(key, std::forward(args)...); + } + + template + BOOST_FORCEINLINE std::pair try_emplace( + key_type&& key, Args&&... args) + { + return table_.try_emplace(std::move(key), std::forward(args)...); + } + + template + BOOST_FORCEINLINE typename std::enable_if< + boost::unordered::detail::transparent_non_iterable::value, + std::pair >::type + try_emplace(K&& key, Args&&... args) + { + return table_.try_emplace( + std::forward(key), std::forward(args)...); + } + + template + BOOST_FORCEINLINE iterator try_emplace( + const_iterator, key_type const& key, Args&&... args) + { + return table_.try_emplace(key, std::forward(args)...).first; + } + + template + BOOST_FORCEINLINE iterator try_emplace( + const_iterator, key_type&& key, Args&&... args) + { + return table_.try_emplace(std::move(key), std::forward(args)...) + .first; + } + + template + BOOST_FORCEINLINE typename std::enable_if< + boost::unordered::detail::transparent_non_iterable::value, + iterator>::type + try_emplace(const_iterator, K&& key, Args&&... args) + { + return table_ + .try_emplace(std::forward(key), std::forward(args)...) + .first; + } + + BOOST_FORCEINLINE void erase(iterator pos) { table_.erase(pos); } + BOOST_FORCEINLINE void erase(const_iterator pos) + { + return table_.erase(pos); + } + iterator erase(const_iterator first, const_iterator last) + { + while (first != last) { + this->erase(first++); + } + return iterator{detail::foa::const_iterator_cast_tag{}, last}; + } + + BOOST_FORCEINLINE size_type erase(key_type const& key) + { + return table_.erase(key); + } + + template + BOOST_FORCEINLINE typename std::enable_if< + detail::transparent_non_iterable::value, + size_type>::type + erase(K const& key) + { + return table_.erase(key); + } + + void swap(unordered_node_map& rhs) noexcept( + noexcept(std::declval().swap(std::declval()))) + { + table_.swap(rhs.table_); + } + + node_type extract(const_iterator pos) + { + BOOST_ASSERT(pos != end()); + node_type nh; + auto elem = table_.extract(pos); + nh.emplace(std::move(elem), get_allocator()); + return nh; + } + + node_type extract(key_type const& key) + { + auto pos = find(key); + return pos != end() ? extract(pos) : node_type(); + } + + template + typename std::enable_if< + boost::unordered::detail::transparent_non_iterable::value, + node_type>::type + extract(K const& key) + { + auto pos = find(key); + return pos != end() ? extract(pos) : node_type(); + } + + template + void merge( + unordered_node_map& + source) + { + table_.merge(source.table_); + } + + template + void merge( + unordered_node_map&& + source) + { + table_.merge(std::move(source.table_)); + } + + /// Lookup + /// + + mapped_type& at(key_type const& key) + { + auto pos = table_.find(key); + if (pos != table_.end()) { + return pos->second; + } + // TODO: someday refactor this to conditionally serialize the key and + // include it in the error message + // + boost::throw_exception( + std::out_of_range("key was not found in unordered_node_map")); + } + + mapped_type const& at(key_type const& key) const + { + auto pos = table_.find(key); + if (pos != table_.end()) { + return pos->second; + } + boost::throw_exception( + std::out_of_range("key was not found in unordered_node_map")); + } + + template + typename std::enable_if< + boost::unordered::detail::are_transparent::value, + mapped_type&>::type + at(K&& key) + { + auto pos = table_.find(std::forward(key)); + if (pos != table_.end()) { + return pos->second; + } + boost::throw_exception( + std::out_of_range("key was not found in unordered_node_map")); + } + + template + typename std::enable_if< + boost::unordered::detail::are_transparent::value, + mapped_type const&>::type + at(K&& key) const + { + auto pos = table_.find(std::forward(key)); + if (pos != table_.end()) { + return pos->second; + } + boost::throw_exception( + std::out_of_range("key was not found in unordered_node_map")); + } + + BOOST_FORCEINLINE mapped_type& operator[](key_type const& key) + { + return table_.try_emplace(key).first->second; + } + + BOOST_FORCEINLINE mapped_type& operator[](key_type&& key) + { + return table_.try_emplace(std::move(key)).first->second; + } + + template + typename std::enable_if< + boost::unordered::detail::are_transparent::value, + mapped_type&>::type + operator[](K&& key) + { + return table_.try_emplace(std::forward(key)).first->second; + } + + BOOST_FORCEINLINE size_type count(key_type const& key) const + { + auto pos = table_.find(key); + return pos != table_.end() ? 1 : 0; + } + + template + BOOST_FORCEINLINE typename std::enable_if< + detail::are_transparent::value, size_type>::type + count(K const& key) const + { + auto pos = table_.find(key); + return pos != table_.end() ? 1 : 0; + } + + BOOST_FORCEINLINE iterator find(key_type const& key) + { + return table_.find(key); + } + + BOOST_FORCEINLINE const_iterator find(key_type const& key) const + { + return table_.find(key); + } + + template + BOOST_FORCEINLINE typename std::enable_if< + boost::unordered::detail::are_transparent::value, + iterator>::type + find(K const& key) + { + return table_.find(key); + } + + template + BOOST_FORCEINLINE typename std::enable_if< + boost::unordered::detail::are_transparent::value, + const_iterator>::type + find(K const& key) const + { + return table_.find(key); + } + + BOOST_FORCEINLINE bool contains(key_type const& key) const + { + return this->find(key) != this->end(); + } + + template + BOOST_FORCEINLINE typename std::enable_if< + boost::unordered::detail::are_transparent::value, + bool>::type + contains(K const& key) const + { + return this->find(key) != this->end(); + } + + std::pair equal_range(key_type const& key) + { + auto pos = table_.find(key); + if (pos == table_.end()) { + return {pos, pos}; + } + + auto next = pos; + ++next; + return {pos, next}; + } + + std::pair equal_range( + key_type const& key) const + { + auto pos = table_.find(key); + if (pos == table_.end()) { + return {pos, pos}; + } + + auto next = pos; + ++next; + return {pos, next}; + } + + template + typename std::enable_if< + detail::are_transparent::value, + std::pair >::type + equal_range(K const& key) + { + auto pos = table_.find(key); + if (pos == table_.end()) { + return {pos, pos}; + } + + auto next = pos; + ++next; + return {pos, next}; + } + + template + typename std::enable_if< + detail::are_transparent::value, + std::pair >::type + equal_range(K const& key) const + { + auto pos = table_.find(key); + if (pos == table_.end()) { + return {pos, pos}; + } + + auto next = pos; + ++next; + return {pos, next}; + } + + /// Hash Policy + /// + + size_type bucket_count() const noexcept { return table_.capacity(); } + + float load_factor() const noexcept { return table_.load_factor(); } + + float max_load_factor() const noexcept + { + return table_.max_load_factor(); + } + + void max_load_factor(float) {} + + size_type max_load() const noexcept { return table_.max_load(); } + + void rehash(size_type n) { table_.rehash(n); } + + void reserve(size_type n) { table_.reserve(n); } + + /// Observers + /// + + hasher hash_function() const { return table_.hash_function(); } + + key_equal key_eq() const { return table_.key_eq(); } + }; + + template + bool operator==( + unordered_node_map const& lhs, + unordered_node_map const& rhs) + { + if (&lhs == &rhs) { + return true; + } + + return (lhs.size() == rhs.size()) && ([&] { + for (auto const& kvp : lhs) { + auto pos = rhs.find(kvp.first); + if ((pos == rhs.end()) || (*pos != kvp)) { + return false; + } + } + return true; + })(); + } + + template + bool operator!=( + unordered_node_map const& lhs, + unordered_node_map const& rhs) + { + return !(lhs == rhs); + } + + template + void swap(unordered_node_map& lhs, + unordered_node_map& rhs) + noexcept(noexcept(lhs.swap(rhs))) + { + lhs.swap(rhs); + } + + template + typename unordered_node_map::size_type + erase_if( + unordered_node_map& map, Pred pred) + { + return erase_if(map.table_, pred); + } + +#if defined(BOOST_MSVC) +#pragma warning(pop) /* C4714 */ +#endif + +#if BOOST_UNORDERED_TEMPLATE_DEDUCTION_GUIDES + + namespace detail { + template + using iter_key_t = + typename std::iterator_traits::value_type::first_type; + template + using iter_val_t = + typename std::iterator_traits::value_type::second_type; + template + using iter_to_alloc_t = + typename std::pair const, iter_val_t >; + } // namespace detail + + template >, + class Pred = + std::equal_to >, + class Allocator = std::allocator< + boost::unordered::detail::iter_to_alloc_t >, + class = boost::enable_if_t >, + class = boost::enable_if_t >, + class = boost::enable_if_t >, + class = boost::enable_if_t > > + unordered_node_map(InputIterator, InputIterator, + std::size_t = boost::unordered::detail::foa::default_bucket_count, + Hash = Hash(), Pred = Pred(), Allocator = Allocator()) + -> unordered_node_map, + boost::unordered::detail::iter_val_t, Hash, Pred, + Allocator>; + + template >, + class Pred = std::equal_to >, + class Allocator = std::allocator >, + class = boost::enable_if_t >, + class = boost::enable_if_t >, + class = boost::enable_if_t > > + unordered_node_map(std::initializer_list >, + std::size_t = boost::unordered::detail::foa::default_bucket_count, + Hash = Hash(), Pred = Pred(), Allocator = Allocator()) + -> unordered_node_map, T, Hash, Pred, + Allocator>; + + template >, + class = boost::enable_if_t > > + unordered_node_map(InputIterator, InputIterator, std::size_t, Allocator) + -> unordered_node_map, + boost::unordered::detail::iter_val_t, + boost::hash >, + std::equal_to >, + Allocator>; + + template >, + class = boost::enable_if_t > > + unordered_node_map(InputIterator, InputIterator, Allocator) + -> unordered_node_map, + boost::unordered::detail::iter_val_t, + boost::hash >, + std::equal_to >, + Allocator>; + + template >, + class = boost::enable_if_t >, + class = boost::enable_if_t > > + unordered_node_map( + InputIterator, InputIterator, std::size_t, Hash, Allocator) + -> unordered_node_map, + boost::unordered::detail::iter_val_t, Hash, + std::equal_to >, + Allocator>; + + template > > + unordered_node_map(std::initializer_list >, std::size_t, + Allocator) -> unordered_node_map, T, + boost::hash >, + std::equal_to >, Allocator>; + + template > > + unordered_node_map(std::initializer_list >, Allocator) + -> unordered_node_map, T, + boost::hash >, + std::equal_to >, Allocator>; + + template >, + class = boost::enable_if_t > > + unordered_node_map(std::initializer_list >, std::size_t, + Hash, Allocator) -> unordered_node_map, T, + Hash, std::equal_to >, Allocator>; +#endif + + } // namespace unordered +} // namespace boost + +#endif diff --git a/include/boost/unordered/unordered_node_map_fwd.hpp b/include/boost/unordered/unordered_node_map_fwd.hpp new file mode 100644 index 00000000..0e08a905 --- /dev/null +++ b/include/boost/unordered/unordered_node_map_fwd.hpp @@ -0,0 +1,49 @@ + +// Copyright (C) 2022 Christian Mazakas +// 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) + +#ifndef BOOST_UNORDERED_NODE_MAP_FWD_HPP_INCLUDED +#define BOOST_UNORDERED_NODE_MAP_FWD_HPP_INCLUDED + +#include +#if defined(BOOST_HAS_PRAGMA_ONCE) +#pragma once +#endif + +#include +#include +#include +#include + +namespace boost { + namespace unordered { + template , + class KeyEqual = std::equal_to, + class Allocator = std::allocator > > + class unordered_node_map; + + template + bool operator==( + unordered_node_map const& lhs, + unordered_node_map const& rhs); + + template + bool operator!=( + unordered_node_map const& lhs, + unordered_node_map const& rhs); + + template + void swap(unordered_node_map& lhs, + unordered_node_map& rhs) + noexcept(noexcept(lhs.swap(rhs))); + } // namespace unordered + + using boost::unordered::unordered_node_map; + + using boost::unordered::swap; + using boost::unordered::operator==; + using boost::unordered::operator!=; +} // namespace boost + +#endif diff --git a/include/boost/unordered/unordered_node_set.hpp b/include/boost/unordered/unordered_node_set.hpp new file mode 100644 index 00000000..c6fc8617 --- /dev/null +++ b/include/boost/unordered/unordered_node_set.hpp @@ -0,0 +1,780 @@ +// Copyright (C) 2022 Christian Mazakas +// 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) + +#ifndef BOOST_UNORDERED_UNORDERED_NODE_SET_HPP_INCLUDED +#define BOOST_UNORDERED_UNORDERED_NODE_SET_HPP_INCLUDED + +#include +#if defined(BOOST_HAS_PRAGMA_ONCE) +#pragma once +#endif + +#include +#include +#include +#include + +#include +#include +#include + +#include +#include +#include +#include + +namespace boost { + namespace unordered { + +#if defined(BOOST_MSVC) +#pragma warning(push) +#pragma warning(disable : 4714) /* marked as __forceinline not inlined */ +#endif + + namespace detail { + template struct node_set_types + { + using key_type = Key; + using init_type = Key; + using value_type = Key; + + static Key const& extract(value_type const& key) { return key; } + + struct element_type + { + value_type* p; + + /* + * we use a deleted copy constructor here so the type is no longer + * trivially copy-constructible which inhibits our memcpy + * optimizations when copying the tables + */ + element_type() = default; + element_type(element_type const&) = delete; + element_type(element_type&& rhs) noexcept + { + p = rhs.p; + rhs.p = nullptr; + } + }; + + static value_type& value_from(element_type const& x) { return *x.p; } + static Key const& extract(element_type const& k) { return *k.p; } + static element_type&& move(element_type& x) { return std::move(x); } + static value_type&& move(value_type& x) { return std::move(x); } + + template + static void construct(A& al, element_type* p, element_type const& copy) + { + construct(al, p, *copy.p); + } + + template + static void construct( + Allocator&, element_type* p, element_type&& x) noexcept + { + p->p = x.p; + x.p = nullptr; + } + + template + static void construct(A& al, element_type* p, Args&&... args) + { + p->p = boost::to_address(boost::allocator_allocate(al, 1)); + BOOST_TRY + { + boost::allocator_construct(al, p->p, std::forward(args)...); + } + BOOST_CATCH(...) + { + boost::allocator_deallocate(al, + boost::pointer_traits< + typename boost::allocator_pointer::type>::pointer_to(*p->p), + 1); + BOOST_RETHROW + } + BOOST_CATCH_END + } + + template static void destroy(A& al, value_type* p) noexcept + { + boost::allocator_destroy(al, p); + boost::allocator_deallocate(al, + boost::pointer_traits< + typename boost::allocator_pointer::type>::pointer_to(*p), + 1); + } + + template static void destroy(A& al, element_type* p) noexcept + { + if (p->p) { + destroy(al, p->p); + } + } + }; + + template + struct node_set_handle + : public detail::foa::node_handle_base + { + private: + using base_type = + detail::foa::node_handle_base; + + using typename base_type::type_policy; + + template + friend class boost::unordered::unordered_node_set; + + public: + using value_type = typename TypePolicy::value_type; + + constexpr node_set_handle() noexcept = default; + node_set_handle(node_set_handle&& nh) noexcept = default; + node_set_handle& operator=(node_set_handle&&) noexcept = default; + + value_type& value() const + { + BOOST_ASSERT(!this->empty()); + return const_cast(this->element()); + } + }; + } // namespace detail + + template + class unordered_node_set + { + using set_types = detail::node_set_types; + + using table_type = detail::foa::table::type>; + + table_type table_; + + template + typename unordered_node_set::size_type friend erase_if( + unordered_node_set& set, Pred pred); + + public: + using key_type = Key; + using value_type = typename set_types::value_type; + using init_type = typename set_types::init_type; + using size_type = std::size_t; + using difference_type = std::ptrdiff_t; + using hasher = Hash; + using key_equal = KeyEqual; + using allocator_type = Allocator; + using reference = value_type&; + using const_reference = value_type const&; + using pointer = typename boost::allocator_pointer::type; + using const_pointer = + typename boost::allocator_const_pointer::type; + using iterator = typename table_type::iterator; + using const_iterator = typename table_type::const_iterator; + using node_type = detail::node_set_handle::type>; + using insert_return_type = + detail::foa::insert_return_type; + + unordered_node_set() : unordered_node_set(0) {} + + explicit unordered_node_set(size_type n, hasher const& h = hasher(), + key_equal const& pred = key_equal(), + allocator_type const& a = allocator_type()) + : table_(n, h, pred, a) + { + } + + unordered_node_set(size_type n, allocator_type const& a) + : unordered_node_set(n, hasher(), key_equal(), a) + { + } + + unordered_node_set(size_type n, hasher const& h, allocator_type const& a) + : unordered_node_set(n, h, key_equal(), a) + { + } + + template + unordered_node_set( + InputIterator f, InputIterator l, allocator_type const& a) + : unordered_node_set(f, l, size_type(0), hasher(), key_equal(), a) + { + } + + explicit unordered_node_set(allocator_type const& a) + : unordered_node_set(0, a) + { + } + + template + unordered_node_set(Iterator first, Iterator last, size_type n = 0, + hasher const& h = hasher(), key_equal const& pred = key_equal(), + allocator_type const& a = allocator_type()) + : unordered_node_set(n, h, pred, a) + { + this->insert(first, last); + } + + template + unordered_node_set( + InputIt first, InputIt last, size_type n, allocator_type const& a) + : unordered_node_set(first, last, n, hasher(), key_equal(), a) + { + } + + template + unordered_node_set(Iterator first, Iterator last, size_type n, + hasher const& h, allocator_type const& a) + : unordered_node_set(first, last, n, h, key_equal(), a) + { + } + + unordered_node_set(unordered_node_set const& other) : table_(other.table_) + { + } + + unordered_node_set( + unordered_node_set const& other, allocator_type const& a) + : table_(other.table_, a) + { + } + + unordered_node_set(unordered_node_set&& other) + noexcept(std::is_nothrow_move_constructible::value&& + std::is_nothrow_move_constructible::value&& + std::is_nothrow_move_constructible::value) + : table_(std::move(other.table_)) + { + } + + unordered_node_set(unordered_node_set&& other, allocator_type const& al) + : table_(std::move(other.table_), al) + { + } + + unordered_node_set(std::initializer_list ilist, + size_type n = 0, hasher const& h = hasher(), + key_equal const& pred = key_equal(), + allocator_type const& a = allocator_type()) + : unordered_node_set(ilist.begin(), ilist.end(), n, h, pred, a) + { + } + + unordered_node_set( + std::initializer_list il, allocator_type const& a) + : unordered_node_set(il, size_type(0), hasher(), key_equal(), a) + { + } + + unordered_node_set(std::initializer_list init, size_type n, + allocator_type const& a) + : unordered_node_set(init, n, hasher(), key_equal(), a) + { + } + + unordered_node_set(std::initializer_list init, size_type n, + hasher const& h, allocator_type const& a) + : unordered_node_set(init, n, h, key_equal(), a) + { + } + + ~unordered_node_set() = default; + + unordered_node_set& operator=(unordered_node_set const& other) + { + table_ = other.table_; + return *this; + } + + unordered_node_set& operator=(unordered_node_set&& other) noexcept( + noexcept(std::declval() = std::declval())) + { + table_ = std::move(other.table_); + return *this; + } + + allocator_type get_allocator() const noexcept + { + return table_.get_allocator(); + } + + /// Iterators + /// + + iterator begin() noexcept { return table_.begin(); } + const_iterator begin() const noexcept { return table_.begin(); } + const_iterator cbegin() const noexcept { return table_.cbegin(); } + + iterator end() noexcept { return table_.end(); } + const_iterator end() const noexcept { return table_.end(); } + const_iterator cend() const noexcept { return table_.cend(); } + + /// Capacity + /// + + BOOST_ATTRIBUTE_NODISCARD bool empty() const noexcept + { + return table_.empty(); + } + + size_type size() const noexcept { return table_.size(); } + + size_type max_size() const noexcept { return table_.max_size(); } + + /// Modifiers + /// + + void clear() noexcept { table_.clear(); } + + BOOST_FORCEINLINE std::pair insert( + value_type const& value) + { + return table_.insert(value); + } + + BOOST_FORCEINLINE std::pair insert(value_type&& value) + { + return table_.insert(std::move(value)); + } + + template + BOOST_FORCEINLINE typename std::enable_if< + detail::transparent_non_iterable::value, + std::pair >::type + insert(K&& k) + { + return table_.try_emplace(std::forward(k)); + } + + BOOST_FORCEINLINE iterator insert(const_iterator, value_type const& value) + { + return table_.insert(value).first; + } + + BOOST_FORCEINLINE iterator insert(const_iterator, value_type&& value) + { + return table_.insert(std::move(value)).first; + } + + template + BOOST_FORCEINLINE typename std::enable_if< + detail::transparent_non_iterable::value, + iterator>::type + insert(const_iterator, K&& k) + { + return table_.try_emplace(std::forward(k)).first; + } + + template + void insert(InputIterator first, InputIterator last) + { + for (auto pos = first; pos != last; ++pos) { + table_.emplace(*pos); + } + } + + void insert(std::initializer_list ilist) + { + this->insert(ilist.begin(), ilist.end()); + } + + insert_return_type insert(node_type&& nh) + { + if (nh.empty()) { + return {end(), false, node_type{}}; + } + + BOOST_ASSERT(get_allocator() == nh.get_allocator()); + + typename set_types::element_type x; + x.p=std::addressof(nh.element()); + + auto itp = table_.insert(std::move(x)); + if (itp.second) { + nh.reset(); + return {itp.first, true, node_type{}}; + } else { + return {itp.first, false, std::move(nh)}; + } + } + + iterator insert(const_iterator, node_type&& nh) + { + if (nh.empty()) { + return end(); + } + + BOOST_ASSERT(get_allocator() == nh.get_allocator()); + + typename set_types::element_type x; + x.p=std::addressof(nh.element()); + + auto itp = table_.insert(std::move(x)); + if (itp.second) { + nh.reset(); + return itp.first; + } else { + return itp.first; + } + } + + template + BOOST_FORCEINLINE std::pair emplace(Args&&... args) + { + return table_.emplace(std::forward(args)...); + } + + template + BOOST_FORCEINLINE iterator emplace_hint(const_iterator, Args&&... args) + { + return table_.emplace(std::forward(args)...).first; + } + + BOOST_FORCEINLINE void erase(const_iterator pos) + { + return table_.erase(pos); + } + iterator erase(const_iterator first, const_iterator last) + { + while (first != last) { + this->erase(first++); + } + return iterator{detail::foa::const_iterator_cast_tag{}, last}; + } + + BOOST_FORCEINLINE size_type erase(key_type const& key) + { + return table_.erase(key); + } + + template + BOOST_FORCEINLINE typename std::enable_if< + detail::transparent_non_iterable::value, + size_type>::type + erase(K const& key) + { + return table_.erase(key); + } + + void swap(unordered_node_set& rhs) noexcept( + noexcept(std::declval().swap(std::declval()))) + { + table_.swap(rhs.table_); + } + + node_type extract(const_iterator pos) + { + BOOST_ASSERT(pos != end()); + node_type nh; + auto elem = table_.extract(pos); + nh.emplace(std::move(elem), get_allocator()); + return nh; + } + + node_type extract(key_type const& key) + { + auto pos = find(key); + return pos!=end()?extract(pos):node_type(); + } + + template + typename std::enable_if< + boost::unordered::detail::transparent_non_iterable::value, + node_type>::type + extract(K const& key) + { + auto pos = find(key); + return pos!=end()?extract(pos):node_type(); + } + + template + void merge(unordered_node_set& source) + { + table_.merge(source.table_); + } + + template + void merge(unordered_node_set&& source) + { + table_.merge(std::move(source.table_)); + } + + /// Lookup + /// + + BOOST_FORCEINLINE size_type count(key_type const& key) const + { + auto pos = table_.find(key); + return pos != table_.end() ? 1 : 0; + } + + template + BOOST_FORCEINLINE typename std::enable_if< + detail::are_transparent::value, size_type>::type + count(K const& key) const + { + auto pos = table_.find(key); + return pos != table_.end() ? 1 : 0; + } + + BOOST_FORCEINLINE iterator find(key_type const& key) + { + return table_.find(key); + } + + BOOST_FORCEINLINE const_iterator find(key_type const& key) const + { + return table_.find(key); + } + + template + BOOST_FORCEINLINE typename std::enable_if< + boost::unordered::detail::are_transparent::value, + iterator>::type + find(K const& key) + { + return table_.find(key); + } + + template + BOOST_FORCEINLINE typename std::enable_if< + boost::unordered::detail::are_transparent::value, + const_iterator>::type + find(K const& key) const + { + return table_.find(key); + } + + BOOST_FORCEINLINE bool contains(key_type const& key) const + { + return this->find(key) != this->end(); + } + + template + BOOST_FORCEINLINE typename std::enable_if< + boost::unordered::detail::are_transparent::value, + bool>::type + contains(K const& key) const + { + return this->find(key) != this->end(); + } + + std::pair equal_range(key_type const& key) + { + auto pos = table_.find(key); + if (pos == table_.end()) { + return {pos, pos}; + } + + auto next = pos; + ++next; + return {pos, next}; + } + + std::pair equal_range( + key_type const& key) const + { + auto pos = table_.find(key); + if (pos == table_.end()) { + return {pos, pos}; + } + + auto next = pos; + ++next; + return {pos, next}; + } + + template + typename std::enable_if< + detail::are_transparent::value, + std::pair >::type + equal_range(K const& key) + { + auto pos = table_.find(key); + if (pos == table_.end()) { + return {pos, pos}; + } + + auto next = pos; + ++next; + return {pos, next}; + } + + template + typename std::enable_if< + detail::are_transparent::value, + std::pair >::type + equal_range(K const& key) const + { + auto pos = table_.find(key); + if (pos == table_.end()) { + return {pos, pos}; + } + + auto next = pos; + ++next; + return {pos, next}; + } + + /// Hash Policy + /// + + size_type bucket_count() const noexcept { return table_.capacity(); } + + float load_factor() const noexcept { return table_.load_factor(); } + + float max_load_factor() const noexcept + { + return table_.max_load_factor(); + } + + void max_load_factor(float) {} + + size_type max_load() const noexcept { return table_.max_load(); } + + void rehash(size_type n) { table_.rehash(n); } + + void reserve(size_type n) { table_.reserve(n); } + + /// Observers + /// + + hasher hash_function() const { return table_.hash_function(); } + + key_equal key_eq() const { return table_.key_eq(); } + }; + + template + bool operator==( + unordered_node_set const& lhs, + unordered_node_set const& rhs) + { + if (&lhs == &rhs) { + return true; + } + + return (lhs.size() == rhs.size()) && ([&] { + for (auto const& key : lhs) { + auto pos = rhs.find(key); + if ((pos == rhs.end()) || (key != *pos)) { + return false; + } + } + return true; + })(); + } + + template + bool operator!=( + unordered_node_set const& lhs, + unordered_node_set const& rhs) + { + return !(lhs == rhs); + } + + template + void swap(unordered_node_set& lhs, + unordered_node_set& rhs) + noexcept(noexcept(lhs.swap(rhs))) + { + lhs.swap(rhs); + } + + template + typename unordered_node_set::size_type + erase_if(unordered_node_set& set, Pred pred) + { + return erase_if(set.table_, pred); + } + +#if defined(BOOST_MSVC) +#pragma warning(pop) /* C4714 */ +#endif + +#if BOOST_UNORDERED_TEMPLATE_DEDUCTION_GUIDES + template ::value_type>, + class Pred = + std::equal_to::value_type>, + class Allocator = std::allocator< + typename std::iterator_traits::value_type>, + class = boost::enable_if_t >, + class = boost::enable_if_t >, + class = boost::enable_if_t >, + class = boost::enable_if_t > > + unordered_node_set(InputIterator, InputIterator, + std::size_t = boost::unordered::detail::foa::default_bucket_count, + Hash = Hash(), Pred = Pred(), Allocator = Allocator()) + -> unordered_node_set< + typename std::iterator_traits::value_type, Hash, Pred, + Allocator>; + + template , + class Pred = std::equal_to, class Allocator = std::allocator, + class = boost::enable_if_t >, + class = boost::enable_if_t >, + class = boost::enable_if_t > > + unordered_node_set(std::initializer_list, + std::size_t = boost::unordered::detail::foa::default_bucket_count, + Hash = Hash(), Pred = Pred(), Allocator = Allocator()) + -> unordered_node_set; + + template >, + class = boost::enable_if_t > > + unordered_node_set(InputIterator, InputIterator, std::size_t, Allocator) + -> unordered_node_set< + typename std::iterator_traits::value_type, + boost::hash::value_type>, + std::equal_to::value_type>, + Allocator>; + + template >, + class = boost::enable_if_t >, + class = boost::enable_if_t > > + unordered_node_set( + InputIterator, InputIterator, std::size_t, Hash, Allocator) + -> unordered_node_set< + typename std::iterator_traits::value_type, Hash, + std::equal_to::value_type>, + Allocator>; + + template > > + unordered_node_set(std::initializer_list, std::size_t, Allocator) + -> unordered_node_set, std::equal_to, Allocator>; + + template >, + class = boost::enable_if_t > > + unordered_node_set(std::initializer_list, std::size_t, Hash, Allocator) + -> unordered_node_set, Allocator>; + + template >, + class = boost::enable_if_t > > + unordered_node_set(InputIterator, InputIterator, Allocator) + -> unordered_node_set< + typename std::iterator_traits::value_type, + boost::hash::value_type>, + std::equal_to::value_type>, + Allocator>; + + template > > + unordered_node_set(std::initializer_list, Allocator) + -> unordered_node_set, std::equal_to, Allocator>; +#endif + + } // namespace unordered +} // namespace boost + +#endif diff --git a/include/boost/unordered/unordered_node_set_fwd.hpp b/include/boost/unordered/unordered_node_set_fwd.hpp new file mode 100644 index 00000000..bdd7fd0b --- /dev/null +++ b/include/boost/unordered/unordered_node_set_fwd.hpp @@ -0,0 +1,49 @@ + +// Copyright (C) 2023 Christian Mazakas +// 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) + +#ifndef BOOST_UNORDERED_NODE_SET_FWD_HPP_INCLUDED +#define BOOST_UNORDERED_NODE_SET_FWD_HPP_INCLUDED + +#include +#if defined(BOOST_HAS_PRAGMA_ONCE) +#pragma once +#endif + +#include +#include +#include +#include + +namespace boost { + namespace unordered { + template , + class KeyEqual = std::equal_to, + class Allocator = std::allocator > + class unordered_node_set; + + template + bool operator==( + unordered_node_set const& lhs, + unordered_node_set const& rhs); + + template + bool operator!=( + unordered_node_set const& lhs, + unordered_node_set const& rhs); + + template + void swap(unordered_node_set& lhs, + unordered_node_set& rhs) + noexcept(noexcept(lhs.swap(rhs))); + } // namespace unordered + + using boost::unordered::unordered_node_set; + + using boost::unordered::swap; + using boost::unordered::operator==; + using boost::unordered::operator!=; +} // namespace boost + +#endif diff --git a/test/Jamfile.v2 b/test/Jamfile.v2 index 9a702bc6..4f7a537f 100644 --- a/test/Jamfile.v2 +++ b/test/Jamfile.v2 @@ -1,6 +1,6 @@ # Copyright 2006-2008 Daniel James. -# Copyright 2022 Christian Mazakas +# Copyright 2022-2023 Christian Mazakas # 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) @@ -26,6 +26,7 @@ project gcc-4.4:-Wno-strict-aliasing gcc-4.4:-fno-deduce-init-list + clang-14:-Wunused-template gcc:on clang:on @@ -94,6 +95,7 @@ run exception/erase_exception_tests.cpp ; run exception/rehash_exception_tests.cpp ; run exception/swap_exception_tests.cpp : : : BOOST_UNORDERED_SWAP_METHOD=2 ; run exception/merge_exception_tests.cpp ; +run exception/less_tests.cpp ; run unordered/narrow_cast_tests.cpp ; run quick.cpp ; @@ -140,6 +142,8 @@ build_foa erase_if ; build_foa scary_tests ; build_foa init_type_insert_tests ; build_foa max_load_tests ; +build_foa extract_tests ; +build_foa node_handle_tests ; run unordered/hash_is_avalanching_test.cpp ; diff --git a/test/exception/containers.hpp b/test/exception/containers.hpp index e6eb267b..390207a8 100644 --- a/test/exception/containers.hpp +++ b/test/exception/containers.hpp @@ -25,8 +25,24 @@ typedef boost::unordered_flat_set< test::exception::allocator > test_pair_set; -#define CONTAINER_SEQ (test_set)(test_map) -#define CONTAINER_PAIR_SEQ (test_pair_set)(test_map) +typedef boost::unordered_node_set > + test_node_set; + +typedef boost::unordered_node_map > + test_node_map; + +typedef boost::unordered_node_set< + std::pair, + test::exception::hash, test::exception::equal_to, + test::exception::allocator > + test_pair_node_set; + +#define CONTAINER_SEQ (test_set)(test_map)(test_node_set)(test_node_map) +#define CONTAINER_PAIR_SEQ (test_pair_set)(test_map)(test_pair_node_set)(test_node_map) #else typedef boost::unordered_set + +UNORDERED_AUTO_TEST (less_osx_regression) { + DISABLE_EXCEPTIONS; + typedef test_pair_set::value_type value_type; + typedef test::exception::object object; + + std::vector v; + v.push_back(value_type(object(12, 98), object(88, 13))); + v.push_back(value_type(object(24, 71), object(62, 84))); + v.push_back(value_type(object(30, 0), object(5, 73))); + v.push_back(value_type(object(34, 64), object(79, 58))); + v.push_back(value_type(object(36, 95), object(64, 23))); + v.push_back(value_type(object(42, 89), object(68, 44))); + v.push_back(value_type(object(42, 26), object(93, 64))); + v.push_back(value_type(object(86, 86), object(16, 62))); + v.push_back(value_type(object(86, 86), object(75, 23))); + v.push_back(value_type(object(92, 37), object(41, 90))); + + BOOST_TEST_EQ(v.size(), 10u); + + std::set s; + s.insert(v.begin(), v.end()); + BOOST_TEST_EQ(s.size(), v.size()); + + test::ordered tracker; + test_pair_set x; + for (std::vector::iterator it = v.begin(); it != v.end(); + ++it) { + x.insert(*it); + } + + tracker.insert(v.begin(), v.end()); + tracker.compare(x); +} + +RUN_TESTS() diff --git a/test/exception/merge_exception_tests.cpp b/test/exception/merge_exception_tests.cpp index e824fdde..6101e0bb 100644 --- a/test/exception/merge_exception_tests.cpp +++ b/test/exception/merge_exception_tests.cpp @@ -1,6 +1,6 @@ // Copyright 2017-2018 Daniel James. -// Copyright 2022 Christian Mazakas. +// Copyright 2022-2023 Christian Mazakas. // 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) @@ -64,6 +64,12 @@ boost::unordered_flat_set >* test_map_; +boost::unordered_node_set >* test_node_set_; +boost::unordered_node_map >* test_node_map_; // clang-format off UNORDERED_MULTI_TEST(set_merge, merge_exception_test, @@ -99,6 +105,40 @@ UNORDERED_MULTI_TEST(map_merge_collisions, merge_exception_test, ((generate_collisions)) ((generate_collisions)) ) +UNORDERED_MULTI_TEST(node_set_merge, merge_exception_test, + ((test_node_set_)) + ((test_node_set_)) + (/* (0x0000)(0x6400) */(0x0064)/* (0x0a64)(0x3232) */) + ((0x0000)(0x0001)(0x0102)) + ((default_generator)(limited_range)) + ((default_generator)(limited_range)) +) +UNORDERED_MULTI_TEST(node_map_merge, merge_exception_test, + ((test_node_map_)) + ((test_node_map_)) + ((0x0000)(0x6400)(0x0064)(0x0a64)(0x3232)) + ((0x0101)(0x0200)(0x0201)) + ((default_generator)(limited_range)) + ((default_generator)(limited_range)) +) +// Run fewer generate_collisions tests, as they're slow. +UNORDERED_MULTI_TEST(node_set_merge_collisions, merge_exception_test, + ((test_node_set_)) + ((test_node_set_)) + ((0x0a0a)) + ((0x0202)(0x0100)(0x0201)) + ((generate_collisions)) + ((generate_collisions)) +) +UNORDERED_MULTI_TEST(node_map_merge_collisions, merge_exception_test, + ((test_node_map_)) + ((test_node_map_)) + ((0x0a0a)) + ((0x0000)(0x0002)(0x0102)) + ((generate_collisions)) + ((generate_collisions)) +) +// clang-format on #else boost::unordered_set, std::equal_to, test::allocator1 >; using unordered_flat_map = boost::unordered_flat_map, std::equal_to, test::allocator1 > >; +using unordered_node_set = boost::unordered_node_set, + std::equal_to, test::allocator1 >; +using unordered_node_map = boost::unordered_node_map, + std::equal_to, test::allocator1 > >; -#define SWAP_CONTAINER_SEQ (unordered_flat_set)(unordered_flat_map) +#define SWAP_CONTAINER_SEQ \ + (unordered_flat_set)(unordered_flat_map) \ + (unordered_node_set)(unordered_node_map) #else diff --git a/test/helpers/tracker.hpp b/test/helpers/tracker.hpp index bc3d4b47..9c877459 100644 --- a/test/helpers/tracker.hpp +++ b/test/helpers/tracker.hpp @@ -42,9 +42,8 @@ namespace test { value_list values2(x2.begin(), x2.end()); values1.sort(); values2.sort(); - BOOST_TEST(values1.size() == values2.size() && - test::equal(values1.begin(), values1.end(), values2.begin(), - test::equivalent)); + BOOST_TEST_ALL_WITH(values1.begin(), values1.end(), values2.begin(), + values2.end(), test::equivalent); } template diff --git a/test/helpers/unordered.hpp b/test/helpers/unordered.hpp index a3af9452..d3cbf17f 100644 --- a/test/helpers/unordered.hpp +++ b/test/helpers/unordered.hpp @@ -11,6 +11,8 @@ #ifdef BOOST_UNORDERED_FOA_TESTS #include #include +#include +#include #include #else #include diff --git a/test/objects/exception.hpp b/test/objects/exception.hpp index 68595eab..61a4be10 100644 --- a/test/objects/exception.hpp +++ b/test/objects/exception.hpp @@ -262,7 +262,7 @@ namespace test { if (less_impl(x1.first, x2.first)) { return true; } - if (!less_impl(x1.first, x2.first)) { + if (less_impl(x2.first, x1.first)) { return false; } return less_impl(x1.second, x2.second); diff --git a/test/unordered/assign_tests.cpp b/test/unordered/assign_tests.cpp index b000e42f..b45efa01 100644 --- a/test/unordered/assign_tests.cpp +++ b/test/unordered/assign_tests.cpp @@ -1,6 +1,6 @@ // Copyright 2006-2009 Daniel James. -// Copyright 2022 Christian Mazakas. +// Copyright 2022-2023 Christian Mazakas. // 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) @@ -209,40 +209,75 @@ namespace assign_tests { #ifdef BOOST_UNORDERED_FOA_TESTS boost::unordered_flat_map >* test_map_std_alloc; + boost::unordered_node_map >* test_node_map_std_alloc; boost::unordered_flat_set >* test_set; + boost::unordered_node_set >* test_node_set; boost::unordered_flat_map >* test_map; + boost::unordered_node_map >* test_node_map; boost::unordered_flat_set >* test_set_prop_assign; + boost::unordered_node_set >* + test_node_set_prop_assign; boost::unordered_flat_map >* test_map_prop_assign; + boost::unordered_node_map >* + test_node_map_prop_assign; boost::unordered_flat_set >* test_set_no_prop_assign; + boost::unordered_node_set >* + test_node_set_no_prop_assign; boost::unordered_flat_map >* test_map_no_prop_assign; + boost::unordered_node_map >* + test_node_map_no_prop_assign; UNORDERED_AUTO_TEST (check_traits) { BOOST_TEST(!is_propagate(test_set)); BOOST_TEST(is_propagate(test_set_prop_assign)); BOOST_TEST(!is_propagate(test_set_no_prop_assign)); + + BOOST_TEST(!is_propagate(test_node_set)); + BOOST_TEST(is_propagate(test_node_set_prop_assign)); + BOOST_TEST(!is_propagate(test_node_set_no_prop_assign)); } UNORDERED_TEST(assign_tests1, - ((test_map_std_alloc)(test_set)(test_map)(test_set_prop_assign)(test_map_prop_assign)(test_set_no_prop_assign)(test_map_no_prop_assign))( + ((test_map_std_alloc)(test_node_map_std_alloc) + (test_set)(test_node_set) + (test_map)(test_node_map) + (test_set_prop_assign)(test_node_set_prop_assign) + (test_map_prop_assign)(test_node_map_prop_assign) + (test_set_no_prop_assign)(test_node_set_no_prop_assign) + (test_map_no_prop_assign)(test_node_map_no_prop_assign))( (default_generator)(generate_collisions)(limited_range))) UNORDERED_TEST(assign_tests2, - ((test_set)(test_map)(test_set_prop_assign)(test_map_prop_assign)(test_set_no_prop_assign)(test_map_no_prop_assign))( + ((test_set)(test_node_set) + (test_map)(test_node_map) + (test_set_prop_assign)(test_node_set_prop_assign) + (test_map_prop_assign)(test_node_map_prop_assign) + (test_set_no_prop_assign)(test_node_set_no_prop_assign) + (test_map_no_prop_assign)(test_node_map_no_prop_assign))( (default_generator)(generate_collisions)(limited_range))) #else boost::unordered_map > init; #ifdef BOOST_UNORDERED_FOA_TESTS boost::unordered_flat_map x1; + boost::unordered_node_map x2; + x2[25] = 3; + x2[16] = 10; + BOOST_TEST(!x2.empty()); + x2 = init; + BOOST_TEST(x2.empty()); #else boost::unordered_map x1; #endif @@ -333,6 +374,12 @@ namespace assign_tests { #ifdef BOOST_UNORDERED_FOA_TESTS boost::unordered_flat_set x; + boost::unordered_node_set y; + y.insert(10); + y.insert(20); + y = {1, 2, -10}; + BOOST_TEST(y.find(10) == y.end()); + BOOST_TEST(y.find(-10) != y.end()); #else boost::unordered_set x; #endif diff --git a/test/unordered/at_tests.cpp b/test/unordered/at_tests.cpp index b06dfddb..eddf1789 100644 --- a/test/unordered/at_tests.cpp +++ b/test/unordered/at_tests.cpp @@ -1,6 +1,6 @@ // Copyright 2007-2009 Daniel James. -// Copyright 2022 Christian Mazakas. +// Copyright 2022-2023 Christian Mazakas. // 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) @@ -11,16 +11,12 @@ namespace at_tests { - UNORDERED_AUTO_TEST (at_tests) { + template static void at_tests(X*) + { BOOST_LIGHTWEIGHT_TEST_OSTREAM << "Create Map" << std::endl; -#ifdef BOOST_UNORDERED_FOA_TESTS - boost::unordered_flat_map x; - boost::unordered_flat_map const& x_const(x); -#else - boost::unordered_map x; - boost::unordered_map const& x_const(x); -#endif + X x; + X const& x_const(x); BOOST_LIGHTWEIGHT_TEST_OSTREAM << "Check empty container" << std::endl; @@ -64,6 +60,21 @@ namespace at_tests { BOOST_LIGHTWEIGHT_TEST_OSTREAM << "Finished" << std::endl; } -} + +#ifdef BOOST_UNORDERED_FOA_TESTS + static boost::unordered_flat_map* test_map; + static boost::unordered_node_map* test_node_map; + + // clang-format off + UNORDERED_TEST(at_tests, ((test_map)(test_node_map))) + // clang-format on +#else + static boost::unordered_map* test_map; + + // clang-format off + UNORDERED_TEST(at_tests, ((test_map))) + // clang-format on +#endif +} // namespace at_tests RUN_TESTS() diff --git a/test/unordered/compile_map.cpp b/test/unordered/compile_map.cpp index c4c8d7ce..b929ced8 100644 --- a/test/unordered/compile_map.cpp +++ b/test/unordered/compile_map.cpp @@ -1,6 +1,6 @@ // Copyright 2006-2009 Daniel James. -// Copyright 2022 Christian Mazakas. +// Copyright 2022-2023 Christian Mazakas. // 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) @@ -35,6 +35,22 @@ template class instantiate_flat_map, test::minimal::allocator >; +template +class instantiate_node_map +{ + typedef boost::unordered_node_map container; + container x; +}; + +template class instantiate_node_map, + std::equal_to, test::minimal::allocator >; + +template class instantiate_node_map, + test::minimal::equal_to, + test::minimal::allocator >; + #else #define INSTANTIATE(type) \ template class boost::unordered::detail::instantiate_##type @@ -55,149 +71,101 @@ INSTANTIATE(multimap) >; #endif -UNORDERED_AUTO_TEST (test0) { +template