libtcspc C++ API
Streaming TCSPC and time tag data processing
Loading...
Searching...
No Matches
vector_queue.hpp
1/*
2 * This file is part of libtcspc
3 * Copyright 2019-2026 Board of Regents of the University of Wisconsin System
4 * SPDX-License-Identifier: MIT
5 */
6
7#pragma once
8
9#include "int_arith.hpp"
10
11#include <algorithm>
12#include <cassert>
13#include <cstddef>
14#include <iterator>
15#include <memory>
16#include <new>
17#include <utility>
18
19namespace tcspc::internal {
20
21// Equivalent of std::queue<T> (i.e., std::queue<T, std::deque<T>>) but backed
22// by a single array used as a ring buffer and enlarged as needed (which should
23// result in better locality of reference and fewer allocations/deallocations
24// than std::deque). This is intended for event buffering where the
25// steady-state buffer capacity is (expected to be) bounded.
26//
27// This is similar to std::queue<T, boost::devector<T>>, but I don't want to
28// have Boost as a dependency. Implementing a devector equivalent that can be
29// used with std::queue would be much more work (iterators, etc.).
30//
31// For exception safety, T should be no-throw move-constructible.
32template <typename T> class vector_queue {
33 T *ptr; // nullptr iff zero capacity
34 T *endptr; // nullptr iff zero capacity
35 T *head; // Never equal to endptr unless zero capacity
36 T *tail; // Never equal to endptr unless zero capacity
37
38 // We use std::allocator for implementation convenience, even though we
39 // don't currently allow allocator customization.
40 using alloctraits = std::allocator_traits<std::allocator<T>>;
41
42 [[nodiscard]] constexpr auto is_full() const noexcept -> bool {
43 return ptr == nullptr || tail + 1 == head ||
44 (tail + 1 == endptr && head == ptr);
45 }
46
47 static auto compute_enlarged_cap(std::size_t oldcap) -> std::size_t {
48 auto alloc = std::allocator<T>();
49 auto const max_size = alloctraits::max_size(alloc);
50 if (oldcap == max_size)
51 throw std::bad_alloc();
52 if (oldcap == 0)
53 return 8;
54 std::size_t newcap = oldcap / 2 * 3;
55 if (newcap < oldcap || newcap > max_size)
56 newcap = max_size;
57 return newcap;
58 }
59
60 void expand_cap() {
61 std::size_t const siz = size();
62 std::size_t const newcap =
63 compute_enlarged_cap(as_unsigned(std::distance(ptr, endptr)));
64 auto alloc = std::allocator<T>();
65 auto const dtor = [&](T &v) { alloctraits::destroy(alloc, &v); };
66
67 T *const newptr = alloctraits::allocate(alloc, newcap);
68
69 if (head > tail) {
70 std::uninitialized_move(head, endptr, newptr);
71 std::uninitialized_move(ptr, tail,
72 newptr + std::distance(head, endptr));
73 std::for_each(head, endptr, dtor);
74 std::for_each(ptr, tail, dtor);
75 } else {
76 std::uninitialized_move(head, tail, newptr);
77 std::for_each(head, tail, dtor);
78 }
79 alloctraits::deallocate(alloc, ptr,
80 as_unsigned(std::distance(ptr, endptr)));
81
82 ptr = newptr;
83 endptr = newptr + newcap;
84 head = newptr;
85 tail = newptr + siz;
86 }
87
88 public:
89 ~vector_queue() {
90 auto alloc = std::allocator<T>();
91 auto const dtor = [&](T &v) { alloctraits::destroy(alloc, &v); };
92
93 if (head > tail) {
94 std::for_each(head, endptr, dtor);
95 std::for_each(ptr, tail, dtor);
96 } else {
97 std::for_each(head, tail, dtor);
98 }
99
100 alloctraits::deallocate(alloc, ptr,
101 as_unsigned(std::distance(ptr, endptr)));
102 }
103
104 vector_queue() noexcept
105 : ptr(nullptr), endptr(nullptr), head(nullptr), tail(nullptr) {}
106
107 vector_queue(vector_queue const &other) {
108 std::size_t const cap = other.size() > 0 ? other.size() + 1 : 0;
109 if (cap > 0) {
110 auto alloc = std::allocator<T>();
111 ptr = alloctraits::allocate(alloc, cap);
112 if (other.head > other.tail) {
113 std::uninitialized_copy(other.head, other.endptr, ptr);
114 std::uninitialized_copy(
115 other.ptr, other.tail,
116 ptr + std::distance(other.head, other.endptr));
117 } else {
118 std::uninitialized_copy(other.head, other.tail, ptr);
119 }
120 } else {
121 ptr = nullptr;
122 }
123 endptr = ptr + cap;
124 head = ptr;
125 tail = ptr + other.size();
126 }
127
128 vector_queue(vector_queue &&other) noexcept
129 : ptr(std::exchange(other.ptr, nullptr)),
130 endptr(std::exchange(other.endptr, nullptr)),
131 head(std::exchange(other.head, nullptr)),
132 tail(std::exchange(other.tail, nullptr)) {}
133
134 auto operator=(vector_queue const &rhs) -> vector_queue & {
135 vector_queue t(rhs);
136 swap(t);
137 return *this;
138 }
139
140 auto operator=(vector_queue &&rhs) noexcept -> vector_queue & {
141 vector_queue t(std::move(rhs));
142 swap(t);
143 return *this;
144 }
145
146 [[nodiscard]] constexpr auto empty() const noexcept -> bool {
147 return head == tail;
148 }
149
150 [[nodiscard]] constexpr auto size() const noexcept -> std::size_t {
151 if (head > tail)
152 return as_unsigned(std::distance(head, endptr) +
153 std::distance(ptr, tail));
154 return as_unsigned(std::distance(head, tail));
155 }
156
157 auto front() -> T & {
158 assert(head != tail);
159 return *head;
160 }
161
162 auto front() const -> T const & {
163 assert(head != tail);
164 return *head;
165 }
166
167 auto back() -> T & {
168 assert(head != tail);
169 if (tail == ptr)
170 return *(endptr - 1);
171 return *(tail - 1);
172 }
173
174 auto back() const -> T const & {
175 assert(head != tail);
176 if (tail == ptr)
177 return *(endptr - 1);
178 return *(tail - 1);
179 }
180
181 void pop() {
182 assert(head != tail);
183 auto alloc = std::allocator<T>();
184 alloctraits::destroy(alloc, head);
185 ++head; // NOLINT(cppcoreguidelines-pro-bounds-pointer-arithmetic)
186 if (head == endptr)
187 head = ptr;
188 }
189
190 void push(T const &value) {
191 if (is_full())
192 expand_cap();
193 auto alloc = std::allocator<T>();
194 alloctraits::construct(alloc, tail, value);
195 ++tail; // NOLINT(cppcoreguidelines-pro-bounds-pointer-arithmetic)
196 if (tail == endptr)
197 tail = ptr;
198 }
199
200 void push(T &&value) {
201 if (is_full())
202 expand_cap();
203 auto alloc = std::allocator<T>();
204 alloctraits::construct(alloc, tail, std::move(value));
205 ++tail; // NOLINT(cppcoreguidelines-pro-bounds-pointer-arithmetic)
206 if (tail == endptr)
207 tail = ptr;
208 }
209
210 void swap(vector_queue &other) noexcept {
211 using std::swap;
212 swap(ptr, other.ptr);
213 swap(endptr, other.endptr);
214 swap(head, other.head);
215 swap(tail, other.tail);
216 }
217
218 // Not in std::queue interface
219 template <typename F>
220 void for_each(F func) noexcept(noexcept(func(std::declval<T &>()))) {
221 if (head <= tail) {
222 std::for_each(head, tail, func);
223 } else {
224 std::for_each(head, endptr, func);
225 std::for_each(ptr, tail, func);
226 }
227 }
228
229 template <typename F>
230 void for_each(F func) const noexcept(noexcept(func(std::declval<T &>()))) {
231 if (head <= tail) {
232 std::for_each(head, tail, func);
233 } else {
234 std::for_each(head, endptr, func);
235 std::for_each(ptr, tail, func);
236 }
237 }
238};
239
240} // namespace tcspc::internal