#pragma once #include #include #include #include #include #include #include namespace uc::detail { // A fixed-capacity circular leaf. Empty slots are represented explicitly so // the block also works for non-default-constructible and non-trivial values. // Insert/erase shifts the shorter side and therefore moves at most size()/2 // elements. offset_ is the physical slot containing logical element zero. template class RingBlock { public: explicit RingBlock(std::size_t capacity = 256) : slots_(capacity) { if (capacity < 2) { throw std::invalid_argument("RingBlock capacity must be at least two"); } } RingBlock(const RingBlock&) = default; RingBlock(RingBlock&&) noexcept = default; RingBlock& operator=(const RingBlock&) = default; RingBlock& operator=(RingBlock&&) noexcept = default; ~RingBlock() = default; [[nodiscard]] std::size_t size() const noexcept { return size_; } [[nodiscard]] std::size_t capacity() const noexcept { return slots_.size(); } [[nodiscard]] bool empty() const noexcept { return size_ == 0; } [[nodiscard]] bool full() const noexcept { return size_ == capacity(); } [[nodiscard]] std::size_t offset() const noexcept { return offset_; } T& operator[](std::size_t index) noexcept { assert(index < size_); return *slots_[physical(index)]; } const T& operator[](std::size_t index) const noexcept { assert(index < size_); return *slots_[physical(index)]; } T& at(std::size_t index) { if (index >= size_) { throw std::out_of_range("RingBlock index out of range"); } return (*this)[index]; } const T& at(std::size_t index) const { if (index >= size_) { throw std::out_of_range("RingBlock index out of range"); } return (*this)[index]; } template void insert(std::size_t index, U&& value) { if (index > size_) { throw std::out_of_range("RingBlock insertion index out of range"); } if (full()) { throw std::length_error("RingBlock is full"); } const auto old_offset = offset_; if (index < size_ / 2) { const auto new_offset = decrement(old_offset); for (std::size_t i = 0; i < index; ++i) { move_slot((new_offset + i) % capacity(), (old_offset + i) % capacity()); } offset_ = new_offset; } else { for (std::size_t i = size_; i > index; --i) { move_slot((old_offset + i) % capacity(), (old_offset + i - 1) % capacity()); } } slots_[physical(index)].emplace(std::forward(value)); ++size_; assert_invariant(); } template T& emplace(std::size_t index, Args&&... args) { T value(std::forward(args)...); insert(index, std::move(value)); return (*this)[index]; } template void push_back(U&& value) { insert(size_, std::forward(value)); } T erase(std::size_t index) { if (index >= size_) { throw std::out_of_range("RingBlock erase index out of range"); } T removed(std::move((*this)[index])); const auto old_offset = offset_; slots_[physical(index)].reset(); if (index < size_ / 2) { for (std::size_t i = index; i > 0; --i) { move_slot((old_offset + i) % capacity(), (old_offset + i - 1) % capacity()); } offset_ = increment(old_offset); } else { for (std::size_t i = index; i + 1 < size_; ++i) { move_slot((old_offset + i) % capacity(), (old_offset + i + 1) % capacity()); } } --size_; if (size_ == 0) { offset_ = 0; } assert_invariant(); return removed; } void clear() noexcept { for (auto& slot : slots_) { slot.reset(); } size_ = 0; offset_ = 0; } [[nodiscard]] std::size_t allocated_bytes() const noexcept { return slots_.capacity() * sizeof(typename decltype(slots_)::value_type); } private: [[nodiscard]] std::size_t physical(std::size_t logical) const noexcept { return (offset_ + logical) % capacity(); } [[nodiscard]] std::size_t increment(std::size_t slot) const noexcept { return slot + 1 == capacity() ? 0 : slot + 1; } [[nodiscard]] std::size_t decrement(std::size_t slot) const noexcept { return slot == 0 ? capacity() - 1 : slot - 1; } void move_slot(std::size_t destination, std::size_t source) { assert(!slots_[destination].has_value()); assert(slots_[source].has_value()); slots_[destination].emplace(std::move(*slots_[source])); slots_[source].reset(); } void assert_invariant() const noexcept { #ifndef NDEBUG std::size_t occupied = 0; for (const auto& slot : slots_) { occupied += slot.has_value() ? 1U : 0U; } assert(occupied == size_); for (std::size_t i = 0; i < size_; ++i) { assert(slots_[physical(i)].has_value()); } #endif } std::vector> slots_; std::size_t offset_ = 0; std::size_t size_ = 0; }; } // namespace uc::detail