opynsim
Unofficial C++ Documentation
Loading...
Searching...
No Matches
circular_buffer.h
Go to the documentation of this file.
1#pragma once
2
3#include <array>
4#include <concepts>
5#include <cstddef>
6#include <iterator>
7#include <memory>
8#include <new>
9#include <optional>
10#include <stdexcept>
11#include <type_traits>
12#include <utility>
13
14namespace osc
15{
16 template<typename T, size_t N>
17 requires (N > 1)
18 class CircularBuffer final {
19
20 // iterator implementation
21 template<bool IsConst>
22 class Iterator final {
23 public:
24 using difference_type = ptrdiff_t;
25 using value_type = T;
26 using pointer = typename std::conditional_t<IsConst, const T*, T*>;
27 using reference = typename std::conditional_t<IsConst, const T&, T&>;
28 using iterator_category = std::random_access_iterator_tag;
29
30 constexpr Iterator(pointer data, ptrdiff_t offset) :
31 data_{data},
32 offset_{offset}
33 {}
34
35 // implicit conversion from non-const iterator to a const one
36
37 constexpr operator Iterator<true>() const
38 {
39 return Iterator<true>{data_, offset_};
40 }
41
42 // LegacyIterator
43
44 constexpr Iterator& operator++()
45 {
46 offset_ = (offset_ + 1) % N;
47 return *this;
48 }
49
50 constexpr reference operator*() const
51 {
52 return data_[offset_];
53 }
54
55 // EqualityComparable
56
57 constexpr friend auto operator<=>(const Iterator&, const Iterator&) = default;
58
59 // LegacyInputIterator
60
61 constexpr pointer operator->() const
62 {
63 return &data_[offset_];
64 }
65
66 // LegacyForwardIterator
67
68 constexpr Iterator operator++(int)
69 {
70 Iterator copy{*this};
71 ++(*this);
72 return copy;
73 }
74
75 // LegacyBidirectionalIterator
76
77 constexpr Iterator& operator--()
78 {
79 offset_ = offset_ == 0 ? N - 1 : offset_ - 1;
80 return *this;
81 }
82
83 constexpr Iterator operator--(int)
84 {
85 Iterator copy{*this};
86 --(*this);
87 return copy;
88 }
89
90 // LegacyRandomAccessIterator
91
92 constexpr Iterator& operator+=(difference_type i)
93 {
94 offset_ = (offset_ + i) % N;
95 return *this;
96 }
97
98 constexpr Iterator operator+(difference_type i) const
99 {
100 Iterator copy{*this};
101 copy += i;
102 return copy;
103 }
104
105 constexpr Iterator& operator-=(difference_type i)
106 {
107 difference_type im = (i % N);
108
109 if (im > offset_) {
110 offset_ = N - (im - offset_);
111 }
112 else {
113 offset_ = offset_ - im;
114 }
115
116 return *this;
117 }
118
119 constexpr Iterator operator-(difference_type i) const
120 {
121 Iterator copy{*this};
122 copy -= i;
123 return copy;
124 }
125
126 constexpr difference_type operator-(const Iterator& other) const
127 {
128 return offset_ - other.offset_;
129 }
130
131 constexpr reference operator[](difference_type pos) const
132 {
133 return data_[(offset_ + pos) % N];
134 }
135
136 private:
137 pointer data_ = nullptr;
138 ptrdiff_t offset_ = 0;
139 };
140
141 Iterator(T*, ptrdiff_t) -> Iterator<false>;
142 Iterator(const T*, ptrdiff_t) -> Iterator<true>;
143
144 public:
145 using value_type = T;
147 using reference = T&;
148 using const_reference = const T&;
151 using reverse_iterator = std::reverse_iterator<iterator>;
152 using const_reverse_iterator = std::reverse_iterator<const_iterator>;
153
154 CircularBuffer() = default;
160 {
161 std::destroy(this->begin(), this->end());
162 }
163
164
165 // element access
166
168 return at(*this, pos);
169 }
170
171 constexpr const_reference at(size_type pos) const {
172 return at(*this, pos);
173 }
174
176 return at(*this, pos);
177 }
178
180 return at(*this, pos);
181 }
182
183 constexpr reference front() {
184 return at(*this, 0);
185 }
186
187 constexpr const_reference front() const {
188 return at(*this, 0);
189 }
190
191 constexpr reference back() {
192 return *rbegin();
193 }
194
195 constexpr const_reference back() const {
196 return *rbegin();
197 }
198
199 // iterators
200
201 constexpr const_iterator begin() const {
202 return iterator_at(*this, begin_offset_);
203 }
204
205 constexpr iterator begin() {
206 return iterator_at(*this, begin_offset_);
207 }
208
209 constexpr const_iterator cbegin() const {
210 return begin();
211 }
212
213 constexpr const_iterator end() const {
214 return iterator_at(*this, end_offset_);
215 }
216
217 constexpr iterator end() {
218 return iterator_at(*this, end_offset_);
219 }
220
221 constexpr const_iterator cend() const {
222 return end();
223 }
224
225 constexpr const_reverse_iterator rbegin() const {
226 return const_reverse_iterator{end()};
227 }
228
230 return reverse_iterator{end()};
231 }
232
233 constexpr const_reverse_iterator crbegin() const {
234 return const_reverse_iterator{end()};
235 }
236
237 constexpr const_reverse_iterator rend() const {
238 return const_reverse_iterator{begin()};
239 }
240
241 constexpr reverse_iterator rend() {
242 return reverse_iterator{begin()};
243 }
244
245 constexpr const_reverse_iterator crend() const {
246 return const_reverse_iterator{begin()};
247 }
248
249 // capacity
250
251 constexpr bool empty() const {
252 return begin_offset_ == end_offset_;
253 }
254
255 constexpr size_type size() const {
256 return end_offset_ >= begin_offset_ ? end_offset_ - begin_offset_ : (N - begin_offset_) + end_offset_;
257 }
258
259 constexpr size_type max_size() const {
260 return N;
261 }
262
263 // modifiers
264 //
265 // be careful here: the data is type-punned from a sequence of bytes
266 // so that the backing storage does not have a strict requirement of
267 // having to contain redundant default-constructed elements
268
269 constexpr void clear()
270 {
271 std::destroy(this->begin(), this->end());
272 begin_offset_ = 0;
273 end_offset_ = 0;
274 }
275
276 template<typename... Args>
277 requires std::constructible_from<T, Args&&...>
278 constexpr T& emplace_back(Args&&... args)
279 {
280 const ptrdiff_t new_end = (end_offset_ + 1) % N;
281
282 if (new_end == begin_offset_) {
283 // wraparound case: this is a fixed-size non-blocking circular
284 // buffer
285 //
286 // there is a "dead" element in the buffer after the last element
287 // but before the first (head). The head is about to become the
288 // new "dead" element and should be destructed
289
290 std::destroy_at(&front());
291 begin_offset_ = (begin_offset_ + 1) % N;
292 }
293
294 // construct T in the old "dead" element position
295 object_bytes* const ptr = raw_storage_bytes_.data() + end_offset_;
296 T* const constructed_el = new (ptr) T{std::forward<Args>(args)...};
297
298 end_offset_ = new_end;
299
300 return *constructed_el;
301 }
302
303 constexpr void push_back(const T& value)
304 {
305 emplace_back(value);
306 }
307
308 constexpr void push_back(T&& value)
309 {
310 emplace_back(std::move(value));
311 }
312
314 {
315 if (last != end()) {
316 throw std::runtime_error{"tried to remove a range of elements in the middle of a circular buffer (can currently only erase elements from end of circular buffer)"};
317 }
318
319 std::destroy(first, last);
320 end_offset_ -= std::distance(first, last);
321
322 return end();
323 }
324
325 constexpr std::optional<T> try_pop_back()
326 {
327 std::optional<T> rv = std::nullopt;
328
329 if (empty()) {
330 return rv;
331 }
332
333 rv.emplace(std::move(back()));
334 end_offset_ = end_offset_ == 0 ? static_cast<ptrdiff_t>(N) - 1 : end_offset_ - 1;
335
336 return rv;
337 }
338
339 constexpr T pop_back()
340 {
341 if (empty()) {
342 throw std::runtime_error{"tried to call Circular_buffer::pop_back on an empty circular buffer"};
343 }
344
345 T rv{std::move(back())};
346 end_offset_ = end_offset_ == 0 ? static_cast<ptrdiff_t>(N) - 1 : end_offset_ - 1;
347 return rv;
348 }
349
350 private:
351 template<typename CircularBuf>
352 static constexpr auto at(CircularBuf& buf, size_type pos) -> decltype(*buf.begin()) {
353 if (pos >= buf.size()) {
354 throw std::out_of_range{"tried to access a circular buffer element outside of its range"};
355 }
356 return buf.begin()[pos];
357 }
358
359 template<typename CircularBuf>
360 static constexpr auto iterator_at(CircularBuf& buf, ptrdiff_t pos) {
361 using Value = std::conditional_t<std::is_const_v<CircularBuf>, const T, T>;
362
363 static_assert(alignof(Value) == alignof(typename decltype(buf.raw_storage_bytes_)::value_type));
364 static_assert(sizeof(Value) == sizeof(typename decltype(buf.raw_storage_bytes_)::value_type));
365 Value* ptr = std::launder(reinterpret_cast<Value*>(buf.raw_storage_bytes_.data())); // NOLINT(cppcoreguidelines-pro-type-reinterpret-cast)
366
367 return Iterator{ptr, pos};
368 }
369
370 // raw (byte) storage for elements
371 //
372 // - it's raw bytes so that the implementation doesn't
373 // require a sequence of default-constructed `T`s to
374 // populate the storage
375 //
376 // - the circular/modulo range `[begin_offset_..end_offset_)` contains
377 // fully-constructed `T`s
378 //
379 // - `end_offset_` always points to a "dead", but valid, position
380 // in memory
381 //
382 // - the above constraints imply that the number of "live"
383 // elements in storage is N-1, because `end_offset_` will modulo
384 // spin into position 0 once it is equal to `N` (this is
385 // in contrast to non-circular storage, where `end_offset_`
386 // typically points one past the end of the storage range)
387 //
388 // - this behavior makes the implementation simpler, because
389 // you don't have to handle `begin_offset_ == end_offset_` edge
390 // cases and one-past-the end out-of-bounds checks
391 class alignas(T) object_bytes { std::byte data[sizeof(T)]; };
392 std::array<object_bytes, N> raw_storage_bytes_{};
393
394 // index (`T`-based, not raw byte based) of the first element
395 ptrdiff_t begin_offset_ = 0;
396
397 // first index (`T`-based, not raw byte based) *after* the last element
398 ptrdiff_t end_offset_ = 0;
399 };
400}
Definition circular_buffer.h:18
Iterator< false > iterator
Definition circular_buffer.h:149
const T & const_reference
Definition circular_buffer.h:148
CircularBuffer()=default
constexpr void push_back(const T &value)
Definition circular_buffer.h:303
T value_type
Definition circular_buffer.h:145
constexpr const_iterator cend() const
Definition circular_buffer.h:221
constexpr bool empty() const
Definition circular_buffer.h:251
constexpr const_reverse_iterator rend() const
Definition circular_buffer.h:237
constexpr reference at(size_type pos)
Definition circular_buffer.h:167
constexpr iterator erase(iterator first, iterator last)
Definition circular_buffer.h:313
constexpr size_type max_size() const
Definition circular_buffer.h:259
constexpr const_reference back() const
Definition circular_buffer.h:195
constexpr iterator end()
Definition circular_buffer.h:217
constexpr const_iterator cbegin() const
Definition circular_buffer.h:209
Iterator< true > const_iterator
Definition circular_buffer.h:150
constexpr reverse_iterator rend()
Definition circular_buffer.h:241
constexpr const_iterator end() const
Definition circular_buffer.h:213
constexpr size_type size() const
Definition circular_buffer.h:255
CircularBuffer(CircularBuffer &&) noexcept=delete
constexpr iterator begin()
Definition circular_buffer.h:205
constexpr reference back()
Definition circular_buffer.h:191
constexpr T pop_back()
Definition circular_buffer.h:339
constexpr void push_back(T &&value)
Definition circular_buffer.h:308
size_t size_type
Definition circular_buffer.h:146
constexpr reference operator[](size_type pos)
Definition circular_buffer.h:175
constexpr const_reference front() const
Definition circular_buffer.h:187
constexpr void clear()
Definition circular_buffer.h:269
constexpr reverse_iterator rbegin()
Definition circular_buffer.h:229
CircularBuffer(const CircularBuffer &)=delete
constexpr const_reverse_iterator crend() const
Definition circular_buffer.h:245
T & reference
Definition circular_buffer.h:147
std::reverse_iterator< iterator > reverse_iterator
Definition circular_buffer.h:151
constexpr const_iterator begin() const
Definition circular_buffer.h:201
constexpr const_reverse_iterator rbegin() const
Definition circular_buffer.h:225
constexpr const_reverse_iterator crbegin() const
Definition circular_buffer.h:233
constexpr std::optional< T > try_pop_back()
Definition circular_buffer.h:325
constexpr const_reference at(size_type pos) const
Definition circular_buffer.h:171
constexpr reference front()
Definition circular_buffer.h:183
std::reverse_iterator< const_iterator > const_reverse_iterator
Definition circular_buffer.h:152
constexpr const_reference operator[](size_type pos) const
Definition circular_buffer.h:179
constexpr T & emplace_back(Args &&... args)
Definition circular_buffer.h:278
Definition custom_decoration_generator.h:5
constexpr std::ranges::range_reference_t< R > at(R &&r, std::ranges::range_size_t< R > pos)
Definition algorithms.h:67
constexpr std::common_type_t< Angle< Rep1, Units1 >, Angle< Rep2, Units2 > > operator+(const Angle< Rep1, Units1 > &lhs, const Angle< Rep2, Units2 > &rhs)
Definition angle.h:99
constexpr U to(T &&value)
Definition conversion.h:56
constexpr auto operator<=>(const Angle< Rep1, Units1 > &lhs, const Angle< Rep2, Units2 > &rhs)
Definition angle.h:142
constexpr std::common_type_t< Angle< Rep1, Units1 >, Angle< Rep2, Units2 > > operator-(const Angle< Rep1, Units1 > &lhs, const Angle< Rep2, Units2 > &rhs)
Definition angle.h:114