Realisation v1
This commit is contained in:
@@ -0,0 +1,184 @@
|
||||
#pragma once
|
||||
|
||||
#include <cassert>
|
||||
#include <cstddef>
|
||||
#include <optional>
|
||||
#include <stdexcept>
|
||||
#include <type_traits>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
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 T>
|
||||
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 <class U>
|
||||
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<U>(value));
|
||||
++size_;
|
||||
assert_invariant();
|
||||
}
|
||||
|
||||
template <class... Args>
|
||||
T& emplace(std::size_t index, Args&&... args) {
|
||||
T value(std::forward<Args>(args)...);
|
||||
insert(index, std::move(value));
|
||||
return (*this)[index];
|
||||
}
|
||||
|
||||
template <class U>
|
||||
void push_back(U&& value) {
|
||||
insert(size_, std::forward<U>(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<std::optional<T>> slots_;
|
||||
std::size_t offset_ = 0;
|
||||
std::size_t size_ = 0;
|
||||
};
|
||||
|
||||
} // namespace uc::detail
|
||||
|
||||
Reference in New Issue
Block a user