From 242c567d208bfaccaef0fbf599e6148bc33ba972 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ion=20Gazta=C3=B1aga?= Date: Fri, 5 Jun 2026 23:46:41 +0200 Subject: [PATCH] Put hub.hpp at the top-level --- experimental/bench_hub.cpp | 2 +- .../container/{experimental => }/hub.hpp | 375 ++++++++++++++++++ 2 files changed, 376 insertions(+), 1 deletion(-) rename include/boost/container/{experimental => }/hub.hpp (76%) diff --git a/experimental/bench_hub.cpp b/experimental/bench_hub.cpp index 2b031bd..db070a1 100644 --- a/experimental/bench_hub.cpp +++ b/experimental/bench_hub.cpp @@ -103,7 +103,7 @@ BOOST_CONTAINER_FORCEINLINE void resume_timing() measure_start += boost::move_detail::nsec_clock() - measure_pause; } -#include +#include #include #include #include diff --git a/include/boost/container/experimental/hub.hpp b/include/boost/container/hub.hpp similarity index 76% rename from include/boost/container/experimental/hub.hpp rename to include/boost/container/hub.hpp index 5d21aac..f2f8e19 100644 --- a/include/boost/container/experimental/hub.hpp +++ b/include/boost/container/hub.hpp @@ -942,6 +942,34 @@ struct block_typedefs } /* namespace container::hub_detail */ +//! 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 are stored in \e blocks of contiguous memory, each with a fixed +//! capacity of 64 elements. 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. +//! +//! 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. template class hub: empty_value< typename hub_detail::block_typedefs::block_allocator, 0> @@ -969,11 +997,25 @@ public: using reverse_iterator = std::reverse_iterator; using const_reverse_iterator = std::reverse_iterator; + //! Effects: Constructs an empty hub, using \c Allocator() as the allocator. + //! + //! Requires: \c Allocator is DefaultConstructible. + //! + //! Complexity: Constant. hub() noexcept(noexcept(Allocator())): hub{Allocator()} {} + //! Effects: Constructs an empty hub, using the specified allocator. + //! + //! Complexity: Constant. explicit hub(const Allocator& al_) noexcept: allocator_base{empty_init, 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& al_ = Allocator()): hub{al_} { range_insert_impl(size_type(0), n, [&, this] (T* p, size_type) { @@ -981,11 +1023,21 @@ public: }); } + //! 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& al_ = Allocator()): 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, typename = hub_detail::enable_if_is_input_iterator_t @@ -998,6 +1050,10 @@ public: } #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 R> hub(from_range_t, R&& rg, const Allocator& al_ = Allocator()): hub{al_} { @@ -1005,23 +1061,67 @@ public: } #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, allocator_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 hub_detail::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(std::move(x.al())), std::true_type{}} {} + //! Effects: Allocator-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 hub_detail::type_identity_t& al_): hub{std::move(x), al_, allocator_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& al_ = Allocator()): 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 = @@ -1041,6 +1141,20 @@ public: 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( allocator_propagate_on_container_move_assignment_t::value || @@ -1058,12 +1172,19 @@ public: 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, typename = hub_detail::enable_if_is_input_iterator_t @@ -1077,6 +1198,10 @@ public: } #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 R> void assign_range(R&& rg) { @@ -1087,6 +1212,9 @@ public: } #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( @@ -1095,14 +1223,27 @@ public: [&] (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()}; } @@ -1114,9 +1255,19 @@ public: 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 @@ -1126,8 +1277,24 @@ public: (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: std::length_error 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()) { @@ -1137,14 +1304,40 @@ public: 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; @@ -1158,6 +1351,16 @@ public: } } + //! 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_FORCEINLINE iterator emplace(Args&&... args) { @@ -1176,12 +1379,25 @@ public: 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_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_FORCEINLINE iterator insert(const T& x) { return emplace(x); } BOOST_FORCEINLINE iterator insert(const_iterator, const T& x) { return emplace(x); } @@ -1189,9 +1405,21 @@ public: BOOST_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 *this. + //! + //! Complexity: Linear in the number of elements inserted; one object of + //! type T is constructed per element. template R> void insert_range(R&& rg) { @@ -1201,6 +1429,14 @@ public: } #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 *this. + //! + //! Complexity: Linear in the number of elements inserted; one object of + //! type T is constructed per element. template< typename InputIterator, typename = hub_detail::enable_if_is_input_iterator_t @@ -1212,6 +1448,12 @@ public: }); } + //! 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) { @@ -1219,6 +1461,15 @@ public: }); } + //! 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_FORCEINLINE iterator erase(const_iterator pos) { auto pbb = pos.pbb; @@ -1228,11 +1479,28 @@ public: 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_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; ) { @@ -1257,6 +1525,10 @@ public: return {last.pbb, last.n}; } + //! Effects: Exchanges the contents and capacity() of *this with those + //! of x. + //! + //! Complexity: Constant. void swap(hub& x) noexcept( allocator_propagate_on_container_swap_t::value || @@ -1276,8 +1548,21 @@ public: 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); @@ -1299,8 +1584,24 @@ public: } } + //! 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()) { @@ -1321,6 +1622,17 @@ public: #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()) { @@ -1351,6 +1663,14 @@ public: #pragma warning(pop) /* C4127 */ #endif + //! Effects: Returns an iterator (or 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. iterator get_iterator(const_pointer p) { std::less less; @@ -1862,6 +2182,9 @@ hub(from_range_t, R&&, Allocator = Allocator()) #endif #endif +//! Effects: Equivalent to x.swap(y). +//! +//! Complexity: Constant. template void swap(hub& x, hub& y) noexcept(noexcept(x.swap(y))) @@ -1869,6 +2192,12 @@ void swap(hub& x, hub& 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) @@ -1877,6 +2206,14 @@ erase(hub& x, const U& value) 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) @@ -1900,6 +2237,16 @@ erase_if(hub& x, Predicate pred) 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 F for_each( hub_detail::iterator first, hub_detail::iterator last, @@ -1912,6 +2259,13 @@ F for_each( 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 F for_each(hub& x, F f) { @@ -1926,6 +2280,18 @@ F for_each(const hub& x, F 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 std::pair, F> for_each_while( hub_detail::iterator first, hub_detail::iterator last, @@ -1945,6 +2311,15 @@ std::pair, F> for_each_while( return {first, std::move(f)}; } +//! 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)