◀︎

P3933R0: constexpr hive

This paper makes std::hive usable during constant evaluation, resolving NB comment CZ 1-231 23.3.8 [hive] make std::hive constexpr and making everything in section containers consistently marked constexpr.

Motivation

C++ users are often surprised by missing functionality available during constant evaluation, sometimes there is no real reason in the language, it's just no one wrote a paper making the thing constexpr. Anecdotally this is often surprise to Jason Turner's students when they start experimenting with constexpr code.

This paper rectify this, and makes section [containers] fully constexpr compatible. This is also design intent of P3372: constexpr containers and adapters which made all containers marked constexpr, std::hive which got merged after this paper is not marked constexpr.

We believe it's important to have it constexpr now otherwise a valid implementation strategy would make future addition of constexpr ABI break (by forcing specific layout).

History of std::hive and constexpr

Implementation

NylteJ provided implementation of constexpr std::hive in his fork of MS STL, and informed me under the issue for std::hive implementation tracking in MS STL where Steven T. Lavavej made a note that whoever imlements it, should make it sure it can be constexpr compatible. According the author only problematic thing is ::get_iterator(const T *) functionality.

get_iterator and comparing of unrelated pointers

Wording requirement for get_iterator is to be linear with number of blocks allocated. During constant evaluation there is no global ordering of pointers (even in runtime it's just implementation defined behaviour). And implementation can't blindly compare pointers to find in which block and calculate offset to get the iterator. So for now this paper doesn't propose making this function marked constexpr.

Alternative would be ignore complexity requirement during constant evaluation and do a linear scan. But that's really suboptimal. For a future language and library development we need to get a functionality to check if two pointers are associated with same object. Then we can calculate differences of this pointer and start of the block, so we can calculate index of the item for our iterator.

to get_iterator or not to get_iterator now

I wrote a proposal P3852 (alternative link here) to create a query mechanism to check if a pointer belongs to a memory area between two pointers. This needs a compiler magic builtin. Basically we have two options, add constexpr now to get_iterator or wait for C++29 with proposed functionality and add it later. The check is implemented this way:

constexpr bool is_pointer_between(const void * ptr, const void * first, const void * last) {
	if consteval {
		if (!__builtin_pointer_related(first, ptr) || !__builtin_pointer_related(ptr, last)) {
			return false;
		}
	}
	return first <= ptr && ptr < last;
}

This or a similar builtin mechanism can land indepedently in compilers and it wouldn't be first time a library functionality needed some magic.

Wording

Wording adds constexpr to all functions and member functions in [hive.syn] and [hive]. Following diff is made against version of C++26's draft from November 8th, so it probably won't contain changes applied after the Kona meeting, if there is a conflict, intention is to just add constexpr everywhere.

23.3.8 Header <hive> synopsis [hive.syn]

#include <initializer_list> // see [initializer.list.syn] #include <compare> // see [compare.syn] namespace std { struct hive_limits { size_t min; size_t max; constexpr hive_limits(size_t minimum, size_t maximum) noexcept : min(minimum), max(maximum) {} }; // [hive], class template hive template<class T, class Allocator = allocator<T>> class hive; template<class T, class Allocator> constexpr void swap(hive<T, Allocator>& x, hive<T, Allocator>& y) noexcept(noexcept(x.swap(y))); template<class T, class Allocator, class U = T> constexpr typename hive<T, Allocator>::size_type erase(hive<T, Allocator>& c, const U& value); template<class T, class Allocator, class Predicate> constexpr typename hive<T, Allocator>::size_type erase_if(hive<T, Allocator>& c, Predicate pred); namespace pmr { template<class T> using hive = std::hive<T, polymorphic_allocator<T>>; } }

23.3.9 Class template hive [hive]

A hive is a type of sequence container that provides constant-time insertion and erasure operations.
Storage is automatically managed in multiple memory blocks, referred to as element blocks.
Insertion position is determined by the container, and insertion may re-use the memory locations of erased elements.
Element blocks which contain elements are referred to as active blocks, those which do not are referred to as reserved blocks.
Active blocks which become empty of elements are either deallocated or become reserved blocks.
Reserved blocks become active blocks when they are used to store elements.
A user can create additional reserved blocks by calling reserve.
Erasures use unspecified techniques of constant time complexity to identify the memory locations of erased elements, which are subsequently skipped during iteration, as opposed to relocating subsequent elements during erasure.
Active block capacities have an implementation-defined growth factor (which need not be integral), for example a new active block's capacity could be equal to the summed capacities of the pre-existing active blocks.
Limits can be placed on both the minimum and maximum element capacities of element blocks, both by users and implementations.
  • The minimum limit shall be no larger than the maximum limit.
  • When limits are not specified by a user during construction, the implementation's default limits are used.
  • The default limits of an implementation are not guaranteed to be the same as the minimum and maximum possible capacities for an implementation's element blocks.
    [Note 1: 
    To allow latitude for both implementation-specific and user-directed optimization.
    — end note]
    The latter are defined as hard limits.
    The maximum hard limit shall be no larger than std​::​allocator_traits<Allocator>​::​max_size().
  • If user-specified limits are not within hard limits, or if the specified minimum limit is greater than the specified maximum limit, the behavior is undefined.
  • An element block is said to be within the bounds of a pair of minimum/maximum limits when its capacity is greater-or-equal-to the minimum limit and less-than-or-equal-to the maximum limit.
A hive conforms to the requirements for containers ([container.reqmts]), with the exception of operators == and !=.
A hive also meets the requirements of a reversible container ([container.rev.reqmts]), of an allocator-aware container ([container.alloc.reqmts]), and some of the requirements of a sequence container ([sequence.reqmts]).
Descriptions are provided here only for operations on hive that are not described in that table or for operations where there is additional semantic information.
The iterators of hive meet the Cpp17BidirectionalIterator requirements but also model three_way_comparable<strong_ordering>.
namespace std { template<class T, class Allocator = allocator<T>> class hive { public: // types using value_type = T; using allocator_type = Allocator; using pointer = allocator_traits<Allocator>::pointer; using const_pointer = allocator_traits<Allocator>::const_pointer; using reference = value_type&; using const_reference = const value_type&; using size_type = implementation-defined; // see [container.requirements] using difference_type = implementation-defined; // see [container.requirements] using iterator = implementation-defined; // see [container.requirements] using const_iterator = implementation-defined; // see [container.requirements] using reverse_iterator = std::reverse_iterator<iterator>; // see [container.requirements] using const_reverse_iterator = std::reverse_iterator<const_iterator>; // see [container.requirements] // [hive.cons], construct/copy/destroy constexpr hive() noexcept(noexcept(Allocator())) : hive(Allocator()) {} constexpr explicit hive(const Allocator&) noexcept; constexpr explicit hive(hive_limits block_limits) : hive(block_limits, Allocator()) {} constexpr hive(hive_limits block_limits, const Allocator&); constexpr explicit hive(size_type n, const Allocator& = Allocator()); constexpr hive(size_type n, hive_limits block_limits, const Allocator& = Allocator()); constexpr hive(size_type n, const T& value, const Allocator& = Allocator()); constexpr hive(size_type n, const T& value, hive_limits block_limits, const Allocator& = Allocator()); template<class InputIterator> constexpr hive(InputIterator first, InputIterator last, const Allocator& = Allocator()); template<class InputIterator> constexpr hive(InputIterator first, InputIterator last, hive_limits block_limits, const Allocator& = Allocator()); template<container-compatible-range<T> R> constexpr hive(from_range_t, R&& rg, const Allocator& = Allocator()); template<container-compatible-range<T> R> constexpr hive(from_range_t, R&& rg, hive_limits block_limits, const Allocator& = Allocator()); constexpr hive(const hive& x); constexpr hive(hive&&) noexcept; constexpr hive(const hive& x, const type_identity_t<Allocator>& alloc); constexpr hive(hive&&, const type_identity_t<Allocator>& alloc); constexpr hive(initializer_list<T> il, const Allocator& = Allocator()); constexpr hive(initializer_list<T> il, hive_limits block_limits, const Allocator& = Allocator()); constexpr ~hive(); constexpr hive& operator=(const hive& x); constexpr hive& operator=(hive&& x) noexcept(see below); constexpr hive& operator=(initializer_list<T>); template<class InputIterator> constexpr void assign(InputIterator first, InputIterator last); template<container-compatible-range<T> R> constexpr void assign_range(R&& rg); constexpr void assign(size_type n, const T& t); constexpr void assign(initializer_list<T>); constexpr allocator_type get_allocator() const noexcept; // iterators constexpr iterator begin() noexcept; constexpr const_iterator begin() const noexcept; constexpr iterator end() noexcept; constexpr const_iterator end() const noexcept; constexpr reverse_iterator rbegin() noexcept; constexpr const_reverse_iterator rbegin() const noexcept; constexpr reverse_iterator rend() noexcept; constexpr const_reverse_iterator rend() const noexcept; constexpr const_iterator cbegin() const noexcept; constexpr const_iterator cend() const noexcept; constexpr const_reverse_iterator crbegin() const noexcept; constexpr const_reverse_iterator crend() const noexcept; // [hive.capacity], capacity constexpr bool empty() const noexcept; constexpr size_type size() const noexcept; constexpr size_type max_size() const noexcept; constexpr size_type capacity() const noexcept; constexpr void reserve(size_type n); constexpr void shrink_to_fit(); constexpr void trim_capacity() noexcept; constexpr void trim_capacity(size_type n) noexcept; constexpr hive_limits block_capacity_limits() const noexcept; static constexpr hive_limits block_capacity_default_limits() noexcept; static constexpr hive_limits block_capacity_hard_limits() noexcept; constexpr void reshape(hive_limits block_limits); // [hive.modifiers], modifiers template<class... Args> constexpr iterator emplace(Args&&... args); template<class... Args> constexpr iterator emplace_hint(const_iterator hint, Args&&... args); constexpr iterator insert(const T& x); constexpr iterator insert(T&& x); constexpr iterator insert(const_iterator hint, const T& x); constexpr iterator insert(const_iterator hint, T&& x); constexpr void insert(initializer_list<T> il); template<container-compatible-range<T> R> constexpr void insert_range(R&& rg); template<class InputIterator> constexpr void insert(InputIterator first, InputIterator last); constexpr void insert(size_type n, const T& x); constexpr iterator erase(const_iterator position); constexpr iterator erase(const_iterator first, const_iterator last); constexpr void swap(hive&) noexcept(see below); constexpr void clear() noexcept; // [hive.operations], hive operations constexpr void splice(hive& x); constexpr void splice(hive&& x); template<class BinaryPredicate = equal_to<T>> constexpr size_type unique(BinaryPredicate binary_pred = BinaryPredicate()); template<class Compare = less<T>> constexpr void sort(Compare comp = Compare()); iterator get_iterator(const_pointer p) noexcept; const_iterator get_iterator(const_pointer p) const noexcept; private: hive_limits current-limits = implementation-defined; // exposition only }; template<class InputIterator, class Allocator = allocator<iter-value-type<InputIterator>>> hive(InputIterator, InputIterator, Allocator = Allocator()) -> hive<iter-value-type<InputIterator>, Allocator>; template<class InputIterator, class Allocator = allocator<iter-value-type<InputIterator>>> hive(InputIterator, InputIterator, hive_limits, Allocator = Allocator()) -> hive<iter-value-type<InputIterator>, Allocator>; template<ranges::input_range R, class Allocator = allocator<ranges::range_value_t<R>>> hive(from_range_t, R&&, Allocator = Allocator()) -> hive<ranges::range_value_t<R>, Allocator>; template<ranges::input_range R, class Allocator = allocator<ranges::range_value_t<R>>> hive(from_range_t, R&&, hive_limits, Allocator = Allocator()) -> hive<ranges::range_value_t<R>, Allocator>; }

23.3.9.2 Constructors, copy, and assignment [hive.cons]

constexpr explicit hive(const Allocator&) noexcept;
Effects: Constructs an empty hive, using the specified allocator.
Complexity: Constant.
constexpr hive(hive_limits block_limits, const Allocator&);
Effects: Constructs an empty hive, using the specified allocator.
Initializes current-limits with block_limits.
Complexity: Constant.
constexpr explicit hive(size_type n, const Allocator& = Allocator()); constexpr hive(size_type n, hive_limits block_limits, const Allocator& = Allocator());
Preconditions: T is Cpp17DefaultInsertable into hive.
Effects: Constructs a hive with n default-inserted elements, using the specified allocator.
If the second overload is called, also initializes current-limits with block_limits.
Complexity: Linear in n.
constexpr hive(size_type n, const T& value, const Allocator& = Allocator()); constexpr hive(size_type n, const T& value, hive_limits block_limits, const Allocator& = Allocator());
Preconditions: T is Cpp17CopyInsertable into hive.
Effects: Constructs a hive with n copies of value, using the specified allocator.
If the second overload is called, also initializes current-limits with block_limits.
Complexity: Linear in n.
template<class InputIterator> constexpr hive(InputIterator first, InputIterator last, const Allocator& = Allocator()); template<class InputIterator> constexpr hive(InputIterator first, InputIterator last, hive_limits block_limits, const Allocator& = Allocator());
Effects: Constructs a hive equal to the range [first, last), using the specified allocator.
If the second overload is called, also initializes current-limits with block_limits.
Complexity: Linear in distance(first, last).
template<container-compatible-range<T> R> constexpr hive(from_range_t, R&& rg, const Allocator& = Allocator()); template<container-compatible-range<T> R> constexpr hive(from_range_t, R&& rg, hive_limits block_limits, const Allocator& = Allocator());
Effects: Constructs a hive object with the elements of the range rg, using the specified allocator.
If the second overload is called, also initializes current-limits with block_limits.
Complexity: Linear in ranges​::​distance(rg).
constexpr hive(const hive& x); constexpr hive(const hive& x, const type_identity_t<Allocator>& alloc);
Preconditions: T is Cpp17CopyInsertable into hive.
Effects: Constructs a hive object with the elements of x.
If the second overload is called, uses alloc.
Initializes current-limits with x.current-limits.
Complexity: Linear in x.size().
constexpr hive(hive&& x) noexcept; constexpr hive(hive&& x, const type_identity_t<Allocator>& alloc);
Preconditions: For the second overload, when allocator_traits<alloc>​::​is_always_equal​::​value is false, T meets the Cpp17MoveInsertable requirements.
Effects: When the first overload is called, or the second overload is called and alloc == x.get_allocator() is true, current-limits is set to x.current-limits and each element block is moved from x into *this.
Pointers and references to the elements of x now refer to those same elements but as members of *this.
Iterators referring to the elements of x will continue to refer to their elements, but they now behave as iterators into *this.
If the second overload is called and alloc == x.get_allocator() is false, each element in x is moved into *this.
References, pointers and iterators referring to the elements of x, as well as the past-the-end iterator of x, are invalidated.
Postconditions: x.empty() is true.
Complexity: If the second overload is called and alloc == x.get_allocator() is false, linear in x.size().
Otherwise constant.
constexpr hive(initializer_list<T> il, const Allocator& = Allocator()); constexpr hive(initializer_list<T> il, hive_limits block_limits, const Allocator& = Allocator());
Preconditions: T is Cpp17CopyInsertable into hive.
Effects: Constructs a hive object with the elements of il, using the specified allocator.
If the second overload is called, also initializes current-limits with block_limits.
Complexity: Linear in il.size().
constexpr hive& operator=(const hive& x);
Preconditions: T is Cpp17CopyInsertable into hive and Cpp17CopyAssignable.
Effects: All elements in *this are either copy-assigned to, or destroyed.
All elements in x are copied into *this.
[Note 1: 
current-limits is unchanged.
— end note]
Complexity: Linear in size() + x.size().
constexpr hive& operator=(hive&& x) noexcept(allocator_traits<Allocator>::propagate_on_container_move_assignment::value || allocator_traits<Allocator>::is_always_equal::value);
Preconditions: When (allocator_traits<Allocator>::propagate_on_container_move_assignment::value || allocator_traits<Allocator>::is_always_equal::value) is false, T is Cpp17MoveInsertable into hive and Cpp17MoveAssignable.
Effects: Each element in *this is either move-assigned to, or destroyed.
When (allocator_traits<Allocator>::propagate_on_container_move_assignment::value || get_allocator() == x.get_allocator()) is true, current-limits is set to x.current-limits and each element block is moved from x into *this.
Pointers and references to the elements of x now refer to those same elements but as members of *this.
Iterators referring to the elements of x will continue to refer to their elements, but they now behave as iterators into *this, not into x.
When (allocator_traits<Allocator>::propagate_on_container_move_assignment::value || get_allocator() == x.get_allocator()) is false, each element in x is moved into *this.
References, pointers and iterators referring to the elements of x, as well as the past-the-end iterator of x, are invalidated.
Postconditions: x.empty() is true.
Complexity: Linear in size().
If (allocator_traits<Allocator>::propagate_on_container_move_assignment::value || get_allocator() == x.get_allocator()) is false, also linear in x.size().
constexpr size_type capacity() const noexcept;
Returns: The total number of elements that *this can hold without requiring allocation of more element blocks.
Complexity: Constant.
constexpr void reserve(size_type n);
Effects: If n <= capacity() is true, there are no effects.
Otherwise increases capacity() by allocating reserved blocks.
Postconditions: capacity() >= n is true.
Throws: length_error if n > max_size(), as well as any exceptions thrown by the allocator.
Complexity: Linear in the number of reserved blocks allocated.
Remarks: The size of the sequence is not changed.
All references, pointers, and iterators referring to elements in *this, as well as the past-the-end iterator, remain valid.
constexpr void shrink_to_fit();
Preconditions: T is Cpp17MoveInsertable into hive.
Effects: shrink_to_fit is a non-binding request to reduce capacity() to be closer to size().
[Note 1: 
The request is non-binding to allow latitude for implementation-specific optimizations.
— end note]
It does not increase capacity(), but may reduce capacity().
It may reallocate elements.
If capacity() is already equal to size(), there are no effects.
If an exception is thrown during allocation of a new element block, capacity() may be reduced and reallocation may occur.
Otherwise if an exception is thrown, the effects are unspecified.
Complexity: If reallocation happens, linear in the size of the sequence.
Remarks: If reallocation happens, the order of the elements in *this may change and all references, pointers, and iterators referring to the elements in *this, as well as the past-the-end iterator, are invalidated.
constexpr void trim_capacity() noexcept; constexpr void trim_capacity(size_type n) noexcept;
Effects: For the first overload, all reserved blocks are deallocated, and capacity() is reduced accordingly.
For the second overload, capacity() is reduced to no less than n.
Complexity: Linear in the number of reserved blocks deallocated.
Remarks: All references, pointers, and iterators referring to elements in *this, as well as the past-the-end iterator, remain valid.
constexpr hive_limits block_capacity_limits() const noexcept;
Returns: current-limits.
Complexity: Constant.
static constexpr hive_limits block_capacity_default_limits() noexcept;
Returns: A hive_limits struct with the min and max members set to the implementation's default limits.
Complexity: Constant.
static constexpr hive_limits block_capacity_hard_limits() noexcept;
Returns: A hive_limits struct with the min and max members set to the implementation's hard limits.
Complexity: Constant.
constexpr void reshape(hive_limits block_limits);
Preconditions: T is Cpp17MoveInsertable into hive.
Effects: For any active blocks not within the bounds of block_limits, the elements within those active blocks are reallocated to new or existing element blocks which are within the bounds.
Any element blocks not within the bounds of block_limits are deallocated.
If an exception is thrown during allocation of a new element block, capacity() may be reduced, reallocation may occur, and current-limits may be assigned a value other than block_limits.
Otherwise block_limits is assigned to current-limits.
If any other exception is thrown the effects are unspecified.
Postconditions: size() is unchanged.
Complexity: Linear in the number of element blocks in *this.
If reallocation happens, also linear in the number of elements reallocated.
Remarks: This operation may change capacity().
If reallocation happens, the order of the elements in *this may change.
Reallocation invalidates all references, pointers, and iterators referring to the elements in *this, as well as the past-the-end iterator.
[Note 2: 
If no reallocation happens, they remain valid.
— end note]
template<class... Args> constexpr iterator emplace(Args&&... args); template<class... Args> constexpr iterator emplace_hint(const_iterator hint, Args&&... args);
Preconditions: T is Cpp17EmplaceConstructible into hive from args.
Effects: Inserts an object of type T constructed with std​::​forward<Args>(args)....
The hint parameter is ignored.
If an exception is thrown, there are no effects.
[Note 1: 
args can directly or indirectly refer to a value in *this.
— end note]
Returns: An iterator that points to the new element.
Complexity: Constant.
Exactly one object of type T is constructed.
Remarks: Invalidates the past-the-end iterator.
constexpr iterator insert(const T& x); constexpr iterator insert(const_iterator hint, const T& x); constexpr iterator insert(T&& x); constexpr iterator insert(const_iterator hint, T&& x);
Effects: Equivalent to: return emplace(std​::​forward<decltype(x)>(x));
[Note 2: 
The hint parameter is ignored.
— end note]
constexpr void insert(initializer_list<T> rg); template<container-compatible-range<T> R> constexpr void insert_range(R&& rg);
Preconditions: T is Cpp17EmplaceInsertable into hive from *ranges​::​begin(rg).
rg and *this do not overlap.
Effects: Inserts copies of elements in rg.
Each iterator in the range rg is dereferenced exactly once.
Complexity: Linear in the number of elements inserted.
Exactly one object of type T is constructed for each element inserted.
Remarks: If an element is inserted, invalidates the past-the-end iterator.
constexpr void insert(size_type n, const T& x);
Preconditions: T is Cpp17CopyInsertable into hive.
Effects: Inserts n copies of x.
Complexity: Linear in n.
Exactly one object of type T is constructed for each element inserted.
Remarks: If an element is inserted, invalidates the past-the-end iterator.
template<class InputIterator> constexpr void insert(InputIterator first, InputIterator last);
Effects: Equivalent to insert_range(ranges​::​subrange(first, last)).
constexpr iterator erase(const_iterator position); constexpr iterator erase(const_iterator first, const_iterator last);
Complexity: Linear in the number of elements erased.
Additionally, if any active blocks become empty of elements as a result of the function call, at worst linear in the number of element blocks.
Remarks: Invalidates references, pointers and iterators referring to the erased elements.
An erase operation that erases the last element in *this also invalidates the past-the-end iterator.
constexpr void swap(hive& x) noexcept(allocator_traits<Allocator>::propagate_on_container_swap::value || allocator_traits<Allocator>::is_always_equal::value);
Effects: Exchanges the contents, capacity(), and current-limits of *this with that of x.
Complexity: Constant.
In this subclause, arguments for a template parameter named Predicate or BinaryPredicate shall meet the corresponding requirements in [algorithms.requirements].
The semantics of i + n and i - n, where i is an iterator into the hive and n is an integer, are the same as those of next(i, n) and prev(i, n), respectively.
For sort, the definitions and requirements in [alg.sorting] apply.
constexpr void splice(hive& x); constexpr void splice(hive&& x);
Preconditions: get_allocator() == x.get_allocator() is true.
Effects: If addressof(x) == this is true, the behavior is erroneous and there are no effects.
Otherwise, inserts the contents of x into *this and x becomes empty.
Pointers and references to the moved elements of x now refer to those same elements but as members of *this.
Iterators referring to the moved elements continue to refer to their elements, but they now behave as iterators into *this, not into x.
Throws: length_error if any of x's active blocks are not within the bounds of current-limits.
Complexity: Linear in the sum of all element blocks in x plus all element blocks in *this.
Remarks: Reserved blocks in x are not transferred into *this.
If addressof(x) == this is false, invalidates the past-the-end iterator for both x and *this.
template<class BinaryPredicate = equal_to<T>> constexpr size_type unique(BinaryPredicate binary_pred = BinaryPredicate());
Preconditions: binary_pred is an equivalence relation.
Effects: Erases all but the first element from every consecutive group of equivalent elements.
That is, for a nonempty hive, erases all elements referred to by the iterator i in the range [begin() + 1, end()) for which binary_pred(*i, *(i - 1)) is true.
Returns: The number of elements erased.
Throws: Nothing unless an exception is thrown by the predicate.
Complexity: If empty() is false, exactly size() - 1 applications of the corresponding predicate, otherwise no applications of the predicate.
Remarks: Invalidates references, pointers, and iterators referring to the erased elements.
If the last element in *this is erased, also invalidates the past-the-end iterator.
template<class Compare = less<T>> constexpr void sort(Compare comp = Compare());
Preconditions: T is Cpp17MoveInsertable into hive, Cpp17MoveAssignable, and Cpp17Swappable.
Effects: Sorts *this according to the comp function object.
If an exception is thrown, the order of the elements in *this is unspecified.
Complexity: comparisons, where N is size().
Remarks: May allocate.
References, pointers, and iterators referring to elements in *this, as well as the past-the-end iterator, may be invalidated.
[Note 1: 
Not required to be stable[algorithm.stable].
— end note]
iterator get_iterator(const_pointer p) noexcept; const_iterator get_iterator(const_pointer p) const noexcept;
Preconditions: p points to an element in *this.
Returns: An iterator or const_iterator pointing to the same element as p.
Complexity: Linear in the number of active blocks in *this.
template<class T, class Allocator, class U = T> constexpr typename hive<T, Allocator>::size_type erase(hive<T, Allocator>& c, const U& value);
Effects: Equivalent to: return erase_if(c, [&](const auto& elem) -> bool { return elem == value; });
template<class T, class Allocator, class Predicate> constexpr typename hive<T, Allocator>::size_type erase_if(hive<T, Allocator>& c, Predicate pred);
Effects: Equivalent to: auto original_size = c.size(); for (auto i = c.begin(), last = c.end(); i != last; ) { if (pred(*i)) { i = c.erase(i); } else { ++i; } } return original_size - c.size();

Feature test macro

17.3.2 Header <version> synopsis [version.syn]

#define __cpp_lib_constexpr_hive 2026??L // also in <hive>