////////////////////////////////////////////////////////////////////////////// // // (C) Copyright Joaquin M Lopez Munoz 2025-2026. // (C) Copyright Ion Gaztanaga 2026. 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 http://www.boost.org/libs/container for documentation. // ////////////////////////////////////////////////////////////////////////////// #ifndef BOOST_CONTAINER_HUB_HPP #define BOOST_CONTAINER_HUB_HPP #ifndef BOOST_CONFIG_HPP # include #endif #if defined(BOOST_HAS_PRAGMA_ONCE) # pragma once #endif #include #include //container #include #include #include //from_range_t / from_range #include #include #include #include #include #include //default allocator when Allocator = void #include #include //dtl::addressof (avoids ) #include //portable (over)aligned nothrow alloc #include //forward declares std::allocator #include #include #include //movelib::unique_ptr (avoids ) #include #include #include #include #include #include #include #include #ifndef BOOST_NO_CXX17_HDR_MEMORY_RESOURCE #include #endif #ifndef BOOST_CONTAINER_DOXYGEN_INVOKED #if defined(BOOST_NO_CXX20_HDR_CONCEPTS) || defined(BOOST_NO_CXX20_HDR_RANGES) #define BOOST_CONTAINER_HUB_NO_RANGES #elif BOOST_WORKAROUND(BOOST_CLANG_VERSION, < 170100) && \ defined(BOOST_LIBSTDCXX_VERSION) //https://gcc.gnu.org/bugzilla/show_bug.cgi?id=109647 //https://github.com/llvm/llvm-project/issues/49620 #define BOOST_CONTAINER_HUB_NO_RANGES #endif #if !defined(BOOST_CONTAINER_HUB_NO_RANGES) // (std::input_iterator, std::iter_value_t/iter_reference_t) is already //included unconditionally below; only (std::convertible_to) is extra. #include #endif //Software prefetch hint accepting a (possibly fancy) pointer: convert to a raw //pointer and delegate to BOOST_CONTAINER_PREFETCH (workaround.hpp), which holds //the per-compiler/-arch implementation (and is a macro, not a function, because //of https://gcc.gnu.org/bugzilla/show_bug.cgi?id=109985). #define BOOST_CONTAINER_HUB_PREFETCH(p) \ BOOST_CONTAINER_PREFETCH(boost::movelib::to_raw_pointer(p)) #define BOOST_CONTAINER_HUB_PREFETCH_BLOCK(pbb, Block) \ do{ \ auto p0 = &static_cast(*(pbb)); \ BOOST_CONTAINER_HUB_PREFETCH(p0->data()); \ } while(0) #if defined(BOOST_MSVC) #pragma warning(push) #pragma warning(disable:4714) //marked as __forceinline not inlined #endif #endif //BOOST_CONTAINER_DOXYGEN_INVOKED namespace boost { namespace container { #ifndef BOOST_CONTAINER_DOXYGEN_INVOKED //Default argument for Allocator is provided by container_fwd.hpp template class hub; template typename hub::size_type erase_if(hub&, Predicate); namespace hub_detail { template class iterator; } template F for_each(hub&, F); template std::pair::iterator, F> for_each_while( hub&, F); template std::pair::const_iterator, F> for_each_while( const hub&, F); template std::pair, F> for_each_while( hub_detail::iterator, hub_detail::iterator, F); namespace hub_detail { //Shared bit helpers live in boost::container::dtl (detail/bit_utilities.hpp). //Use Boost.Intrusive's pointer_traits (modeled on std::pointer_traits) so the //header does not depend on Boost.Core. using boost::intrusive::pointer_traits; //Thin alias templates over boost::container::allocator_traits replacing the //former boost::core::allocator_access helpers. The propagate_on_* / is_always_equal //members of boost::container::allocator_traits are Boost integral constants; wrap //their value in std::integral_constant so the std::true_type/std::false_type-based //tag-dispatch helpers (swap_if, if_constexpr, copy_assign_if, ...) keep matching. template using pocca_t = std::integral_constant::propagate_on_container_copy_assignment::value>; template using pocma_t = std::integral_constant::propagate_on_container_move_assignment::value>; template using pocs_t = std::integral_constant::propagate_on_container_swap::value>; template using is_always_equal_t = std::integral_constant::is_always_equal::value>; template using rebind_alloc_t = typename allocator_traits::template portable_rebind_alloc::type; template using pointer_rebind_t = typename pointer_traits::template rebind; template struct block_base { using pointer = pointer_rebind_t; using const_pointer = pointer_rebind_t; using mask_type = std::uint64_t; static constexpr int N = 64; static constexpr mask_type full = (mask_type)(-1); static pointer pointer_to(block_base& x) noexcept { return pointer_traits::pointer_to(x); } static const_pointer pointer_to(const block_base& x) noexcept { return pointer_traits::pointer_to(x); } BOOST_CONTAINER_FORCEINLINE void link_available_before(pointer p) noexcept { next_available = p; prev_available = p->prev_available; next_available->prev_available = pointer_to(*this); prev_available->next_available = pointer_to(*this); } BOOST_CONTAINER_FORCEINLINE void link_available_after(pointer p) noexcept { prev_available = p; next_available = p->next_available; next_available->prev_available = pointer_to(*this); prev_available->next_available = pointer_to(*this); } BOOST_CONTAINER_FORCEINLINE void unlink_available() noexcept { prev_available->next_available = next_available; next_available->prev_available = prev_available; } BOOST_CONTAINER_FORCEINLINE void link_before(pointer p) noexcept { next = p; prev = p->prev; next->prev = pointer_to(*this); prev->next = pointer_to(*this); } BOOST_CONTAINER_FORCEINLINE void unlink() noexcept { prev->next = next; next->prev = prev; } pointer prev_available, next_available, prev, next; mask_type mask; }; template struct block: block_base> { using super = block_base>; using pointer = pointer_rebind_t; ValuePointer data() noexcept { return data_; } ValuePointer data_; static pointer static_cast_block_pointer(typename super::pointer pbb) noexcept { return pointer_traits::pointer_to(static_cast(*pbb)); } }; template void swap_payload(block& x, block& y) noexcept { std::swap(x.mask, y.mask); std::swap(x.data_, y.data_); } template struct block_list: block { using block = hub_detail::block; using block_base = typename block::super; using block_base_pointer = typename block_base::pointer; using const_block_base_pointer = typename block_base::const_pointer; using block_pointer = typename block::pointer; using block_base::full; using block_base::pointer_to; using block_base::prev_available; using block_base::next_available; using block_base::prev; using block_base::next; using block_base::mask; using block::data_; block_list() { reset(); mask = 1; //sentinel data_ = nullptr; } block_list(block_list&& x) noexcept: block_list{} { if(x.next_available != x.header()) { prev_available = x.prev_available; next_available = x.next_available; next_available->prev_available = header(); prev_available->next_available = header(); } if(x.prev != x.header()) { prev = x.prev; next = x.next; next->prev = header(); prev->next = header(); } x.reset(); } block_list& operator=(block_list&& x) noexcept { reset(); if(x.next_available != x.header()) { prev_available = x.prev_available; next_available = x.next_available; next_available->prev_available = header(); prev_available->next_available = header(); } if(x.prev != x.header()) { prev = x.prev; next = x.next; next->prev = header(); prev->next = header(); } x.reset(); return *this; } void reset() noexcept { prev_available = header(); next_available = header(); prev = header(); next = header(); } block_base_pointer header() noexcept { return pointer_to(static_cast(*this)); } const_block_base_pointer header() const noexcept { return pointer_to(static_cast(*this)); } BOOST_CONTAINER_FORCEINLINE void link_at_back(block_pointer pb) noexcept { pb->link_before(header()); } BOOST_CONTAINER_FORCEINLINE void link_before( block_pointer pbx, block_pointer pby) noexcept { pbx->link_before(pby); } BOOST_CONTAINER_FORCEINLINE static void unlink(block_pointer pb) noexcept { pb->unlink(); } BOOST_CONTAINER_FORCEINLINE void link_available_at_back(block_pointer pb) noexcept { pb->link_available_before(header()); } BOOST_CONTAINER_FORCEINLINE void link_available_at_front(block_pointer pb) noexcept { pb->link_available_after(header()); } BOOST_CONTAINER_FORCEINLINE void unlink_available(block_pointer pb) noexcept { pb->unlink_available(); } }; template class iterator { using element_type = typename pointer_traits::element_type; template using enable_if_consts_to_element_type_t =typename std::enable_if< std::is_same< const typename pointer_traits::element_type, element_type>::value >::type; public: using value_type = typename std::remove_const::type; using difference_type = typename pointer_traits::difference_type; using pointer = ValuePointer; using reference = element_type&; using iterator_category = std::bidirectional_iterator_tag; BOOST_CONTAINER_FORCEINLINE iterator() = default; BOOST_CONTAINER_FORCEINLINE iterator(const iterator& x) noexcept: pbb{x.pbb}, n{x.n} {} template< typename Value2Pointer, typename = enable_if_consts_to_element_type_t > BOOST_CONTAINER_FORCEINLINE iterator(const iterator& x) noexcept: pbb{x.pbb}, n{x.n} {} BOOST_CONTAINER_FORCEINLINE iterator& operator=(const iterator& x) noexcept { pbb = x.pbb; n = x.n; return *this; } template< typename Value2Pointer, typename = enable_if_consts_to_element_type_t > BOOST_CONTAINER_FORCEINLINE iterator& operator=( const iterator& x) noexcept { pbb = x.pbb; n = x.n; return *this; } BOOST_CONTAINER_FORCEINLINE pointer operator->() const noexcept { return static_cast(*pbb).data() + n; } BOOST_CONTAINER_FORCEINLINE reference operator*() const noexcept { return *operator->(); } BOOST_CONTAINER_FORCEINLINE iterator& operator++() noexcept { auto mask = pbb->mask & (full << 1 << n); if(BOOST_UNLIKELY(mask == 0)) { pbb = pbb->next; BOOST_CONTAINER_HUB_PREFETCH(pbb->next->next); BOOST_CONTAINER_HUB_PREFETCH_BLOCK(pbb->next, block); mask = pbb->mask; } n = dtl::unchecked_countr_zero(mask); return *this; } BOOST_CONTAINER_FORCEINLINE iterator operator++(int) noexcept { iterator tmp(*this); this->operator++(); return tmp; } BOOST_CONTAINER_FORCEINLINE iterator& operator--() noexcept { auto mask = pbb->mask & (full >> 1 >> (N - 1 - n)); if(BOOST_UNLIKELY(mask == 0)) { pbb = pbb->prev; BOOST_CONTAINER_HUB_PREFETCH(pbb->prev->prev); BOOST_CONTAINER_HUB_PREFETCH_BLOCK(pbb->prev, block); mask = pbb->mask; } n = N - 1 - dtl::unchecked_countl_zero(mask); return *this; } BOOST_CONTAINER_FORCEINLINE iterator operator--(int) noexcept { iterator tmp(*this); this->operator--(); return tmp; } BOOST_CONTAINER_FORCEINLINE friend bool operator==( const iterator& x, const iterator& y) noexcept { return x.pbb == y.pbb && x.n == y.n; } BOOST_CONTAINER_FORCEINLINE friend bool operator!=( const iterator& x, const iterator& y) noexcept { return !(x == y); } private: template friend class iterator; template friend class container::hub; template friend std::pair, F> container::for_each_while( hub_detail::iterator, hub_detail::iterator, F); template friend std::pair::iterator, F> container::for_each_while( hub&, F); template using pointer_rebind_t = hub_detail::pointer_rebind_t; using block_base = hub_detail::block_base>; using block_base_pointer = pointer_rebind_t; using const_block_base_pointer = pointer_rebind_t; using nonconst_pointer = pointer_rebind_t; //used by Natvis using block = hub_detail::block; using mask_type = typename block_base::mask_type; static constexpr int N = block_base::N; static constexpr mask_type full = block_base::full; BOOST_CONTAINER_FORCEINLINE iterator(const_block_base_pointer pbb_, int n_) noexcept: pbb{const_cast_block_base_pointer(pbb_)}, n{n_} {} BOOST_CONTAINER_FORCEINLINE iterator(const_block_base_pointer pbb_) noexcept: pbb{const_cast_block_base_pointer(pbb_)}, n{dtl::unchecked_countr_zero(pbb->mask)} {} static block_base_pointer const_cast_block_base_pointer(const_block_base_pointer pbb_) noexcept { return block_base::pointer_to(const_cast(*pbb_)); } block_base_pointer pbb = nullptr; int n = 0; }; template struct inline_ref_caller { F& f; template BOOST_CONTAINER_FORCEINLINE auto operator()(T&& x) -> decltype(std::declval()(std::declval())) { return f(std::forward(x)); } }; template struct inline_ref_const_caller { F& f; template BOOST_CONTAINER_FORCEINLINE auto operator()(const T& x) -> decltype(std::declval()(std::declval())) { return f(x); } }; template struct inline_return_true_ref_caller { F& f; template BOOST_CONTAINER_FORCEINLINE bool operator()(T&& x) { f(std::forward(x)); return true; } }; template struct inline_return_true_ref_const_caller { F& f; template BOOST_CONTAINER_FORCEINLINE bool operator()(const T& x) { f(x); return true; } }; template struct sort_iterator { using value_type = T; using difference_type = std::ptrdiff_t; using pointer = T*; using reference = T&; using iterator_category = std::random_access_iterator_tag; sort_iterator(T** pp_, difference_type index_): pp{pp_}, index{index_} {} pointer operator->() const noexcept { return pp[(std::size_t)index / N] + ((std::size_t)index % N); } reference operator*() const noexcept { return *operator->(); } sort_iterator& operator++() noexcept { ++index; return *this; } sort_iterator operator++(int) noexcept { sort_iterator tmp(*this); ++index; return tmp; } sort_iterator& operator--() noexcept { --index; return *this; } sort_iterator operator--(int) noexcept { sort_iterator tmp(*this); --index; return tmp; } friend difference_type operator-(const sort_iterator& x, const sort_iterator& y) noexcept { return x.index - y.index; } sort_iterator& operator+=(difference_type n) noexcept { index += n; return *this; } friend sort_iterator operator+(const sort_iterator& x, difference_type n) noexcept { return {x.pp, x.index + n}; } friend sort_iterator operator+(difference_type n, const sort_iterator& x) noexcept { return {x.pp, n + x.index}; } sort_iterator& operator-=(difference_type n) noexcept { index -= n; return *this; } friend sort_iterator operator-(const sort_iterator& x, difference_type n) noexcept { return {x.pp, x.index - n}; } reference operator[](difference_type n) const noexcept { return *(*this + n); } friend bool operator==(const sort_iterator& x, const sort_iterator& y) noexcept { return x.index == y.index; } friend bool operator!=(const sort_iterator& x, const sort_iterator& y) noexcept { return x.index != y.index; } friend bool operator<(const sort_iterator& x, const sort_iterator& y) noexcept { return x.index < y.index; } friend bool operator>(const sort_iterator& x, const sort_iterator& y) noexcept { return x.index > y.index; } friend bool operator<=(const sort_iterator& x, const sort_iterator& y) noexcept { return x.index <= y.index; } friend bool operator>=(const sort_iterator& x, const sort_iterator& y) noexcept { return x.index >= y.index; } T** pp; difference_type index; }; template struct buffer { buffer(std::size_t n, Allocator al_) noexcept: al{al_} { allocate_data(n); if(data) capacity = n; } ~buffer() { if(data) { for(; begin_ != end_; ++begin_) allocator_traits::destroy(al, begin()); deallocate_data(); } } T* begin() const noexcept { return data + begin_; } T* end() const noexcept { return data + end_; } template void emplace_back(Args&&... args) { BOOST_ASSERT(data && end_ != capacity); allocator_traits::construct(al, end(), std::forward(args)...); ++end_; } void erase_front() noexcept { BOOST_ASSERT(data && begin_ != end_); allocator_traits::destroy(al, begin()); ++begin_; } Allocator al; std::size_t begin_ = 0, end_ = 0; std::size_t capacity = 0; T* data = nullptr; private: //Portable, nothrow, (over)aligned allocation: this is the same primitive //boost::container::new_allocator relies on for overalignment, so there is no //need to special-case __cpp_aligned_new here. The nothrow null return lets //sort() fall back to a leaner algorithm when this (possibly large) scratch //buffer cannot be allocated. void allocate_data(std::size_t n) { data = static_cast(dtl::aligned_allocate(alignof(T), n * sizeof(T))); } void deallocate_data() { dtl::aligned_deallocate(data); } }; template struct nodtor_deleter { using pointer = T*; void operator()(pointer p) noexcept { ::operator delete(p); } }; template struct nodtor_deleter { using pointer = T*; void operator()(pointer p) noexcept { ::operator delete[](p); } }; template using nodtor_unique_ptr = boost::movelib::unique_ptr>; #if !defined(BOOST_CONTAINER_HUB_NO_RANGES) //begin()/end() access (without or std::begin/std::end) lives in //boost::container::dtl::adl_range (range_utils.hpp); reuse it here. template using range_iterator_t = decltype(dtl::adl_range::adl_begin(std::declval())); template using range_reference_t = std::iter_reference_t >; template using range_value_t = std::iter_value_t >; template concept input_range_like = requires(R& r) { dtl::adl_range::adl_begin(r); dtl::adl_range::adl_end(r); } && std::input_iterator >; template concept container_compatible_range = input_range_like && std::convertible_to, T>; #endif template using enable_if_is_input_iterator_t = typename std::enable_if< std::is_convertible< typename std::iterator_traits::iterator_category, std::input_iterator_tag >::value >::type; //std::pmr::polymorphic_allocator::destroy may be marked as deprecated. //C&P from boost/core/allocator_access.hpp. #if defined(_LIBCPP_SUPPRESS_DEPRECATED_PUSH) _LIBCPP_SUPPRESS_DEPRECATED_PUSH #endif #if defined(_STL_DISABLE_DEPRECATED_WARNING) _STL_DISABLE_DEPRECATED_WARNING #endif #if defined(__clang__) && defined(__has_warning) # if __has_warning("-Wdeprecated-declarations") # pragma clang diagnostic push # pragma clang diagnostic ignored "-Wdeprecated-declarations" # endif #elif defined(_MSC_VER) # pragma warning(push) # pragma warning(disable: 4996) #elif defined(BOOST_GCC) && BOOST_GCC >= 40600 # pragma GCC diagnostic push # pragma GCC diagnostic ignored "-Wdeprecated-declarations" #endif template struct allocator_has_destroy: std::false_type {}; template struct allocator_has_destroy< Allocator, Ptr, decltype((void)std::declval().destroy(std::declval())) >: std::true_type {}; #if defined(__clang__) && defined(__has_warning) # if __has_warning("-Wdeprecated-declarations") # pragma clang diagnostic pop # endif #elif defined(_MSC_VER) # pragma warning(pop) #elif defined(BOOST_GCC) && BOOST_GCC >= 40600 # pragma GCC diagnostic pop #endif #if defined(_STL_RESTORE_DEPRECATED_WARNING) _STL_RESTORE_DEPRECATED_WARNING #endif #if defined(_LIBCPP_SUPPRESS_DEPRECATED_POP) _LIBCPP_SUPPRESS_DEPRECATED_POP #endif template struct is_std_allocator: std::false_type {}; template struct is_std_allocator>: std::true_type {}; template struct is_std_pmr_polymorphic_allocator: std::false_type {}; #ifndef BOOST_NO_CXX17_HDR_MEMORY_RESOURCE template struct is_std_pmr_polymorphic_allocator>: std::true_type {}; #endif struct if_constexpr_void_else{ void operator()() const {} }; template void if_constexpr(std::true_type, F f, G = G{}) { f(); } template void if_constexpr(std::false_type, F, G g = G{}) { g(); } template void copy_assign_if(std::true_type, T& x, const T& y) { x = y; } template void copy_assign_if(std::false_type, T&, const T&) {} template void move_assign_if(std::true_type, T& x, T& y) { x = std::move(y); } template void move_assign_if(std::false_type, T&, T&) {} template void swap_if(std::true_type, T& x, T& y) { using std::swap; swap(x, y); } template void swap_if(std::false_type, T&, T&) {} template struct block_typedefs { using pointer = typename allocator_traits::pointer; template using pointer_rebind_t = hub_detail::pointer_rebind_t; using block_base = hub_detail::block_base>; using block_base_pointer = pointer_rebind_t; using const_block_base_pointer = pointer_rebind_t; using block = hub_detail::block; using block_pointer = pointer_rebind_t; using block_allocator = typename allocator_traits::template portable_rebind_alloc::type; using block_list = hub_detail::block_list; }; } //namespace container::hub_detail #endif //BOOST_CONTAINER_DOXYGEN_INVOKED //! A hub is a container with constant-time insertion and erasure and element //! stability: pointers and iterators to an element remain valid until the //! element is erased. It is a nearly drop-in, more compact alternative to the //! C++26 \c std::hive. //! //! Elements of a hub are stored in \e blocks of contiguous memory, each with a fixed //! element capacity. The insertion position is chosen by the container, //! which may reuse the memory of previously erased elements. A block with at //! least one element is called \e active; an empty block kept internally for //! future reuse is called \e reserved. Reserved blocks are not used until all //! active blocks are full, and are only deallocated by \c shrink_to_fit, //! \c trim_capacity or on container destruction. New blocks are allocated only //! when every block is full or when the user issues a \c reserve operation. //! //! \c hub is a model of \c Container, \c ReversibleContainer, //! \c AllocatorAwareContainer and \c SequenceContainer, with the following //! exceptions: operators \c == and \c != are not provided, and positional //! insertion of the form \c insert(position,\ ...) or \c emplace(position,\ ...) //! is not provided or ignores the position argument. Its iterators model //! \c LegacyBidirectionalIterator. //! //! \tparam T The cv-unqualified object type of the elements stored in the hub. //! \tparam Allocator An allocator whose value type is \c T. If \c void (the //! default), \c boost::container::new_allocator is used. //! //! Exception safety: Except when explicitly noted, all non-const member //! functions (and free functions taking \c hub by non-const reference) provide //! the basic exception guarantee, whereas all const member functions (and free //! functions taking \c hub by const reference) provide the strong guarantee. #ifdef BOOST_CONTAINER_DOXYGEN_INVOKED template class hub #else template class hub : hub_detail::block_typedefs::type>::block_allocator #endif { public: //! new_allocator is if Allocator is void, an alias for Allocator otherwise. typedef BOOST_CONTAINER_IMPDEF (typename real_allocator::type) allocator_type; private: static_assert( !std::is_const::value && !std::is_volatile::value && !std::is_function::value && !std::is_reference::value && !std::is_void::value, "T must be a cv-unqualified object type"); static_assert( std::is_same::value_type>::value, "Allocator's value_type must be the same type as T"); public: using value_type = T; using pointer = typename allocator_traits::pointer; using const_pointer = typename allocator_traits::const_pointer; using reference = T&; using const_reference = const T&; using size_type = typename allocator_traits::size_type; using difference_type = typename allocator_traits::difference_type; using iterator = hub_detail::iterator; using const_iterator = hub_detail::iterator; using reverse_iterator = std::reverse_iterator; using const_reverse_iterator = std::reverse_iterator; //! Effects: Constructs an empty hub, using \c allocator_type() as the allocator. //! //! Requires: \c allocator_type is DefaultConstructible. //! //! Complexity: Constant. hub() noexcept(noexcept(allocator_type())): hub{allocator_type()} {} //! Effects: Constructs an empty hub, using the specified allocator. //! //! Complexity: Constant. explicit hub(const allocator_type& al_) noexcept: allocator_base(al_) {} //! Effects: Constructs a hub with n default-inserted elements, using //! the specified allocator. //! //! Requires: T is DefaultInsertable into the hub. //! //! Complexity: Linear in n. explicit hub(size_type n, const allocator_type& al_ = allocator_type()): hub{al_} { range_insert_impl(size_type(0), n, [&, this] (T* p, size_type) { block_alloc_traits::construct(al(), p); }); } //! Effects: Constructs a hub with n copies of value, using the //! specified allocator. //! //! Requires: T is CopyInsertable into the hub. //! //! Complexity: Linear in n. hub(size_type n, const T& x, const allocator_type& al_ = allocator_type()): hub{al_} { insert(n, x); } //! Effects: Constructs a hub equal to the range [first, last), using //! the specified allocator. //! //! Complexity: Linear in std::distance(first, last). template< typename InputIterator BOOST_CONTAINER_DOCIGN(BOOST_MOVE_I typename = hub_detail::enable_if_is_input_iterator_t) > hub( InputIterator first, InputIterator last, const allocator_type& al_ = allocator_type()): hub{al_} { insert(first, last); } #if !defined(BOOST_CONTAINER_HUB_NO_RANGES) //! Effects: Constructs a hub equal to the range rg, using the //! specified allocator. //! //! Complexity: Linear in std::ranges::distance(rg). template< BOOST_CONTAINER_DOC1ST(std::ranges::input_range, hub_detail::container_compatible_range) R > hub(from_range_t, R&& rg, const allocator_type& al_ = allocator_type()): hub{al_} { insert_range(std::forward(rg)); } #endif //! Effects: Constructs a hub equal to x. The second overload uses the //! given allocator. //! //! Requires: T is CopyInsertable into the hub. //! //! Complexity: Linear in x.size(). hub(const hub& x): hub{x, block_alloc_traits::select_on_container_copy_construction(x.al())} {} //! Effects: Constructs a hub equal to x, using the given allocator. //! //! Requires: T is CopyInsertable into the hub. //! //! Complexity: Linear in x.size(). hub( const hub& x , const BOOST_CONTAINER_DOC1ST(allocator_type, dtl::type_identity_t) &al_): hub(x.begin(), x.end(), al_) {} //! Effects: Move constructor. Element blocks are moved from x into //! *this; pointers, references and iterators to elements of x remain valid //! but now refer to *this. //! //! Postcondition: x.empty() is true. //! //! Complexity: Constant. hub(hub&& x) noexcept: hub{std::move(x), allocator_type(std::move(x.al())), std::true_type{}} {} //! Effects: allocator_type-extended move constructor. If alloc equals //! x.get_allocator() the element blocks are moved (iterators/pointers to x //! remain valid as members of *this); otherwise each element is moved into //! *this and references, pointers and iterators to x are invalidated. //! //! Requires: T is MoveInsertable when the allocators may be unequal. //! //! Postcondition: x.empty() is true. //! //! Complexity: Constant, or linear in x.size() if elements are moved //! one by one. hub( hub&& x , const BOOST_CONTAINER_DOC1ST(allocator_type, dtl::type_identity_t) &al_): hub{std::move(x), al_, hub_detail::is_always_equal_t{}} {} //! Effects: Constructs a hub equal to il, using the specified allocator. //! //! Requires: T is CopyInsertable into the hub. //! //! Complexity: Linear in il.size(). hub(std::initializer_list il, const allocator_type& al_ = allocator_type()): hub{il.begin(), il.end(), al_} {} //! Effects: Destroys all elements and deallocates all blocks. //! //! Complexity: Linear in size() plus the number of blocks. ~hub() { reset(); } //! Effects: Copy assignment. Existing elements are copy-assigned or //! destroyed and the elements of x are copied into *this, keeping their //! relative order. //! //! Requires: T is CopyInsertable into the hub and CopyAssignable. //! //! Complexity: Linear in size() + x.size(). hub& operator=(const hub& x) { using pocca = hub_detail::pocca_t; if(this != &x) { if(al() != x.al() && pocca::value) { reset(); hub_detail::copy_assign_if(pocca{}, al(), x.al()); insert(x.begin(), x.end()); } else{ hub_detail::copy_assign_if(pocca{}, al(), x.al()); assign(x.begin(), x.end()); } } return *this; } //! Effects: Move assignment. Existing elements are move-assigned or //! destroyed. If the allocator propagates on move assignment or both //! allocators are equal, element blocks are moved from x (iterators/pointers //! to x remain valid as members of *this); otherwise each element of x is //! moved into *this and references, pointers and iterators to x are //! invalidated. //! //! Requires: T is MoveInsertable and MoveAssignable when elements are //! moved one by one. //! //! Postcondition: x.empty() is true. //! //! Complexity: Linear in size(), plus linear in x.size() when elements //! are moved one by one. hub& operator=(hub&& x) noexcept( hub_detail::pocma_t::value || hub_detail::is_always_equal_t::value) { if(this != &x) { move_assign( x, std::integral_constant< bool, hub_detail::pocma_t:: value || hub_detail::is_always_equal_t::value>{}); } return *this; } //! Effects: Replaces the contents of *this with a copy of il. //! //! Complexity: Linear in size() + il.size(). hub& operator=(std::initializer_list il) { assign(il); return *this; } //! Effects: Replaces the contents of *this with a copy of the range //! [first, last). //! //! Complexity: Linear in size() + std::distance(first, last). template< typename InputIterator BOOST_CONTAINER_DOCIGN(BOOST_MOVE_I typename = hub_detail::enable_if_is_input_iterator_t) > void assign(InputIterator first, InputIterator last) { range_assign_impl( first, last, [this] (T* p, InputIterator it) { block_alloc_traits::construct(al(), p, *it); }, [] (T* p, InputIterator it) { *p = *it; }); } #if !defined(BOOST_CONTAINER_HUB_NO_RANGES) //! Effects: Replaces the contents of *this with a copy of the elements //! in the range rg. //! //! Complexity: Linear in size() + std::ranges::distance(rg). template< BOOST_CONTAINER_DOC1ST(std::ranges::input_range, hub_detail::container_compatible_range) R > void assign_range(R&& rg) { range_assign_impl( dtl::adl_range::adl_begin(rg), dtl::adl_range::adl_end(rg), [this] (T* p, auto it) { block_alloc_traits::construct(al(), p, *it); }, [] (T* p, auto it) { *p = *it; }); } #endif //! Effects: Replaces the contents of *this with n copies of x. //! //! Complexity: Linear in size() + n. void assign(size_type n, const T& x) { range_assign_impl( size_type(0), n, [&, this] (T* p, size_type) { block_alloc_traits::construct(al(), p, x); }, [&] (T* p, size_type) { *p = x; }); } //! Effects: Replaces the contents of *this with a copy of il. //! //! Complexity: Linear in size() + il.size(). void assign(std::initializer_list il) { assign(il.begin(), il.end()); } //! Effects: Returns a copy of the allocator associated with *this. //! //! Complexity: Constant. allocator_type get_allocator() const noexcept { return al(); } //! Effects: Returns an iterator to the first element, or end() if empty. //! The const overloads return a const_iterator. Complexity: Constant. iterator begin() noexcept { return ++end(); } const_iterator begin() const noexcept { return ++end(); } //! Effects: Returns the past-the-end iterator. The end iterator is //! stable: it is not invalidated by insertion or erasure. The const overloads //! return a const_iterator. Complexity: Constant. iterator end() noexcept { return {blist.header(), 0}; } const_iterator end() const noexcept { return {blist.header(), 0}; } //! Effects: Reverse and const iterator accessors, with the usual //! semantics. Complexity: Constant. reverse_iterator rbegin() noexcept { return reverse_iterator{end()}; } const_reverse_iterator rbegin() const noexcept { return const_reverse_iterator{end()}; } reverse_iterator rend() noexcept { return reverse_iterator{begin()}; } const_reverse_iterator rend() const noexcept { return const_reverse_iterator{begin()}; } const_iterator cbegin() const noexcept { return begin(); } const_iterator cend() const noexcept { return end(); } const_reverse_iterator crbegin() const noexcept { return rbegin(); } const_reverse_iterator crend() const noexcept { return rend(); } //! Effects: Returns true if the hub contains no elements. //! //! Complexity: Constant. bool empty() const noexcept { return size_ == 0; } //! Effects: Returns the number of elements in the hub. //! //! Complexity: Constant. size_type size() const noexcept { return size_; } //! Effects: Returns the largest possible size of the hub. //! //! Complexity: Constant. size_type max_size() const noexcept { std::size_t bs = (std::size_t)block_alloc_traits::max_size(al()) * sizeof(block), vs = (std::size_t)value_alloc_traits::max_size(value_allocator(al())) * sizeof(T); return (size_type)((std::min)(bs, vs) / (sizeof(block) + sizeof(T) * N) * N); } //! Effects: Returns the total number of elements that *this can hold //! without requiring allocation of more element blocks. //! //! Complexity: Constant. size_type capacity() const noexcept { return num_blocks * N; } //! Effects: If n <= capacity() there are no effects; otherwise //! increases capacity() by allocating reserved blocks. //! //! Postcondition: capacity() >= n. //! //! Throws: if n > max_size(), plus any exception //! thrown by the allocator. //! //! Complexity: Linear in the number of reserved blocks allocated. //! //! Note: All references, pointers and iterators (including the //! past-the-end iterator) remain valid. void reserve(size_type n) { if(n > max_size()) { throw_length_error("Requested capacity greater than max_size()"); } while(capacity() < n) (void)create_new_available_block(); } //! Effects: Reallocates elements if needed so that the number of active //! blocks is minimized and deallocates the ensuing reserved blocks. If //! capacity() already equals size() there are no effects. If T throws during //! reallocation, the effects are unspecified. //! //! Requires: T is MoveInsertable into the hub. //! //! Complexity: Linear in size() if reallocation happens, plus linear in //! the number of reserved blocks. //! //! Note: If reallocation happens, the order of the elements may change //! and all references, pointers and iterators to elements are invalidated. void shrink_to_fit() { compact(); trim_capacity(); } //! Effects: Deallocates all reserved blocks, reducing capacity() //! accordingly. //! //! Complexity: Linear in the number of reserved blocks deallocated. //! //! Note: All references, pointers and iterators (including the //! past-the-end iterator) remain valid. void trim_capacity() noexcept { trim_capacity(0); } //! Effects: If n >= capacity() there are no effects; otherwise reduces //! capacity() to no less than n by deallocating reserved blocks. //! //! Complexity: Linear in the number of reserved blocks deallocated. //! //! Note: All references, pointers and iterators (including the //! past-the-end iterator) remain valid. void trim_capacity(size_type n) noexcept { if(capacity() <= n) return; for(auto pbb = blist.header()->prev_available; capacity() - n >= N && pbb != blist.header() && pbb->mask == 0; ) { auto pb = static_cast_block_pointer(pbb); pbb = pbb-> prev_available; blist.unlink_available(pb); delete_block(pb); --num_blocks; } } //! Effects: Inserts an object of type T constructed with //! std::forward(args)... at a position chosen by the container. If an //! exception is thrown there are no effects. args may directly or indirectly //! refer to a value in *this. //! //! Requires: T is EmplaceConstructible into the hub from args. //! //! Returns: An iterator pointing to the new element. //! //! Complexity: Constant. Exactly one object of type T is constructed. template BOOST_CONTAINER_FORCEINLINE iterator emplace(Args&&... args) { auto pbb = blist.next_available; //for construct_or_restore_capacity int n; auto pb = retrieve_available_block(n); auto mask = pb->mask; construct_or_restore_capacity( boost::movelib::to_raw_pointer(pb->data() + n), pbb, std::forward(args)...); mask |= mask + 1; pb->mask = mask; auto mask_plus_one = mask + 1; if(BOOST_UNLIKELY(mask_plus_one <= 2)) { //pb->mask == 0 (impossible), 1 or full if(mask_plus_one == 0) blist.unlink_available(pb); else /* pb->mask == 1 */ blist.link_at_back(pb); } ++size_; return {pb, n}; } //! Effects: Equivalent to emplace(std::forward(args)...); the //! hint is ignored. //! //! Returns: An iterator pointing to the new element. //! //! Complexity: Constant. template BOOST_CONTAINER_FORCEINLINE iterator emplace_hint(const_iterator, Args&&... args) { return emplace(std::forward(args)...); } //! Effects: Inserts a copy of x (or moves x) at a position chosen by //! the container; overloads taking a hint ignore it. Equivalent to //! emplace(std::forward(x)). //! //! Returns: An iterator pointing to the new element. //! //! Complexity: Constant. BOOST_CONTAINER_FORCEINLINE iterator insert(const T& x) { return emplace(x); } BOOST_CONTAINER_FORCEINLINE iterator insert(const_iterator, const T& x) { return emplace(x); } BOOST_CONTAINER_FORCEINLINE iterator insert(T&& x) { return emplace(std::move(x)); } BOOST_CONTAINER_FORCEINLINE iterator insert(const_iterator, T&& x) { return emplace(std::move(x)); } //! Effects: Inserts copies of the elements in il. Equivalent to //! insert(il.begin(), il.end()). //! //! Complexity: Linear in il.size(). void insert(std::initializer_list il) { insert(il.begin(), il.end()); } #if !defined(BOOST_CONTAINER_HUB_NO_RANGES) //! Effects: Inserts copies of the elements in rg. Each iterator in rg //! is dereferenced exactly once. //! //! Requires: T is EmplaceConstructible into the hub from //! *ranges::begin(rg) and rg does not overlap with *this. //! //! Complexity: Linear in the number of elements inserted; one object of //! type T is constructed per element. template< BOOST_CONTAINER_DOC1ST(std::ranges::input_range, hub_detail::container_compatible_range) R > void insert_range(R&& rg) { range_insert_impl( dtl::adl_range::adl_begin(rg), dtl::adl_range::adl_end(rg), [this] (T* p, auto it) { block_alloc_traits::construct(al(), p, *it); }); } #endif //! Effects: Inserts copies of the elements in [first, last). Each //! iterator in the range is dereferenced exactly once. //! //! Requires: T is EmplaceConstructible into the hub from *first and //! [first, last) does not overlap with *this. //! //! Complexity: Linear in the number of elements inserted; one object of //! type T is constructed per element. template< typename InputIterator BOOST_CONTAINER_DOCIGN(BOOST_MOVE_I typename = hub_detail::enable_if_is_input_iterator_t) > void insert(InputIterator first, InputIterator last) { range_insert_impl(first, last, [this] (T* p, InputIterator it) { block_alloc_traits::construct(al(), p, *it); }); } //! Effects: Inserts n copies of x. //! //! Requires: T is CopyInsertable into the hub. //! //! Complexity: Linear in n; one object of type T is constructed per //! element. void insert(size_type n, const T& x) { range_insert_impl(size_type(0), n, [&, this] (T* p, size_type) { block_alloc_traits::construct(al(), p, x); }); } //! Effects: Erases the element pointed to by pos. //! //! Returns: An iterator pointing to the element that followed the //! erased one. //! //! Complexity: Constant. //! //! Note: Invalidates references, pointers and iterators referring to //! the erased element. BOOST_CONTAINER_FORCEINLINE iterator erase(const_iterator pos) { auto pbb = pos.pbb; auto n = pos.n; ++pos; erase_impl(pbb, n); return {pos.pbb, pos.n}; } //! Effects: Erases the element pointed to by pos. Equivalent to //! erase(pos) but returns nothing. //! //! Complexity: Constant. //! //! Note: Potentially faster than erase(pos) as no return iterator needs //! to be computed. Invalidates references, pointers and iterators referring //! to the erased element. BOOST_CONTAINER_FORCEINLINE void erase_void(const_iterator pos) { erase_impl(pos.pbb, pos.n); } //! Effects: Erases the elements in the range [first, last). //! //! Returns: An iterator pointing to the element that followed the last //! erased element. //! //! Complexity: Linear in the number of elements erased. //! //! Note: Invalidates references, pointers and iterators referring to //! the erased elements. iterator erase(const_iterator first, const_iterator last) { for(auto pbb = first.pbb; first != last; ) { first = erase(first); if(first.pbb != pbb) break; } auto pbb = first.pbb; if(pbb != last.pbb) { do { auto pb = static_cast_block_pointer(pbb); pbb = pb->next; BOOST_CONTAINER_HUB_PREFETCH_BLOCK(pbb, block); size_ -= destroy_all_in_nonempty_block(pb); blist.unlink(pb); if(BOOST_LIKELY(pb->mask != full)) blist.unlink_available(pb); blist.link_available_at_back(pb); pb->mask = 0; } while(pbb != last.pbb); first = {pbb}; } while(first != last) first = erase(first); return {last.pbb, last.n}; } //! Effects: Exchanges the contents and capacity() of *this with those //! of x. //! //! Complexity: Constant. void swap(hub& x) noexcept( hub_detail::pocs_t::value || hub_detail::is_always_equal_t::value) { using pocs = hub_detail::pocs_t; hub_detail::if_constexpr(pocs{}, [&, this]{ hub_detail::swap_if(pocs{}, al(), x.al()); }, [&, this]{ //else BOOST_ASSERT(al() == x.al()); (void)this; }); std::swap(blist, x.blist); std::swap(num_blocks, x.num_blocks); std::swap(size_, x.size_); } //! Effects: Erases all elements. Reserved blocks are kept. //! //! Complexity: Linear in size(). void clear() noexcept { erase(begin(), end()); } //! Effects: Inserts the contents of x into *this, leaving x empty. //! Pointers and references to the moved elements of x now refer to *this; //! iterators continue to refer to their elements but behave as iterators //! into *this. Reserved blocks of x are not transferred. //! //! Requires: get_allocator() == x.get_allocator() and //! std::addressof(x) != this. //! //! Complexity: Linear in the number of blocks of x plus the number of //! blocks of *this. void splice(hub& x) { BOOST_ASSERT(this != &x); BOOST_ASSERT(al() == x.al()); for(auto pbb = x.blist.header()->next; pbb != x.blist.header(); ) { auto pb = static_cast_block_pointer(pbb); pbb = pbb->next; if(pb->mask != full) { x.blist.unlink_available(pb); blist.link_available_at_front(pb); } x.blist.unlink(pb); blist.link_at_back(pb); --x.num_blocks; ++num_blocks; auto s = dtl::popcount(pb->mask); x.size_ -= (size_type)s; size_ += (size_type)s; } } //! Effects: Equivalent to splice(x). void splice(hub&& x) { splice(x); } //! Effects: Erases all but the first element from every consecutive //! group of equivalent elements, i.e. erases each element i in //! [begin() + 1, end()) for which pred(*i, *(i - 1)) is true. //! //! Requires: pred is an equivalence relation. //! //! Returns: The number of elements erased. //! //! Throws: Nothing unless pred throws. //! //! Complexity: Exactly size() - 1 applications of pred for a non-empty //! hub, otherwise none. //! //! Note: Invalidates references, pointers and iterators referring to //! the erased elements. template> size_type unique(BinaryPredicate pred = BinaryPredicate()) { auto s = size_; for(auto first = cbegin(), last = cend(); first != last; ) { auto next = std::next(first); first = erase( next, std::find_if_not(next, last, [&] (const T& x) { return pred(x, *first); })); } return (size_type)(s - size_); } #if defined(BOOST_MSVC) #pragma warning(push) #pragma warning(disable:4127) //conditional expression is constant #endif //! Effects: Sorts *this according to comp. If comp or any operation on //! T throws, *this is left in a valid but unspecified state; if an exception //! is thrown while allocating internal memory there are no effects. //! //! Requires: T is MoveInsertable into the hub, MoveConstructible, //! MoveAssignable and Swappable. //! //! Complexity: O(N*log(N)) comparisons, where N is size(). //! //! Note: May allocate. References, pointers and iterators to elements //! may be invalidated. The sort is not stable. template> void sort(Compare comp = Compare()) { //transfer_sort is usually the fastest, but it consumes the most //auxiliary memory when sizeof(T) > sizeof(sort_proxy), so we restrict //its usage to the case sizeof(T) <= sizeof(sort_proxy). //compact_sort uses the least amount of auxiliary memory (by far), and //it's also faster than proxy_sort when the auxiliary memory of the latter //exceeds some threshold seemingly related to the size of the L2 cache; //we conventionally set the threshold to 2MB for lack of a more precise //estimation mechanism. //TODO: In 32-bit mode, the threshold policy is not so clear-cut and the //cost of moving elements around seems to play a role. BOOST_IF_CONSTEXPR(sizeof(T) <= sizeof(sort_proxy)) { if(transfer_sort(comp)) return; } else{ static constexpr std::size_t memory_threshold = 2 * 1024 * 1024; if((std::size_t)size_ * sizeof(sort_proxy) <= memory_threshold) { if(proxy_sort(comp)) return; } } compact_sort(comp); } #if defined(BOOST_MSVC) #pragma warning(pop) //C4127 #endif //! Effects: Returns an iterator referring to the //! same element as p. //! //! Requires: p points to an element in *this. //! //! Throws: Nothing. //! //! Complexity: Linear in the number of active blocks in *this. iterator get_iterator(const_pointer p) { std::less less; for(auto pbb = blist.next; pbb != blist.header(); pbb = pbb-> next) { auto pb = static_cast_block_pointer(pbb); if(!less(boost::movelib::to_raw_pointer(p), boost::movelib::to_raw_pointer(pb->data())) && less(boost::movelib::to_raw_pointer(p), boost::movelib::to_raw_pointer(pb->data() + N))) { int n = (int)(p - pb->data()); BOOST_ASSERT_MSG( (pb->mask & ((mask_type)(1) << n)) != 0, "p points to an invalid element"); return {pb, n}; } } BOOST_ASSERT_MSG(false, "p does not point into the extents of *this"); #if defined(BOOST_ASSERT_HANDLER_IS_NORETURN) BOOST_UNREACHABLE_RETURN(end()); #else return end(); #endif } //! Effects: Returns a const_iterator referring to the //! same element as p. //! //! Requires: p points to an element in *this. //! //! Throws: Nothing. //! //! Complexity: Linear in the number of active blocks in *this. const_iterator get_iterator(const_pointer p) const { return const_cast(this)->get_iterator(p); } #ifndef BOOST_CONTAINER_DOXYGEN_INVOKED private: template friend std::pair::iterator, F> for_each_while( hub&, F); template friend typename hub::size_type erase_if(hub&, P); using block_typedefs = hub_detail::block_typedefs; using block_base = typename block_typedefs::block_base; using block_base_pointer = typename block_typedefs::block_base_pointer; using const_block_base_pointer = typename block_typedefs::const_block_base_pointer; using block = typename block_typedefs::block; using block_pointer = typename block_typedefs::block_pointer; using block_allocator = typename block_typedefs::block_allocator; using block_list = typename block_typedefs::block_list; //EBO: hub derives directly from block_allocator (like dtl::vector_alloc_holder). using allocator_base = block_allocator; using block_alloc_traits = allocator_traits; using value_allocator = hub_detail::rebind_alloc_t; using value_alloc_traits = allocator_traits; using mask_type = typename block_base::mask_type; static constexpr int N = block_base::N; static constexpr mask_type full = block_base::full; block_allocator& al() noexcept { return static_cast(*this); } const block_allocator& al() const noexcept { return static_cast(*this); } struct reset_on_exit { ~reset_on_exit() { x.reset(); } hub& x; }; hub( hub&& x, const allocator_type& al_, std::true_type /* equal allocs */) noexcept: allocator_base(al_), blist{std::move(x.blist)}, num_blocks{x.num_blocks}, size_{x.size_} { x.num_blocks = 0; x.size_ = 0; } hub( hub&& x, const allocator_type& al_, std::false_type /* maybe unequal allocs */): hub{al_} { if(al() == x.al()) { blist = std::move(x.blist); num_blocks = x.num_blocks; size_ = x.size_; x.num_blocks = 0; x.size_ = 0; } else { reset_on_exit on_exit{x}; (void)on_exit; range_insert_impl(x.begin(), x.end(), [this] (T* p, iterator it) { block_alloc_traits::construct(al(), p, std::move(*it)); }); } } void move_assign(hub& x, std::true_type /* transfer structure */) { using pocma = hub_detail::pocma_t; reset(); hub_detail::move_assign_if(pocma{}, al(), x.al()); blist = std::move(x.blist); num_blocks = x.num_blocks; size_ = x.size_; x.num_blocks = 0; x.size_ = 0; } void move_assign(hub& x, std::false_type /* maybe move data */) { if(al() == x.al()) { move_assign(x, std::true_type{}); } else { reset_on_exit on_exit{x}; (void)on_exit; range_assign_impl( x.begin(), x.end(), [this] (T* p, iterator it) { block_alloc_traits::construct(al(), p, std::move(*it)); }, [] (T* p, iterator it) { *p = std::move(*it); }); } } static block_pointer static_cast_block_pointer(block_base_pointer pbb) noexcept { return block::static_cast_block_pointer(pbb); } block_pointer create_new_available_block() { auto pb = block_alloc_traits::allocate(al(), 1); pb->mask = 0; BOOST_CONTAINER_TRY { value_allocator val(al()); pb->data_ = value_alloc_traits::allocate(val, N); } BOOST_CONTAINER_CATCH(...) { block_alloc_traits::deallocate(al(), pb, 1); BOOST_CONTAINER_RETHROW; } BOOST_CONTAINER_CATCH_END blist.link_available_at_back(pb); ++num_blocks; return pb; } void delete_block(block_pointer pb) noexcept { value_allocator val(al()); value_alloc_traits::deallocate(val, pb->data(), N); block_alloc_traits::deallocate(al(), pb, 1); } BOOST_CONTAINER_FORCEINLINE block_pointer retrieve_available_block(int& n) { if(BOOST_LIKELY(blist.next_available != blist.header())) { auto pb = static_cast_block_pointer(blist.next_available); n = dtl::unchecked_countr_one(pb->mask); return pb; } else { n = 0; return create_new_available_block(); } } BOOST_CONTAINER_FORCEINLINE size_type destroy_all_in_nonempty_block( block_pointer pb) noexcept { BOOST_ASSERT(pb->mask != 0); return destroy_all_in_nonempty_block(pb, std::integral_constant::value && ( hub_detail::is_std_allocator::value || hub_detail::is_std_pmr_polymorphic_allocator::value || !hub_detail::allocator_has_destroy::value )>{}); } BOOST_CONTAINER_FORCEINLINE size_type destroy_all_in_nonempty_block( block_pointer pb, std::true_type /* trivial destruction */) noexcept { return (size_type)dtl::popcount(pb->mask); } BOOST_CONTAINER_FORCEINLINE size_type destroy_all_in_nonempty_block( block_pointer pb, std::false_type /* use allocator destroy */) noexcept { size_type s = (size_type)dtl::popcount(pb->mask); auto mask = pb->mask; auto pd = boost::movelib::to_raw_pointer(pb->data()); BOOST_CONTAINER_UNROLL(4) do { auto n = dtl::unchecked_countr_zero(mask); block_alloc_traits::destroy(al(), pd + n); mask &= mask - 1; } while(mask); return s; } BOOST_CONTAINER_FORCEINLINE size_type destroy_all_in_full_block( block_pointer pb) noexcept { BOOST_ASSERT(pb->mask == full); auto pd = boost::movelib::to_raw_pointer(pb->data()); int n = 0; BOOST_CONTAINER_UNROLL(4) for(; n < N; ++n) { block_alloc_traits::destroy(al(), pd + n); } return (size_type)N; } void reset() noexcept { //empty blocks auto pbb = blist.prev_available; BOOST_CONTAINER_UNROLL(4) while(pbb != blist.header() && pbb->mask == 0) { auto pb = static_cast_block_pointer(pbb); pbb = pb->prev_available; BOOST_CONTAINER_HUB_PREFETCH(pbb); delete_block(pb); } //non-empty blocks pbb = blist.next; while(pbb != blist.header()) { auto pb = static_cast_block_pointer(pbb); pbb = pb->next; BOOST_IF_CONSTEXPR(!std::is_trivially_destructible::value) { BOOST_CONTAINER_HUB_PREFETCH_BLOCK(pbb, block); } if(pb->mask == full) destroy_all_in_full_block(pb); else destroy_all_in_nonempty_block(pb); delete_block(pb); } blist.reset(); num_blocks = 0; size_ = 0; } template inline void construct_or_restore_capacity( value_type* p, block_base_pointer pbb, Args&&... args) { BOOST_CONTAINER_TRY { block_alloc_traits::construct(al(), p, std::forward(args)...); } BOOST_CONTAINER_CATCH(...) { restore_capacity_on_throw(pbb); BOOST_CONTAINER_RETHROW } BOOST_CONTAINER_CATCH_END } BOOST_NOINLINE void restore_capacity_on_throw( block_base_pointer pbb) noexcept { auto pb = static_cast_block_pointer(blist.next_available); if(pb != pbb) { //block freshly allocated -> restore capacity blist.unlink_available(pb); delete_block(pb); --num_blocks; } } BOOST_CONTAINER_FORCEINLINE void erase_impl(block_base_pointer pbb, int n) noexcept { auto pb = static_cast_block_pointer(pbb); block_alloc_traits::destroy(al(), boost::movelib::to_raw_pointer(pb->data() + n)); if(BOOST_UNLIKELY(pb->mask == full)) blist.link_available_at_front(pb); pb->mask &= ~((mask_type)(1) << n); if(BOOST_UNLIKELY(pb->mask == 0)) { blist.unlink(pb); blist.unlink_available(pb); blist.link_available_at_back(pb); } --size_; } template void range_insert_impl( Incrementable first, Sentinel last, Construct construct_) { while(first != last) { int n; auto pb = retrieve_available_block(n); for(; ; ) { construct_(boost::movelib::to_raw_pointer(pb->data() + n), first++); ++size_; if(BOOST_UNLIKELY(pb->mask == 0)) blist.link_at_back(pb); pb->mask |= pb->mask +1; if(pb->mask == full) { blist.unlink_available(pb); break; } else if(first == last) return; n = dtl::unchecked_countr_one(pb->mask); } } } template< typename Incrementable, typename Sentinel, typename Construct, typename Insert > void range_assign_impl( Incrementable first, Sentinel last, Construct construct_, Insert insert_) { auto pbb = blist.next; int n = -1; if(first != last) { //consume active blocks for(; pbb != blist.header(); pbb = pbb->next) { auto pb = static_cast_block_pointer(pbb); n = 0; for(mask_type bit = 1; bit; bit <<= 1, ++n) { if(pb->mask & bit) { //full slot insert_(boost::movelib::to_raw_pointer(pb->data() + n), first++); } else { //empty slot construct_(boost::movelib::to_raw_pointer(pb->data() + n), first++); ++size_; pb->mask |= bit; if(pb->mask == full) blist.unlink_available(pb); } if(first == last) goto exit; } } exit: ; } if(first != last) { //all active blocks consumed, keep inserting range_insert_impl(first, last, construct_); } else{ //erase remaining original elements auto it = (n == -1)? const_iterator{pbb}: ++const_iterator{pbb, n}; erase(it, cend()); } } template bool transfer_sort(Compare comp) { //transfer to a buffer, sort and transfer back if(size_ > 1) { hub_detail::buffer buf(size_, al()); if(!buf.data) return false; container::for_each(*this, [&] (value_type& x) { buf.emplace_back(std::move(x)); }); std::sort(buf.begin(), buf.end(), comp); container::for_each(*this, [&] (value_type& x) { x = std::move(*buf.begin()); buf.erase_front(); }); } return true; } struct sort_proxy { T* p; size_type n; }; template bool proxy_sort(Compare comp) { //sort an array of (pointer, index) pairs and relocate according to it if(size_ > 1) { hub_detail::nodtor_unique_ptr p {static_cast( ::operator new[](size_ * sizeof(sort_proxy), std::nothrow))}; if(!p) return false; size_type i = 0; container::for_each(*this, [&] (value_type& x) { p[i] = {dtl::addressof(x), i}; ++i; }); std::sort( p.get(), p.get() + size_, [&] (const sort_proxy& x, const sort_proxy& y) { return comp(const_cast(*x.p), const_cast(*y.p)); }); i = 0; for(; i < size_; ++i) { if(p[i].n != i) { T x = std::move(*(p[i].p)); auto j = i; do { auto k = p[j].n; *(p[j].p) = std::move(*p[k].p); p[j].n = j; j = k; } while(p[j].n != i); *(p[j].p) = std::move(x); p[j].n = j; } } } return true; } template void compact_sort(Compare comp) { //compact elements and build an array of pointers to data chunks of N using sort_iterator = hub_detail::sort_iterator; if(size_ > 1) { std::size_t n = (std::size_t)((size_ + N - 1) / N); hub_detail::nodtor_unique_ptr p {static_cast(::operator new[](n * sizeof(T*)))}; std::size_t i = 0; compact([&] (block_pointer pb) { p[i++] = boost::movelib::to_raw_pointer(pb->data()); }); BOOST_ASSERT(i == n); std::sort( sort_iterator{p.get(), 0}, sort_iterator{p.get(), (std::ptrdiff_t)size_}, comp); } } void compact() { compact([] (block_pointer) {}); } template void compact(Track track) { for(auto pbbx = blist.next; pbbx != blist.header(); ) { auto pbx = static_cast_block_pointer(pbbx); auto pbby = pbbx->next; if(pbx->mask != full) { do{ if(pbby->mask == full) { do { track(static_cast_block_pointer(pbby)); pbby = pbby->next; } while(pbby->mask == full); blist.unlink(pbx); blist.link_before(pbx, static_cast_block_pointer(pbby)); } if(pbby == blist.header()) { compact(pbx); track(pbx); return; } else{ auto pby = static_cast_block_pointer(pbby); compact(pbx,pby); if(pby->mask == 0) { pbby = pby->next; blist.unlink(pby); blist.unlink_available(pby); blist.link_available_at_back(pby); } } }while(pbx->mask != full); blist.unlink_available(pbx); } track(pbx); pbbx = pbby; } } void compact(block_pointer pbx, block_pointer pby) { auto cx = dtl::popcount(pbx->mask), cy = dtl::popcount(pby->mask); if(cx < cy) { std::swap(cx, cy); swap_payload(*pbx, *pby); } auto c = (std::min)(N - cx, cy); while(c--) { auto n = dtl::unchecked_countr_one(pbx->mask); auto m = N - 1 - dtl::unchecked_countl_zero(pby->mask); block_alloc_traits::construct( al(), boost::movelib::to_raw_pointer(pbx->data() + n), std::move(pby->data()[m])); block_alloc_traits::destroy(al(), boost::movelib::to_raw_pointer(pby->data() + m)); pbx->mask |= pbx->mask + 1; pby->mask &= ~((mask_type)(1) << m); } } void compact(block_pointer pb) { for(; ; ) { auto n = dtl::unchecked_countr_one(pb->mask); auto m = N - 1 - dtl::unchecked_countl_zero(pb->mask); if(n > m) return; block_alloc_traits::construct( al(), boost::movelib::to_raw_pointer(pb->data() + n), std::move(pb->data()[m])); block_alloc_traits::destroy(al(), boost::movelib::to_raw_pointer(pb->data() + m)); pb->mask |= pb->mask + 1; pb->mask &= ~((mask_type)(1) << m); } } block_list blist; size_type num_blocks = 0; size_type size_ = 0; #endif //BOOST_CONTAINER_DOXYGEN_INVOKED }; #ifndef BOOST_CONTAINER_DOXYGEN_INVOKED #if !defined(BOOST_NO_CXX17_DEDUCTION_GUIDES) template hub(InputIterator, InputIterator) -> hub::value_type>; template hub(InputIterator, InputIterator, Allocator) -> hub::value_type, Allocator>; //The (initializer_list) case is covered by the implicit guide of the //initializer_list constructor (the allocator defaults to void); an explicit //guide is only needed to deduce a user-supplied allocator, since the resolved //in-class allocator type is a non-deduced context. template hub(std::initializer_list, Allocator) -> hub; #if !defined(BOOST_CONTAINER_HUB_NO_RANGES) template hub(from_range_t, R&&) -> hub >; template hub(from_range_t, R&&, Allocator) -> hub, Allocator>; #endif //BOOST_CONTAINER_HUB_NO_RANGES #endif //BOOST_NO_CXX17_DEDUCTION_GUIDES #endif //BOOST_CONTAINER_DOXYGEN_INVOKED //! Effects: Equivalent to x.swap(y). //! //! Complexity: Constant. template void swap(hub& x, hub& y) noexcept(noexcept(x.swap(y))) { x.swap(y); } //! Effects: Erases all elements of x equal to value. Equivalent to //! erase_if(x, [&](const auto& e){ return e == value; }). //! //! Returns: The number of erased elements. //! //! Complexity: Linear in x.size(). template typename hub::size_type erase(hub& x, const U& value) { return container::erase_if( x, [&](const T& v) -> bool { return v == value; }); } //! Effects: Erases all elements of x for which pred returns true. //! //! Returns: The number of erased elements. //! //! Complexity: Linear in x.size(). //! //! Note: Potentially faster than the naive erase loop due to internal //! optimizations. template typename hub::size_type erase_if(hub& x, Predicate pred) { using hub_container = hub; using size_type = typename hub_container::size_type; using block = typename hub_container::block; auto s = x.size_; for(auto pbb = x.blist.next; pbb != x.blist.header(); ) { auto pb = x.static_cast_block_pointer(pbb); pbb = pb->next; BOOST_CONTAINER_HUB_PREFETCH_BLOCK(pbb, block); auto mask = pb->mask; do { auto n = dtl::unchecked_countr_zero(mask); if(pred(pb->data()[n])) x.erase_impl(pb, n); mask &= mask - 1; } while(mask); } return (size_type)(s - x.size_); } //! Effects: Applies f to every element in [first, last), in order. //! Equivalent to: while(first != last) f(*first++); return f; //! //! Requires: decltype(first) is the `iterator` or `const_iterator` of an //! instantiation of hub and [first, last) is a valid range. //! //! Returns: std::move(f). //! //! Note: Potentially faster than the equivalent loop thanks to internal //! unrolling and prefetching. template< BOOST_CONTAINER_DOC1ST(class ImplDefinedHubIterator, typename ValuePtr), typename F> BOOST_CONTAINER_FORCEINLINE F for_each ( BOOST_CONTAINER_DOC1ST(ImplDefinedHubIterator, hub_detail::iterator) first , BOOST_CONTAINER_DOC1ST(ImplDefinedHubIterator, hub_detail::iterator) last , F f) { container::for_each_while( first, last, hub_detail::inline_return_true_ref_caller{f}); return f; } //! Effects: Applies f to every element of x. Equivalent to //! for_each(x.begin(), x.end(), std::ref(f)). //! //! Returns: std::move(f). //! //! Note: Potentially faster than range iteration thanks to internal //! unrolling and prefetching. template BOOST_CONTAINER_FORCEINLINE F for_each(hub& x, F f) { container::for_each_while( x, hub_detail::inline_return_true_ref_caller{f}); return f; } //! Effects: Applies f to every element of x. Equivalent to //! for_each(x.cbegin(), x.cend(), std::ref(f)). //! //! Returns: std::move(f). //! //! Note: Potentially faster than range iteration thanks to internal //! unrolling and prefetching. template BOOST_CONTAINER_FORCEINLINE F for_each(const hub& x, F f) { container::for_each_while( const_cast&>(x), hub_detail::inline_return_true_ref_const_caller{f}); return f; } //! Effects: Applies f to the elements of [first, last) in order while f //! returns true. Equivalent to: //! while(first != last && f(*first)) ++first; return {first, std::move(f)}; //! //! Requires: decltype(first) is the `iterator` or `const_iterator` of an //! instantiation of hub and [first, last) is a valid range. //! //! Returns: A pair with the iterator past the last visited element and //! std::move(f). //! //! Note: Potentially faster than the equivalent loop thanks to internal //! unrolling and prefetching. template< BOOST_CONTAINER_DOC1ST(class ImplDefinedHubIterator, typename ValuePtr), typename F> std::pair), F> for_each_while ( BOOST_CONTAINER_DOC1ST(ImplDefinedHubIterator, hub_detail::iterator) first , BOOST_CONTAINER_DOC1ST(ImplDefinedHubIterator, hub_detail::iterator) last , F f) { using iterator = hub_detail::iterator; using block = typename iterator::block; using mask_type = typename iterator::mask_type; static constexpr auto full = iterator::full; if(BOOST_UNLIKELY(first == last)) return {last, std::move(f)}; auto pbb = first.pbb, last_pbb = last.pbb; auto last_n = last.n; auto mask = pbb->mask & (full << first.n); for(; ;) { BOOST_CONTAINER_HUB_PREFETCH(&pbb->next->mask); auto is_last = mask_type(pbb == last_pbb); mask &= (is_last << last_n) - mask_type(1); BOOST_CONTAINER_HUB_PREFETCH( block::static_cast_block_pointer(pbb->next)->data()); auto next_n = dtl::unchecked_countr_zero(pbb->next->mask); BOOST_CONTAINER_HUB_PREFETCH( block::static_cast_block_pointer(pbb->next)->data() + next_n); auto pd = block::static_cast_block_pointer(pbb)->data(); BOOST_CONTAINER_UNROLL(4) while(mask) { auto n = dtl::unchecked_countr_zero(mask); if (!f(pd[n])) return {{pbb, n}, std::move(f)}; mask &= mask - 1; } if(BOOST_UNLIKELY(is_last != 0)) return {last, std::move(f)}; pbb = pbb->next; mask = pbb->mask; } } //! Effects: Applies f to the elements of x while f returns true. //! Equivalent to for_each_while(x.begin(), x.end(), std::ref(f)) with f moved //! into the returned pair. //! //! Returns: A pair with the iterator past the last visited element and //! std::move(f). //! //! Note: Potentially faster than range iteration thanks to internal //! unrolling and prefetching. template std::pair::iterator, F> for_each_while(hub& x, F f) { using iterator = typename hub::iterator; using block = typename iterator::block; auto last_pbb = x.blist.header(); for(auto pbb = x.blist.next; pbb != last_pbb; ) { BOOST_CONTAINER_HUB_PREFETCH(&pbb->next->mask); BOOST_CONTAINER_HUB_PREFETCH( block::static_cast_block_pointer(pbb->next)->data()); auto next_n = dtl::unchecked_countr_zero(pbb->next->mask); BOOST_CONTAINER_HUB_PREFETCH( block::static_cast_block_pointer(pbb->next)->data() + next_n); auto pd = block::static_cast_block_pointer(pbb)->data(); auto mask = pbb->mask; BOOST_CONTAINER_UNROLL(4) while(mask) { auto n = dtl::unchecked_countr_zero(mask); if (!f(pd[n])) return {{pbb, n}, std::move(f)}; mask &= mask - 1; } pbb = pbb->next; mask = pbb->mask; } return {{last_pbb}, std::move(f)}; } //! Effects: Applies f to the elements of x while f returns true. //! Equivalent to for_each_while(x.cbegin(), x.cend(), std::ref(f)) with f moved //! into the returned pair. //! //! Returns: A pair with the iterator past the last visited element and //! std::move(f). //! //! Note: Potentially faster than range iteration thanks to internal //! unrolling and prefetching. template std::pair::const_iterator, F> BOOST_CONTAINER_FORCEINLINE for_each_while(const hub& x, F f) { return { container::for_each_while( const_cast&>(x), hub_detail::inline_ref_const_caller{f}).first, std::move(f)}; } } //namespace container } //namespace boost #if defined(BOOST_MSVC) #pragma warning(pop) //C4714 #endif #include #endif //BOOST_CONTAINER_HUB_HPP