aboutsummaryrefslogtreecommitdiff
path: root/ring.h
blob: 43c3fcaf20d08030ad0a1474cc1f2d6015549ab9 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
#ifndef UTILS_RING_H
#define UTILS_RING_H

#include <new>
#include <type_traits>
#include <utility>

#if __cplusplus >= 201703L
#include <optional>
#endif

#include <cassert>
#include <cstddef>

/// Helper to check if the number N is of power two.
constexpr inline bool is_power_two(std::size_t n) {
  return (n > 0) && (n & (n - 1)) == 0;
}

// -- RING ---------------------------------------------------------------------

/// ring
///
/// A minimal statically sized ring buffer which can hold up to N elements of
/// type T.
template <typename T, std::size_t N>
class ring {
  using cursor_t = std::size_t;
  static_assert(std::is_unsigned<cursor_t>::value,
                "cursor type must be unsigned");
  static_assert(is_power_two(N), "ring size must be of power of two");

 public:
  // -- CONSTRUCTOR ------------------------------------------------------------

  explicit constexpr ring() {}

  // -- DESTRUCTOR -------------------------------------------------------------

  ~ring() {
    while (!is_empty()) {
      pop();
    }
  }

  // -- COPY -------------------------------------------------------------------

  // Copy semantics deleted for simplicity.
  ring(const ring&) = delete;
  ring& operator=(const ring&) = delete;

  // -- MOVE -------------------------------------------------------------------

  // Move semantics deleted for simplicity.
  ring(ring&&) = delete;
  ring& operator=(ring&&) = delete;

  // -- STATUS -----------------------------------------------------------------

  constexpr cursor_t size() const {
    return m_wc - m_rc;
  }

  constexpr bool is_full() const {
    return size() == N;
  }

  constexpr bool is_empty() const {
    return size() == 0;
  }

  // -- INSERT -----------------------------------------------------------------

  template <typename... Args>
  constexpr bool emplace(Args&&... args) {
    if (!is_full()) {
      new (&vals[m_wc++ & kMASK]) T(std::forward<Args>(args)...);
      return true;
    }
    return false;
  }

  constexpr bool push(const T& t) {
    return emplace(t);
  }

  constexpr bool push(T&& t) {
    return emplace(std::move(t));
  }

  // -- REMOVE -----------------------------------------------------------------

  void pop() {
    if (!is_empty()) {
      vals[m_rc++ & kMASK].~T();
    }
  }

#if __cplusplus >= 201703L
  constexpr std::optional<T> take() {
    if (!is_empty()) {
      std::optional<T> ret = std::move(vals[m_rc & kMASK]);
      pop();
      return ret;
    }
    return std::nullopt;
  }
#else
  constexpr T take() {
    assert(!is_empty());
    T ret = std::move(vals[m_rc & kMASK]);
    pop();
    return ret;
  }
#endif

  // -- INTERNAL ---------------------------------------------------------------

 private:
  static constexpr cursor_t kMASK = N - 1;

  union {
    // Array is placed in unnamed union to not default construct the Ts, when
    // constructing a ring<>.
    T vals[N];
  };

  cursor_t m_wc = 0;
  cursor_t m_rc = 0;
};

#endif