Created
July 14, 2026 01:58
-
-
Save Pugemon/7bf971044b2901fc0aa1d22cc2cda202 to your computer and use it in GitHub Desktop.
Modern C++20 allocator-aware SmallVec with Small Buffer Optimization (SBO), trivial relocation, and strict exception safety.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #pragma once | |
| #include <algorithm> | |
| #include <compare> | |
| #include <cstddef> | |
| #include <cstring> | |
| #include <initializer_list> | |
| #include <iostream> | |
| #include <iterator> | |
| #include <memory> | |
| #include <type_traits> | |
| #include <utility> | |
| #if defined(__GNUC__) || defined(__clang__) | |
| #define CD_FORCEINLINE __attribute__((always_inline)) inline | |
| #define CD_NOINLINE __attribute__((noinline)) | |
| #define CD_PURE_FN __attribute__((pure)) | |
| #define CD_ARTIFICIAL __attribute__((artificial)) | |
| #define CD_HOT __attribute__((hot)) | |
| #define CD_COLD __attribute__((cold)) | |
| #define CD_RESTRICT __restrict__ | |
| #define CD_UNLIKELY(x) __builtin_expect(!!(x), 0) | |
| #define CD_LIKELY(x) __builtin_expect(!!(x), 1) | |
| #define CD_DEBUG_BREAK() __builtin_trap() | |
| #elif defined(_MSC_VER) | |
| #define CD_FORCEINLINE __forceinline | |
| #define CD_NOINLINE __declspec(noinline) | |
| #define CD_PURE_FN | |
| #define CD_ARTIFICIAL | |
| #define CD_HOT | |
| #define CD_COLD | |
| #define CD_RESTRICT __restrict | |
| #define CD_UNLIKELY(x) (x) | |
| #define CD_LIKELY(x) (x) | |
| #define CD_DEBUG_BREAK() __debugbreak() | |
| #else | |
| #define CD_FORCEINLINE inline | |
| #define CD_NOINLINE | |
| #define CD_PURE_FN | |
| #define CD_ARTIFICIAL | |
| #define CD_HOT | |
| #define CD_COLD | |
| #define CD_RESTRICT | |
| #define CD_UNLIKELY(x) (x) | |
| #define CD_LIKELY(x) (x) | |
| #define CD_DEBUG_BREAK() std::abort() | |
| #endif | |
| #if !defined(NDEBUG) | |
| #define CD_DEBUG 1 | |
| #define CD_ASSERT(expr, msg) \ | |
| do { \ | |
| if (!(expr)) { \ | |
| std::cerr << "[ASSERT FAILED] " << #expr << "\n" \ | |
| << "Message: " << (msg) << "\n" \ | |
| << "File: " << __FILE__ << ":" << __LINE__ << "\n"; \ | |
| CD_DEBUG_BREAK(); \ | |
| } \ | |
| } while (false) | |
| #define CD_ASSUME(expr) CD_ASSERT(expr, "Assumption violated") | |
| #else | |
| #define CD_DEBUG 0 | |
| #define CD_ASSERT(expr, msg) \ | |
| do { \ | |
| (void)sizeof(expr); \ | |
| } while (false) | |
| #if defined(__clang__) | |
| #define CD_ASSUME(expr) __builtin_assume(expr) | |
| #elif defined(_MSC_VER) | |
| #define CD_ASSUME(expr) __assume(expr) | |
| #elif defined(__GNUC__) | |
| #define CD_ASSUME(expr) \ | |
| do { \ | |
| if (!(expr)) __builtin_unreachable(); \ | |
| } while (false) | |
| #else | |
| #define CD_ASSUME(expr) \ | |
| do { \ | |
| (void)sizeof(expr); \ | |
| } while (false) | |
| #endif | |
| #endif | |
| namespace cd::containers { | |
| /// @brief An allocator-aware vector container with an inline local buffer. | |
| /// | |
| /// @details Stores up to `N` elements directly within the object footprint (SBO), | |
| /// falling back to heap allocation only when the inline capacity is exceeded. | |
| /// | |
| /// @note **The Cost of SBO**: Moving or copying a local container (where `data_ == local_buf_`) | |
| /// is an `O(M)` operation (where `M` is `size()`), as elements must be moved/copied individually. | |
| /// Pointer swaps are only used for heap-allocated containers. | |
| /// | |
| /// @note **Trivial Relocation**: If `std::is_trivially_copyable_v<T>` is `true`, reallocations | |
| /// (`grow` and `shrink_to_fit`) bypass move-constructors and destructors, utilizing `std::memcpy`. | |
| /// | |
| /// @note **Aliasing-Safe Shifts**: Elements are shifted using standard `std::move` and | |
| /// `std::move_backward` to safely handle overlapping memory during mutating operations. | |
| /// | |
| /// @warning **Cache Pressure**: Best suited for temporary stack buffers or small collections | |
| /// where the expected element count is `<= N`. Large values of `N` or nesting `SmallVec` inside | |
| /// other containers can cause significant cache pressure due to the increased object footprint. | |
| /// | |
| /// @warning **Self-Insertion**: Self-insertion (e.g., `vec.push_back(vec[0])`) under reallocation | |
| /// is safe, guarded by internal bounds checks. | |
| /// | |
| /// @tparam T The type of the elements. | |
| /// @tparam N The capacity of the inline local buffer. Must be > 0. | |
| /// @tparam Allocator The allocator to use when falling back to the heap. | |
| template <typename T, size_t N, typename Allocator = std::allocator<T>> | |
| class SmallVec { | |
| static_assert(N > 0, "SmallVec capacity must be greater than 0"); | |
| public: | |
| using value_type = T; | |
| using allocator_type = Allocator; | |
| using size_type = size_t; | |
| using difference_type = ptrdiff_t; | |
| using reference = T&; | |
| using const_reference = const T&; | |
| using pointer = T*; | |
| using const_pointer = const T*; | |
| using iterator = T*; | |
| using const_iterator = const T*; | |
| using reverse_iterator = std::reverse_iterator<iterator>; | |
| using const_reverse_iterator = std::reverse_iterator<const_iterator>; | |
| private: | |
| using alloc_traits = std::allocator_traits<Allocator>; | |
| [[no_unique_address]] Allocator alloc_; | |
| pointer data_{nullptr}; | |
| pointer end_{nullptr}; | |
| pointer cap_end_{nullptr}; | |
| alignas(T) std::byte local_buf_[sizeof(T) * N]; | |
| [[nodiscard]] CD_PURE_FN CD_FORCEINLINE CD_ARTIFICIAL constexpr bool is_local() const noexcept { | |
| return data_ == reinterpret_cast<const_pointer>(local_buf_); | |
| } | |
| [[nodiscard]] CD_PURE_FN CD_FORCEINLINE CD_ARTIFICIAL bool is_in_buffer(const_pointer ptr) const noexcept { | |
| return ptr >= data_ && ptr < end_; | |
| } | |
| CD_FORCEINLINE void init_local() noexcept { | |
| data_ = reinterpret_cast<pointer>(local_buf_); | |
| end_ = data_; | |
| cap_end_ = data_ + N; | |
| } | |
| CD_FORCEINLINE void deallocate_if_heap() noexcept { | |
| if (!is_local()) { | |
| alloc_traits::deallocate(alloc_, data_, capacity()); | |
| } | |
| } | |
| CD_COLD CD_NOINLINE void move_to_heap(const size_t new_cap) { | |
| pointer new_data = alloc_traits::allocate(alloc_, new_cap); | |
| const size_t current_size = size(); | |
| if constexpr (std::is_trivially_copyable_v<T>) { | |
| if (current_size > 0) { | |
| std::memcpy(new_data, data_, current_size * sizeof(T)); | |
| } | |
| } else { | |
| try { | |
| if constexpr (std::is_nothrow_move_constructible_v<T>) { | |
| std::uninitialized_move(data_, end_, new_data); | |
| } else { | |
| std::uninitialized_copy(data_, end_, new_data); | |
| } | |
| } catch (...) { | |
| alloc_traits::deallocate(alloc_, new_data, new_cap); | |
| throw; | |
| } | |
| std::destroy(data_, end_); | |
| } | |
| deallocate_if_heap(); | |
| data_ = new_data; | |
| end_ = new_data + current_size; | |
| cap_end_ = new_data + new_cap; | |
| } | |
| CD_COLD CD_NOINLINE void grow(const size_t required_cap) { | |
| const size_t old_cap = capacity(); | |
| const size_t new_cap = std::max(old_cap * 2, required_cap); | |
| move_to_heap(new_cap); | |
| } | |
| CD_FORCEINLINE void insert_impl(size_t index, auto&& val) { | |
| iterator pos = data_ + index; | |
| if (pos == end_) { | |
| std::construct_at(end_, std::forward<decltype(val)>(val)); | |
| } else { | |
| std::construct_at(end_, std::move(*(end_ - 1))); | |
| std::move_backward(pos, end_ - 1, end_); | |
| *pos = std::forward<decltype(val)>(val); | |
| } | |
| } | |
| public: | |
| #pragma region CONSTRUCTORS | |
| SmallVec() noexcept(std::is_nothrow_default_constructible_v<Allocator>) { // NOLINT(*-pro-type-member-init) | |
| init_local(); | |
| } | |
| explicit SmallVec(const size_t count, const Allocator& alloc = Allocator()) // NOLINT(*-pro-type-member-init) | |
| : alloc_(alloc) { | |
| init_local(); | |
| if (CD_UNLIKELY(count > N)) move_to_heap(count); | |
| pointer current = data_; | |
| try { | |
| for (size_t i = 0; i < count; ++i) { | |
| std::construct_at(current++); | |
| } | |
| } catch (...) { | |
| std::destroy(data_, current); | |
| deallocate_if_heap(); | |
| throw; | |
| } | |
| end_ = data_ + count; | |
| } | |
| SmallVec(const size_t count, const T& value, const Allocator& alloc = Allocator()) // NOLINT(*-pro-type-member-init) | |
| : alloc_(alloc) { | |
| init_local(); | |
| if (CD_UNLIKELY(count > N)) move_to_heap(count); | |
| pointer current = data_; | |
| try { | |
| for (size_t i = 0; i < count; ++i) { | |
| std::construct_at(current++, value); | |
| } | |
| } catch (...) { | |
| std::destroy(data_, current); | |
| deallocate_if_heap(); | |
| throw; | |
| } | |
| end_ = data_ + count; | |
| } | |
| SmallVec(std::initializer_list<T> il, const Allocator& alloc = Allocator()) // NOLINT(*-pro-type-member-init) | |
| : alloc_(alloc) { | |
| init_local(); | |
| const size_t count = il.size(); | |
| if (CD_UNLIKELY(count > N)) move_to_heap(count); | |
| pointer current = data_; | |
| try { | |
| for (const auto& val : il) { | |
| std::construct_at(current++, val); | |
| } | |
| } catch (...) { | |
| std::destroy(data_, current); | |
| deallocate_if_heap(); | |
| throw; | |
| } | |
| end_ = data_ + count; | |
| } | |
| template <std::input_iterator It, std::sentinel_for<It> S> | |
| SmallVec(It first, S last, const Allocator& alloc = Allocator()) : alloc_(alloc) { // NOLINT(*-pro-type-member-init) | |
| init_local(); | |
| if constexpr (std::forward_iterator<It>) { | |
| const auto count = static_cast<size_t>(std::distance(first, last)); | |
| if (CD_UNLIKELY(count > N)) move_to_heap(count); | |
| pointer current = data_; | |
| try { | |
| for (auto it = first; it != last; ++it) { | |
| std::construct_at(current++, *it); | |
| } | |
| } catch (...) { | |
| std::destroy(data_, current); | |
| deallocate_if_heap(); | |
| throw; | |
| } | |
| end_ = data_ + count; | |
| } else { | |
| for (; first != last; ++first) { | |
| push_back(*first); | |
| } | |
| } | |
| } | |
| SmallVec(const SmallVec& other) // NOLINT(*-pro-type-member-init) | |
| : alloc_(alloc_traits::select_on_container_copy_construction(other.alloc_)) { | |
| init_local(); | |
| const size_t count = other.size(); | |
| if (CD_UNLIKELY(count > N)) move_to_heap(count); | |
| pointer current = data_; | |
| try { | |
| for (auto it = other.begin(); it != other.end(); ++it) { | |
| std::construct_at(current++, *it); | |
| } | |
| } catch (...) { | |
| std::destroy(data_, current); | |
| deallocate_if_heap(); | |
| throw; | |
| } | |
| end_ = data_ + count; | |
| } | |
| SmallVec(SmallVec&& other) noexcept : alloc_(std::move(other.alloc_)) { // NOLINT(*-pro-type-member-init) | |
| if (other.is_local()) { | |
| init_local(); | |
| std::uninitialized_move(other.begin(), other.end(), data_); | |
| end_ = data_ + other.size(); | |
| other.clear(); | |
| } else { | |
| auto local_ptr = reinterpret_cast<pointer>(other.local_buf_); | |
| data_ = std::exchange(other.data_, local_ptr); | |
| end_ = std::exchange(other.end_, local_ptr); | |
| cap_end_ = std::exchange(other.cap_end_, local_ptr + N); | |
| } | |
| } | |
| ~SmallVec() { | |
| std::destroy(data_, end_); | |
| deallocate_if_heap(); | |
| } | |
| #pragma endregion | |
| #pragma region ASSIGNMENT | |
| SmallVec& operator=(const SmallVec& other) { | |
| if (CD_UNLIKELY(this == &other)) return *this; | |
| SmallVec tmp(other); | |
| swap(tmp); | |
| return *this; | |
| } | |
| SmallVec& operator=(SmallVec&& other) noexcept { | |
| if (CD_UNLIKELY(this == &other)) return *this; | |
| std::destroy(data_, end_); | |
| deallocate_if_heap(); | |
| if constexpr (alloc_traits::propagate_on_container_move_assignment::value) { | |
| alloc_ = std::move(other.alloc_); | |
| if (other.is_local()) { | |
| init_local(); | |
| std::uninitialized_move(other.begin(), other.end(), data_); | |
| end_ = data_ + other.size(); | |
| other.clear(); | |
| } else { | |
| auto local_ptr = reinterpret_cast<pointer>(other.local_buf_); | |
| data_ = std::exchange(other.data_, local_ptr); | |
| end_ = std::exchange(other.end_, local_ptr); | |
| cap_end_ = std::exchange(other.cap_end_, local_ptr + N); | |
| } | |
| } else { | |
| if (alloc_ == other.alloc_) { | |
| if (other.is_local()) { | |
| init_local(); | |
| std::uninitialized_move(other.begin(), other.end(), data_); | |
| end_ = data_ + other.size(); | |
| other.clear(); | |
| } else { | |
| auto local_ptr = reinterpret_cast<pointer>(other.local_buf_); | |
| data_ = std::exchange(other.data_, local_ptr); | |
| end_ = std::exchange(other.end_, local_ptr); | |
| cap_end_ = std::exchange(other.cap_end_, local_ptr + N); | |
| } | |
| } else { | |
| init_local(); | |
| reserve(other.size()); | |
| pointer current = data_; | |
| try { | |
| for (auto it = other.begin(); it != other.end(); ++it) { | |
| std::construct_at(current++, std::move(*it)); | |
| } | |
| } catch (...) { | |
| std::destroy(data_, current); | |
| deallocate_if_heap(); | |
| throw; | |
| } | |
| end_ = data_ + other.size(); | |
| other.clear(); | |
| } | |
| } | |
| return *this; | |
| } | |
| SmallVec& operator=(std::initializer_list<T> il) { | |
| assign(il.begin(), il.end()); | |
| return *this; | |
| } | |
| #pragma endregion | |
| #pragma region ACCESS_AND_ITERATORS | |
| [[nodiscard]] CD_HOT CD_FORCEINLINE CD_ARTIFICIAL reference operator[](const size_t pos) noexcept { | |
| CD_ASSUME(pos < size()); | |
| return data_[pos]; | |
| } | |
| [[nodiscard]] CD_HOT CD_FORCEINLINE CD_ARTIFICIAL const_reference operator[](const size_t pos) const noexcept { | |
| CD_ASSUME(pos < size()); | |
| return data_[pos]; | |
| } | |
| [[nodiscard]] reference at(const size_t pos) { | |
| if (CD_UNLIKELY(pos >= size())) throw std::out_of_range("SmallVec::at"); | |
| return data_[pos]; | |
| } | |
| [[nodiscard]] const_reference at(const size_t pos) const { | |
| if (CD_UNLIKELY(pos >= size())) throw std::out_of_range("SmallVec::at"); | |
| return data_[pos]; | |
| } | |
| [[nodiscard]] CD_HOT CD_FORCEINLINE CD_ARTIFICIAL reference front() noexcept { | |
| CD_ASSUME(!empty()); | |
| return *data_; | |
| } | |
| [[nodiscard]] CD_HOT CD_FORCEINLINE CD_ARTIFICIAL const_reference front() const noexcept { | |
| CD_ASSUME(!empty()); | |
| return *data_; | |
| } | |
| [[nodiscard]] CD_HOT CD_FORCEINLINE CD_ARTIFICIAL reference back() noexcept { | |
| CD_ASSUME(!empty()); | |
| return *(end_ - 1); | |
| } | |
| [[nodiscard]] CD_HOT CD_FORCEINLINE CD_ARTIFICIAL const_reference back() const noexcept { | |
| CD_ASSUME(!empty()); | |
| return *(end_ - 1); | |
| } | |
| [[nodiscard]] CD_PURE_FN CD_FORCEINLINE CD_ARTIFICIAL pointer data() noexcept { return data_; } | |
| [[nodiscard]] CD_PURE_FN CD_FORCEINLINE CD_ARTIFICIAL const_pointer data() const noexcept { return data_; } | |
| [[nodiscard]] CD_PURE_FN CD_FORCEINLINE CD_ARTIFICIAL iterator begin() noexcept { return data_; } | |
| [[nodiscard]] CD_PURE_FN CD_FORCEINLINE CD_ARTIFICIAL const_iterator begin() const noexcept { return data_; } | |
| [[nodiscard]] CD_PURE_FN CD_FORCEINLINE CD_ARTIFICIAL const_iterator cbegin() const noexcept { return data_; } | |
| [[nodiscard]] CD_PURE_FN CD_FORCEINLINE CD_ARTIFICIAL iterator end() noexcept { return end_; } | |
| [[nodiscard]] CD_PURE_FN CD_FORCEINLINE CD_ARTIFICIAL const_iterator end() const noexcept { return end_; } | |
| [[nodiscard]] CD_PURE_FN CD_FORCEINLINE CD_ARTIFICIAL const_iterator cend() const noexcept { return end_; } | |
| [[nodiscard]] CD_PURE_FN CD_FORCEINLINE CD_ARTIFICIAL reverse_iterator rbegin() noexcept { | |
| return reverse_iterator(end_); | |
| } | |
| [[nodiscard]] CD_PURE_FN CD_FORCEINLINE CD_ARTIFICIAL const_reverse_iterator rbegin() const noexcept { | |
| return const_reverse_iterator(end_); | |
| } | |
| [[nodiscard]] CD_PURE_FN CD_FORCEINLINE CD_ARTIFICIAL reverse_iterator rend() noexcept { | |
| return reverse_iterator(data_); | |
| } | |
| [[nodiscard]] CD_PURE_FN CD_FORCEINLINE CD_ARTIFICIAL const_reverse_iterator rend() const noexcept { | |
| return const_reverse_iterator(data_); | |
| } | |
| #pragma endregion | |
| #pragma region CAPACITY | |
| [[nodiscard]] CD_PURE_FN CD_FORCEINLINE CD_ARTIFICIAL bool empty() const noexcept { return data_ == end_; } | |
| [[nodiscard]] CD_PURE_FN CD_FORCEINLINE CD_ARTIFICIAL size_t size() const noexcept { | |
| return static_cast<size_t>(end_ - data_); | |
| } | |
| [[nodiscard]] CD_PURE_FN CD_FORCEINLINE CD_ARTIFICIAL size_t capacity() const noexcept { | |
| return static_cast<size_t>(cap_end_ - data_); | |
| } | |
| [[nodiscard]] CD_PURE_FN CD_FORCEINLINE size_t max_size() const noexcept { return alloc_traits::max_size(alloc_); } | |
| void reserve(const size_t new_cap) { | |
| if (CD_UNLIKELY(new_cap > capacity())) { | |
| move_to_heap(new_cap); | |
| } | |
| } | |
| void shrink_to_fit() { | |
| const size_t current_size = size(); | |
| const size_t current_cap = capacity(); | |
| if (is_local() || current_cap == current_size) { | |
| return; | |
| } | |
| pointer old_data = data_; | |
| const size_t old_cap = current_cap; | |
| if (current_size <= N) { | |
| init_local(); | |
| if constexpr (std::is_trivially_copyable_v<T>) { | |
| if (CD_LIKELY(current_size > 0)) { | |
| std::memcpy(data_, old_data, current_size * sizeof(T)); | |
| } | |
| } else { | |
| try { | |
| if constexpr (std::is_nothrow_move_constructible_v<T>) { | |
| std::uninitialized_move(old_data, old_data + current_size, data_); | |
| } else { | |
| std::uninitialized_copy(old_data, old_data + current_size, data_); | |
| } | |
| } catch (...) { | |
| data_ = old_data; | |
| end_ = old_data + current_size; | |
| cap_end_ = old_data + old_cap; | |
| throw; | |
| } | |
| std::destroy(old_data, old_data + current_size); | |
| } | |
| end_ = data_ + current_size; | |
| alloc_traits::deallocate(alloc_, old_data, old_cap); | |
| } else { | |
| pointer new_data = alloc_traits::allocate(alloc_, current_size); | |
| if constexpr (std::is_trivially_copyable_v<T>) { | |
| std::memcpy(new_data, old_data, current_size * sizeof(T)); | |
| } else { | |
| try { | |
| if constexpr (std::is_nothrow_move_constructible_v<T>) { | |
| std::uninitialized_move(old_data, old_data + current_size, new_data); | |
| } else { | |
| std::uninitialized_copy(old_data, old_data + current_size, new_data); | |
| } | |
| } catch (...) { | |
| alloc_traits::deallocate(alloc_, new_data, current_size); | |
| throw; | |
| } | |
| std::destroy(old_data, old_data + current_size); | |
| } | |
| alloc_traits::deallocate(alloc_, old_data, old_cap); | |
| data_ = new_data; | |
| end_ = new_data + current_size; | |
| cap_end_ = new_data + current_size; | |
| } | |
| } | |
| #pragma endregion | |
| #pragma region MODIFIERS | |
| void clear() noexcept { | |
| std::destroy(data_, end_); | |
| end_ = data_; | |
| } | |
| template <std::input_iterator It, std::sentinel_for<It> S> | |
| void assign(It first, S last) { | |
| clear(); | |
| if constexpr (std::forward_iterator<It>) { | |
| const auto count = static_cast<size_t>(std::distance(first, last)); | |
| if (CD_UNLIKELY(count > capacity())) { | |
| deallocate_if_heap(); | |
| move_to_heap(count); | |
| } | |
| std::uninitialized_copy(first, last, data_); | |
| end_ = data_ + count; | |
| } else { | |
| for (; first != last; ++first) { | |
| push_back(*first); | |
| } | |
| } | |
| } | |
| void assign(const size_t count, const T& value) { | |
| clear(); | |
| if (CD_UNLIKELY(count > capacity())) { | |
| deallocate_if_heap(); | |
| move_to_heap(count); | |
| } | |
| std::uninitialized_fill_n(data_, count, value); | |
| end_ = data_ + count; | |
| } | |
| void assign(std::initializer_list<T> il) { assign(il.begin(), il.end()); } | |
| iterator insert(const_iterator pos, const T& value) { | |
| const auto index = static_cast<size_t>(pos - data_); | |
| if (CD_UNLIKELY(end_ == cap_end_)) { | |
| if (is_in_buffer(&value)) { | |
| T tmp(value); | |
| grow(size() + 1); | |
| insert_impl(index, std::move(tmp)); | |
| } else { | |
| grow(size() + 1); | |
| insert_impl(index, value); | |
| } | |
| } else { | |
| insert_impl(index, value); | |
| } | |
| return data_ + index; | |
| } | |
| iterator insert(const_iterator pos, T&& value) { | |
| const auto index = static_cast<size_t>(pos - data_); | |
| if (CD_UNLIKELY(end_ == cap_end_)) { | |
| grow(size() + 1); | |
| } | |
| insert_impl(index, std::move(value)); | |
| return data_ + index; | |
| } | |
| iterator erase(const_iterator pos) { | |
| auto dst = const_cast<iterator>(pos); | |
| if (dst != end_ - 1) { | |
| // Безопасный сдвиг влево | |
| std::move(dst + 1, end_, dst); | |
| } | |
| --end_; | |
| std::destroy_at(end_); | |
| return dst; | |
| } | |
| iterator erase(const_iterator first, const_iterator last) { | |
| if (first != last) { | |
| auto dst = const_cast<iterator>(first); | |
| auto src = const_cast<iterator>(last); | |
| iterator new_end = std::move(src, end_, dst); | |
| std::destroy(new_end, end_); | |
| end_ = new_end; | |
| } | |
| return const_cast<iterator>(first); | |
| } | |
| template <typename... Args> | |
| CD_HOT reference emplace_back(Args&&... args) { | |
| if (CD_UNLIKELY(end_ == cap_end_)) { | |
| grow(size() + 1); | |
| } | |
| std::construct_at(end_, std::forward<Args>(args)...); | |
| ++end_; | |
| return *(end_ - 1); | |
| } | |
| CD_HOT void push_back(const T& value) { | |
| if (CD_UNLIKELY(end_ == cap_end_)) { | |
| if (is_in_buffer(&value)) { | |
| T tmp(value); | |
| grow(size() + 1); | |
| std::construct_at(end_, std::move(tmp)); | |
| } else { | |
| grow(size() + 1); | |
| std::construct_at(end_, value); | |
| } | |
| } else { | |
| std::construct_at(end_, value); | |
| } | |
| ++end_; | |
| } | |
| CD_HOT void push_back(T&& value) { | |
| if (CD_UNLIKELY(end_ == cap_end_)) { | |
| if (is_in_buffer(&value)) { | |
| T tmp(std::move(value)); | |
| grow(size() + 1); | |
| std::construct_at(end_, std::move(tmp)); | |
| } else { | |
| grow(size() + 1); | |
| std::construct_at(end_, std::move(value)); | |
| } | |
| } else { | |
| std::construct_at(end_, std::move(value)); | |
| } | |
| ++end_; | |
| } | |
| CD_HOT void pop_back() noexcept { | |
| CD_ASSERT(!empty(), "Cannot pop_back from empty SmallVec"); | |
| CD_ASSUME(!empty()); | |
| --end_; | |
| std::destroy_at(end_); | |
| } | |
| void resize(const size_t count) { | |
| if (count < size()) { | |
| std::destroy(data_ + count, end_); | |
| end_ = data_ + count; | |
| } else if (count > size()) { | |
| if (CD_UNLIKELY(count > capacity())) grow(count); | |
| std::uninitialized_default_construct(end_, data_ + count); | |
| end_ = data_ + count; | |
| } | |
| } | |
| void resize(const size_t count, const T& value) { | |
| if (count < size()) { | |
| std::destroy(data_ + count, end_); | |
| end_ = data_ + count; | |
| } else if (count > size()) { | |
| if (CD_UNLIKELY(count > capacity())) grow(count); | |
| std::uninitialized_fill(end_, data_ + count, value); | |
| end_ = data_ + count; | |
| } | |
| } | |
| void swap(SmallVec& other) noexcept { | |
| if (CD_UNLIKELY(this == &other)) return; | |
| if (!is_local() && !other.is_local()) { | |
| std::swap(data_, other.data_); | |
| std::swap(end_, other.end_); | |
| std::swap(cap_end_, other.cap_end_); | |
| if constexpr (alloc_traits::propagate_on_container_swap::value) { | |
| std::swap(alloc_, other.alloc_); | |
| } | |
| return; | |
| } | |
| SmallVec tmp(std::move(*this)); | |
| *this = std::move(other); | |
| other = std::move(tmp); | |
| } | |
| #pragma endregion | |
| #pragma region COMPARISON | |
| [[nodiscard]] bool operator==(const SmallVec& other) const { | |
| if (size() != other.size()) return false; | |
| return std::equal(begin(), end(), other.begin()); | |
| } | |
| [[nodiscard]] std::strong_ordering operator<=>(const SmallVec& other) const { | |
| return std::lexicographical_compare_three_way(begin(), end(), other.begin(), other.end(), std::compare_three_way{}); | |
| } | |
| #pragma endregion | |
| }; | |
| } // namespace cd::containers |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment