emlabcpp
modern opinionated embedded C++ library
static_circular_buffer.h
Go to the documentation of this file.
1 
24 #pragma once
25 
26 #include "./iterator.h"
27 #include "./static_storage.h"
28 #include "./view.h"
29 
30 #include <atomic>
31 #include <limits>
32 #include <ranges>
33 
34 namespace emlabcpp
35 {
36 
37 template < typename T >
38 class static_circular_buffer_iterator;
39 
40 template < std::size_t N >
41 constexpr auto _select_index_type()
42 {
43  if constexpr ( N <= std::numeric_limits< std::uint8_t >::max() )
44  return std::atomic_uint8_t{};
45  else if constexpr ( N <= std::numeric_limits< std::uint16_t >::max() )
46  return std::atomic_uint16_t{};
47  else if constexpr ( N <= std::numeric_limits< std::uint32_t >::max() )
48  return std::atomic_uint32_t{};
49  else
50  return std::atomic_uint64_t{};
51 }
52 
58 template < std::size_t N >
60 {
61  static constexpr std::size_t max_size = N + 1;
62 
63  using index_type = decltype( _select_index_type< N >() );
65 
66  void incr_front() noexcept
67  {
68  from_ = static_cast< size_type >( ( from_ + 1 ) % max_size );
69  }
70 
71  void incr_front( size_type n ) noexcept
72  {
73  from_ = static_cast< size_type >( ( from_ + n ) % max_size );
74  }
75 
76  auto front_idx() const noexcept
77  {
78  return from_.load();
79  }
80 
81  void incr_back() noexcept
82  {
83  to_ = static_cast< size_type >( ( to_ + 1 ) % max_size );
84  }
85 
86  void incr_back( size_type n ) noexcept
87  {
88  to_ = ( to_ + n ) % max_size;
89  }
90 
91  auto back_idx() const noexcept
92  {
93  return to_.load();
94  }
95 
96  size_type size() const noexcept
97  {
98  if ( to_ >= from_ )
99  return to_ - from_;
100  return static_cast< size_type >( to_ + ( max_size - from_ ) );
101  }
102 
103  bool empty() const noexcept
104  {
105  return from_ == to_;
106  }
107 
108  bool full() const noexcept
109  {
110  return size() == N;
111  }
112 
113  index_type last_idx() const noexcept
114  {
115  return ( to_ + max_size - 1 ) % max_size;
116  }
117 
118 private:
119  index_type from_ = 0;
120  index_type to_ = 0;
121 };
122 
128 template < std::size_t N >
130 {
131  static_assert( N && !( N & ( N - 1 ) ), "N must be power of two" );
132  static constexpr std::size_t max_size = N;
133 
134  using index_type = decltype( _select_index_type< N >() );
136 
137  void incr_front() noexcept
138  {
139  from_ = from_ + 1;
140  }
141 
142  void incr_front( size_type n ) noexcept
143  {
144  from_ = from_ + n;
145  }
146 
147  auto front_idx() const noexcept
148  {
149  return from_.load() % max_size;
150  }
151 
152  void incr_back() noexcept
153  {
154  to_ = to_ + 1;
155  }
156 
157  void incr_back( size_type n ) noexcept
158  {
159  to_ = to_ + n;
160  }
161 
162  auto back_idx() const noexcept
163  {
164  return to_.load() % max_size;
165  }
166 
167  auto size() const noexcept
168  {
169  return static_cast< size_type >( to_ - from_ );
170  }
171 
172  bool empty() const noexcept
173  {
174  return from_ == to_;
175  }
176 
177  bool full() const noexcept
178  {
179  return size() == static_cast< size_type >( max_size );
180  }
181 
182  index_type last_idx() const noexcept
183  {
184  return ( to_ + max_size - 1 ) % max_size;
185  }
186 
187 private:
188  index_type from_ = 0;
189  index_type to_ = 0;
190 };
191 
192 template < typename T, std::size_t N >
194 {
195  static constexpr bool is_power_of_two = N && !( N & ( N - 1 ) );
196  if constexpr ( is_power_of_two )
197  return _overflow_strategy< N >{};
198  else
199  return _spacer_strategy< N >{};
200 }
201 
209 template < typename T, std::size_t N, typename Strategy = decltype( _select_strategy< T, N >() ) >
211 {
212  static constexpr std::size_t max_size = Strategy::max_size;
213 
214  using index_type = typename Strategy::index_type;
215  using size_type = typename Strategy::size_type;
216 
217  static_assert(
219  "static_circular_buffer: N must be less than index_type max value" );
220 
221  using value_type = T;
222  using reference = T&;
223  using const_reference = T const&;
226 
228  static_circular_buffer() noexcept = default;
229 
231  {
232  copy_range_back( other );
233  }
234 
236  {
237  move_range_back( other );
238  other.clear();
239  }
240 
244  {
245  if ( this == &other )
246  return *this;
247  clear();
248  copy_range_back( other );
249  return *this;
250  }
251 
255  {
256  if ( this == &other )
257  return *this;
258  clear();
259  move_range_back( other );
260  other.clear();
261  return *this;
262  }
263 
265  [[nodiscard]] iterator begin() noexcept
266  {
267  return iterator{
268  storage_.data(),
269  storage_.data() + max_size,
270  storage_.data() + strategy_.front_idx() };
271  }
272 
274  [[nodiscard]] const_iterator begin() const noexcept
275  {
276  return const_iterator{
277  storage_.data(),
278  storage_.data() + max_size,
279  storage_.data() + strategy_.front_idx() };
280  }
281 
283  [[nodiscard]] std::reverse_iterator< iterator > rbegin() noexcept
284  {
285  return std::make_reverse_iterator( end() );
286  };
287 
289  [[nodiscard]] std::reverse_iterator< const_iterator > rbegin() const noexcept
290  {
291  return std::make_reverse_iterator( end() );
292  };
293 
295  [[nodiscard]] reference front() noexcept
296  {
297  return storage_[static_cast< size_type >( strategy_.front_idx() )];
298  }
299 
301  [[nodiscard]] const_reference front() const noexcept
302  {
303  return storage_[static_cast< size_type >( strategy_.front_idx() )];
304  }
305 
307  [[nodiscard]] T take_front()
308  {
309  T item = std::move( front() );
310  pop_front();
311  return item;
312  }
313 
315  void take_front( auto&& buffer )
316  {
317  auto n = static_cast< size_type >( std::size( buffer ) );
318  auto iter = std::begin( buffer );
319  auto idx = static_cast< size_type >( strategy_.front_idx() );
320  auto* p = storage_.data() + idx;
321  auto capacity = static_cast< size_type >( max_size - idx );
322  if ( capacity >= n ) {
323  std::move( p, p + n, iter );
324  } else {
325  iter = std::move( p, p + capacity, iter );
326  std::move( storage_.data(), storage_.data() + idx + n - capacity, iter );
327  }
328  strategy_.incr_front( n );
329  }
330 
332  void pop_front()
333  {
334  storage_.delete_item( static_cast< size_type >( strategy_.front_idx() ) );
335  strategy_.incr_front();
336  }
337 
340  {
341  auto idx = static_cast< size_type >( strategy_.front_idx() );
342  auto capacity = max_size - idx;
343  if ( capacity >= n ) {
344  storage_.delete_n( idx, n );
345  } else {
346  storage_.delete_n( idx, capacity );
347  storage_.delete_n( 0, n - capacity );
348  }
349  strategy_.incr_front( n );
350  }
351 
353  [[nodiscard]] iterator end() noexcept
354  {
355  return iterator{
356  storage_.data(),
357  storage_.data() + max_size,
358  storage_.data() + strategy_.back_idx() };
359  }
360 
362  [[nodiscard]] const_iterator end() const noexcept
363  {
364  return const_iterator{
365  storage_.data(),
366  storage_.data() + max_size,
367  storage_.data() + strategy_.back_idx() };
368  }
369 
371  [[nodiscard]] std::reverse_iterator< iterator > rend() noexcept
372  {
373  return std::make_reverse_iterator( begin() );
374  };
375 
377  [[nodiscard]] std::reverse_iterator< const_iterator > rend() const noexcept
378  {
379  return std::make_reverse_iterator( begin() );
380  };
381 
383  [[nodiscard]] reference back() noexcept
384  {
385  return storage_[static_cast< size_type >( strategy_.last_idx() )];
386  }
387 
389  [[nodiscard]] const_reference back() const noexcept
390  {
391  return storage_[static_cast< size_type >( strategy_.last_idx() )];
392  }
393 
395  void push_back( T item )
396  {
397  emplace_back( std::move( item ) );
398  }
399 
401  template < typename... Args >
402  T& emplace_back( Args&&... args )
403  {
404  T& ref = storage_.emplace_item(
405  static_cast< size_type >( strategy_.back_idx() ),
406  std::forward< Args >( args )... );
407  strategy_.incr_back();
408  return ref;
409  }
410 
412  void copy_range_back( auto&& range )
413  {
414  auto idx = static_cast< size_type >( strategy_.back_idx() );
415  auto capacity = static_cast< size_type >( max_size - idx );
416  auto n = std::size( range );
417  auto iter = std::begin( range );
418  if ( capacity >= n ) {
419  storage_.copy_n( idx, n, iter );
420  } else {
421  iter = storage_.copy_n( idx, capacity, iter );
422  storage_.copy_n( 0, n - capacity, iter );
423  }
424  strategy_.incr_back( static_cast< size_type >( n ) );
425  }
426 
428  void move_range_back( auto&& range )
429  {
430  auto idx = static_cast< size_type >( strategy_.back_idx() );
431  auto capacity = static_cast< size_type >( max_size - idx );
432  auto n = std::size( range );
433  auto iter = std::begin( range );
434  if ( capacity >= n ) {
435  storage_.move_n( idx, n, iter );
436  } else {
437  iter = storage_.move_n( idx, capacity, iter );
438  storage_.move_n( 0, n - capacity, iter );
439  }
440  strategy_.incr_back( static_cast< size_type >( n ) );
441  }
442 
444  [[nodiscard]] constexpr size_type capacity() const noexcept
445  {
446  return N;
447  }
448 
450  [[nodiscard]] size_type size() const
451  {
452  return static_cast< size_type >( strategy_.size() );
453  }
454 
456  [[nodiscard]] bool empty() const noexcept
457  {
458  return strategy_.empty();
459  }
460 
462  [[nodiscard]] bool full() const noexcept
463  {
464  return strategy_.full();
465  }
466 
468  void clear()
469  {
470  pop_front( size() );
471  }
472 
475  {
476  clear();
477  }
478 
479 private:
481  Strategy strategy_;
482 
483  template < typename U >
485 };
486 
487 template < typename T, std::size_t N >
488 [[nodiscard]] auto
490 {
491  return std::lexicographical_compare_three_way( lh.begin(), lh.end(), rh.begin(), rh.end() );
492 }
493 
494 template < typename T, std::size_t N >
495 [[nodiscard]] bool
497 {
498  auto size = lh.size();
499  if ( size != rh.size() )
500  return false;
501 
502  return std::equal( lh.begin(), lh.end(), rh.begin() );
503 }
504 
505 template < typename T, std::size_t N >
506 [[nodiscard]] bool
508 {
509  return !( lh == rh );
510 }
511 
512 #ifdef EMLABCPP_USE_OSTREAM
514 template < typename T, std::size_t N >
515 std::ostream& operator<<( std::ostream& os, static_circular_buffer< T, N > const& cb )
516 {
517  return os << view{ cb };
518 }
519 #endif
520 
521 } // namespace emlabcpp
522 
523 template < typename T >
524 struct std::iterator_traits< emlabcpp::static_circular_buffer_iterator< T > >
525 {
526  using value_type = T;
527  using difference_type = std::make_signed_t< std::size_t >;
528  using pointer = value_type*;
529  using const_pointer = value_type const*;
531  using iterator_category = std::bidirectional_iterator_tag;
532 };
533 
534 namespace emlabcpp
535 {
536 
537 template < typename T >
539  : public generic_iterator< static_circular_buffer_iterator< T > >
540 {
541 public:
542  static constexpr bool is_const = std::is_const_v< T >;
543  using value_type = T;
544  using reference = std::conditional_t< is_const, value_type const&, value_type& >;
545  using const_reference = value_type const&;
547  typename std::iterator_traits< static_circular_buffer_iterator< T > >::difference_type;
548 
549  static_circular_buffer_iterator( T* beg, T* end, T* p ) noexcept
550  : beg_( beg )
551  , end_( end )
552  , p_( p )
553  {
554  }
555 
557  default;
559 
561  operator=( static_circular_buffer_iterator const& ) noexcept = default;
563  operator=( static_circular_buffer_iterator&& ) noexcept = default;
564 
565  reference operator*() noexcept
566  {
567  return *p_;
568  }
569 
570  reference operator*() const noexcept
571  {
572  return *p_;
573  }
574 
576  {
577  p_++;
578  if ( p_ == end_ )
579  p_ = beg_;
580  return *this;
581  }
582 
584  {
585  p_ += v;
586  while ( p_ >= end_ )
587  p_ -= ( end_ - beg_ );
588  return *this;
589  }
590 
592  {
593  p_--;
594  if ( p_ == ( beg_ - 1 ) )
595  p_ = end_ - 1;
596  return *this;
597  }
598 
600  {
601  p_ -= v;
602  while ( p_ < beg_ )
603  p_ += ( end_ - beg_ );
604  return *this;
605  }
606 
607  auto operator<=>( static_circular_buffer_iterator const& other ) const noexcept
608  {
609  return p_ <=> other.p_;
610  }
611 
612  bool operator==( static_circular_buffer_iterator const& other ) const noexcept
613  {
614  return p_ == other.p_;
615  }
616 
617 private:
618  T* beg_;
619  T* end_;
620  T* p_;
621 };
622 
623 } // namespace emlabcpp
Definition: static_circular_buffer.h:540
static constexpr bool is_const
Definition: static_circular_buffer.h:542
static_circular_buffer_iterator(static_circular_buffer_iterator const &) noexcept=default
static_circular_buffer_iterator(T *beg, T *end, T *p) noexcept
Definition: static_circular_buffer.h:549
static_circular_buffer_iterator & operator--() noexcept
Definition: static_circular_buffer.h:591
value_type const & const_reference
Definition: static_circular_buffer.h:545
reference operator*() const noexcept
Definition: static_circular_buffer.h:570
typename std::iterator_traits< static_circular_buffer_iterator< T > >::difference_type difference_type
Definition: static_circular_buffer.h:547
static_circular_buffer_iterator & operator+=(difference_type v) noexcept
Definition: static_circular_buffer.h:583
static_circular_buffer_iterator(static_circular_buffer_iterator &&) noexcept=default
bool operator==(static_circular_buffer_iterator const &other) const noexcept
Definition: static_circular_buffer.h:612
static_circular_buffer_iterator & operator++() noexcept
Definition: static_circular_buffer.h:575
std::conditional_t< is_const, value_type const &, value_type & > reference
Definition: static_circular_buffer.h:544
T value_type
Definition: static_circular_buffer.h:543
static_circular_buffer_iterator & operator-=(difference_type v) noexcept
Definition: static_circular_buffer.h:599
auto operator<=>(static_circular_buffer_iterator const &other) const noexcept
Definition: static_circular_buffer.h:607
bounded< std::size_t, max_size, max_size > size_type
Definition: serializer.h:74
static constexpr std::size_t max_size
Definition: serializer.h:73
std::variant< int64_t, float, bool, string_buffer > value_type
Definition: base.h:51
MIT License.
Definition: impl.h:31
view(Container &cont) -> view< iterator_of_t< Container > >
The container deduction guide uses iterator_of_t.
constexpr bool equal(LhContainer &&lh, RhContainer &&rh, BinaryPredicateCallable &&f=std::equal_to< void >{})
Returns true if containers 'lh' and 'rh' has same size and calls to predicate f - f(lh[i],...
Definition: algorithm.h:356
Args const & args
Definition: min_max.h:83
constexpr Derived max(vec_point_base< Derived, N > const &a, vec_point_base< Derived, N > const &b)
Definition: vec_point_base.h:229
auto _select_strategy()
Definition: static_circular_buffer.h:193
constexpr auto _select_index_type()
Definition: static_circular_buffer.h:41
constexpr view< iterators::numeric_iterator< Numeric > > range(Numeric from, Numeric to)
Builds numeric view over interval [from, to)
Definition: range.h:34
auto operator<=>(static_circular_buffer< T, N > const &lh, static_circular_buffer< T, N > const &rh)
Definition: static_circular_buffer.h:489
constexpr bool operator!=(pose const &x, pose const &y)
negation of operator== between poses
Definition: pose.h:99
std::ostream & operator<<(std::ostream &os, string_buffer< N > const &sb)
Definition: string_buffer.h:168
N
Definition: static_storage.h:157
constexpr bool operator==(pose const &x, pose const &y)
compares poses on their position and orientation
Definition: pose.h:93
generic_iterator simplifies custom iterator implementation using CRTP.
Definition: iterator.h:62
Strategy for managing indices in circular buffer via overflowing indices.
Definition: static_circular_buffer.h:130
bool full() const noexcept
Definition: static_circular_buffer.h:177
void incr_front() noexcept
Definition: static_circular_buffer.h:137
void incr_front(size_type n) noexcept
Definition: static_circular_buffer.h:142
auto back_idx() const noexcept
Definition: static_circular_buffer.h:162
auto size() const noexcept
Definition: static_circular_buffer.h:167
typename index_type::value_type size_type
Definition: static_circular_buffer.h:135
index_type last_idx() const noexcept
Definition: static_circular_buffer.h:182
void incr_back() noexcept
Definition: static_circular_buffer.h:152
bool empty() const noexcept
Definition: static_circular_buffer.h:172
auto front_idx() const noexcept
Definition: static_circular_buffer.h:147
void incr_back(size_type n) noexcept
Definition: static_circular_buffer.h:157
static constexpr std::size_t max_size
Definition: static_circular_buffer.h:132
decltype(_select_index_type< N >()) index_type
Definition: static_circular_buffer.h:134
Strategy for managing indices in circular buffer via empty slot.
Definition: static_circular_buffer.h:60
size_type size() const noexcept
Definition: static_circular_buffer.h:96
auto back_idx() const noexcept
Definition: static_circular_buffer.h:91
static constexpr std::size_t max_size
Definition: static_circular_buffer.h:61
bool empty() const noexcept
Definition: static_circular_buffer.h:103
decltype(_select_index_type< N >()) index_type
Definition: static_circular_buffer.h:63
typename index_type::value_type size_type
Definition: static_circular_buffer.h:64
void incr_front() noexcept
Definition: static_circular_buffer.h:66
bool full() const noexcept
Definition: static_circular_buffer.h:108
index_type last_idx() const noexcept
Definition: static_circular_buffer.h:113
auto front_idx() const noexcept
Definition: static_circular_buffer.h:76
void incr_back(size_type n) noexcept
Definition: static_circular_buffer.h:86
void incr_back() noexcept
Definition: static_circular_buffer.h:81
void incr_front(size_type n) noexcept
Definition: static_circular_buffer.h:71
Class implementing circular buffer of any type for up to N elements.
Definition: static_circular_buffer.h:211
reference back() noexcept
Reference to last element.
Definition: static_circular_buffer.h:383
std::reverse_iterator< const_iterator > rbegin() const noexcept
Reverse iterator to last element.
Definition: static_circular_buffer.h:289
const_iterator begin() const noexcept
Iterator to first element.
Definition: static_circular_buffer.h:274
iterator end() noexcept
Iterator to one-past-last element.
Definition: static_circular_buffer.h:353
~static_circular_buffer()
Destructor destructs all contained elements.
Definition: static_circular_buffer.h:474
const_reference back() const noexcept
Reference to last element.
Definition: static_circular_buffer.h:389
void take_front(auto &&buffer)
Removes and moves first n elements into provided buffer, is safe in SPSC scenario.
Definition: static_circular_buffer.h:315
T value_type
Definition: static_circular_buffer.h:221
iterator begin() noexcept
Iterator to first element.
Definition: static_circular_buffer.h:265
std::reverse_iterator< iterator > rend() noexcept
Reverse iterator to one-before-first element.
Definition: static_circular_buffer.h:371
static_circular_buffer(static_circular_buffer &&other) noexcept
Definition: static_circular_buffer.h:235
T & reference
Definition: static_circular_buffer.h:222
std::reverse_iterator< iterator > rbegin() noexcept
Reverse iterator to last element.
Definition: static_circular_buffer.h:283
void clear()
Clears the buffer by destructing all contained elements.
Definition: static_circular_buffer.h:468
T & emplace_back(Args &&... args)
Inserts item constructed with args... at the back, is safe in SPSC scenario.
Definition: static_circular_buffer.h:402
void pop_front()
Removes first element, is safe in SPSC scenario.
Definition: static_circular_buffer.h:332
constexpr size_type capacity() const noexcept
Returns maximum capacity of the buffer.
Definition: static_circular_buffer.h:444
typename Strategy::size_type size_type
Definition: static_circular_buffer.h:215
static_circular_buffer & operator=(static_circular_buffer const &other)
Clears the buffer by destructing all contained elements and copies from other buffer.
Definition: static_circular_buffer.h:243
bool empty() const noexcept
Returns whether the buffer is empty.
Definition: static_circular_buffer.h:456
void push_back(T item)
Inserts item at the back, is safe in SPSC scenario.
Definition: static_circular_buffer.h:395
void pop_front(size_type n)
Removes first n elements, is safe in SPSC scenario.
Definition: static_circular_buffer.h:339
const_reference front() const noexcept
Reference to first element.
Definition: static_circular_buffer.h:301
std::reverse_iterator< const_iterator > rend() const noexcept
Reverse iterator to one-before-first element.
Definition: static_circular_buffer.h:377
T const & const_reference
Definition: static_circular_buffer.h:223
const_iterator end() const noexcept
Iterator to one-past-last element.
Definition: static_circular_buffer.h:362
bool full() const noexcept
Returns whether the buffer is full.
Definition: static_circular_buffer.h:462
reference front() noexcept
Reference to first element.
Definition: static_circular_buffer.h:295
T take_front()
Removes and returns first element, is safe in SPSC scenario.
Definition: static_circular_buffer.h:307
size_type size() const
Returns current size of the buffer.
Definition: static_circular_buffer.h:450
static_circular_buffer() noexcept=default
Default constructed circular buffer is empty.
typename Strategy::index_type index_type
Definition: static_circular_buffer.h:214
void move_range_back(auto &&range)
Inserts range by move at the back, is safe in SPSC scenario.
Definition: static_circular_buffer.h:428
void copy_range_back(auto &&range)
Inserts range by copy at the back, is safe in SPSC scenario.
Definition: static_circular_buffer.h:412
static_circular_buffer & operator=(static_circular_buffer &&other) noexcept
Clears the buffer by destructing all contained elements and moves from other buffer.
Definition: static_circular_buffer.h:254
std::bidirectional_iterator_tag iterator_category
Definition: static_circular_buffer.h:531
std::make_signed_t< std::size_t > difference_type
Definition: static_circular_buffer.h:527
value_type * pointer
Definition: static_circular_buffer.h:528
value_type & reference
Definition: static_circular_buffer.h:530
value_type const * const_pointer
Definition: static_circular_buffer.h:529