C/C++ API Reference
Loading...
Searching...
No Matches
deque.h
1// Copyright 2025 The Pigweed Authors
2//
3// Licensed under the Apache License, Version 2.0 (the "License"); you may not
4// use this file except in compliance with the License. You may obtain a copy of
5// the License at
6//
7// https://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
11// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
12// License for the specific language governing permissions and limitations under
13// the License.
14#pragma once
15
16#include <algorithm>
17#include <cstddef>
18#include <cstdint>
19#include <iterator>
20#include <limits>
21#include <type_traits>
22#include <utility>
23
24#include "lib/stdcompat/utility.h"
25#include "pw_allocator/allocator.h"
26#include "pw_allocator/unique_ptr.h"
27#include "pw_assert/assert.h"
28#include "pw_containers/internal/generic_deque.h"
29#include "pw_containers/storage.h"
30#include "pw_polyfill/language_feature_macros.h"
31#include "pw_span/span.h"
32
33namespace pw {
34
36
55template <typename T, typename SizeType = uint16_t>
56class Deque : public containers::internal::
57 GenericDeque<Deque<T, SizeType>, T, SizeType> {
58 private:
59 using Base =
61
62 public:
63 using typename Base::const_iterator;
64 using typename Base::const_pointer;
65 using typename Base::const_reference;
66 using typename Base::difference_type;
67 using typename Base::iterator;
68 using typename Base::pointer;
69 using typename Base::reference;
70 using typename Base::size_type;
71 using typename Base::value_type;
72
79 explicit constexpr Deque(span<std::byte> unaligned_buffer) noexcept
80 : Deque(Aligned::Align(unaligned_buffer)) {}
81
84 template <size_t kAlignment, size_t kSizeBytes>
85 explicit constexpr Deque(
87 : Deque(Aligned(buffer.data(), buffer.size())) {
88 static_assert(kAlignment >= alignof(value_type));
89 }
90
92 Deque(const Deque&) = delete;
93 Deque& operator=(const Deque&) = delete;
94
96 Deque(Deque&&) = delete;
97 Deque& operator=(Deque&&) = delete;
98
99 PW_CONSTEXPR_CPP20 ~Deque() { Base::DestroyAll(); }
100
101 constexpr size_type max_size() const noexcept { return Base::capacity(); }
102
103 private:
104 friend Base;
105
106 template <typename, typename>
107 friend class Queue;
108
109 template <typename, size_t, typename>
110 friend class FixedDeque;
111
112 static constexpr bool kFixedCapacity = true; // uses a fixed buffer
113
114 // Span that is known to be aligned as T.
115 class Aligned {
116 public:
117 // Construct from an UNALIGNED span. Adjusts alignment if necessary.
118 static constexpr Aligned Align(span<std::byte> unaligned_buffer) {
119 Aligned buffer(unaligned_buffer.data(), unaligned_buffer.size());
120
121 if constexpr (alignof(value_type) > alignof(std::byte)) {
122 void* data_void = buffer.data_;
123 buffer.data_ = static_cast<std::byte*>(std::align(
124 alignof(value_type), sizeof(value_type), data_void, buffer.size_));
125 if (buffer.data_ == nullptr) {
126 buffer.size_ = 0; // cannot fit any items
127 }
128 }
129
130 return buffer;
131 }
132
133 // Asserts that a buffer is aligned at least as T.
134 static constexpr Aligned Assert(std::byte* data, size_t size) {
135 void* buffer_start = data;
136 // Avoid "%" in the PW_ASSERT condition being misinterpreted as a format
137 // specifier: https://pigweed.dev/pw_assert/#in-conditions
138 uintptr_t misalignment =
139 reinterpret_cast<uintptr_t>(buffer_start) % alignof(T);
140 PW_ASSERT(misalignment == 0);
141 return Aligned(data, size);
142 }
143
144 constexpr Aligned(std::byte* aligned_data, size_t size_bytes)
145 : data_(aligned_data), size_(size_bytes) {}
146
147 constexpr std::byte* data() const { return data_; }
148 constexpr size_t capacity() const { return size_ / sizeof(value_type); }
149
150 private:
151 std::byte* data_;
152 size_t size_;
153 };
154
155 explicit constexpr Deque(Aligned buffer) noexcept
156 : Base(static_cast<size_type>(buffer.capacity())),
157 buffer_(buffer.data()) {}
158
159 constexpr void MoveBufferFrom(Deque& other) noexcept {
160 Base::DestroyAll();
161 buffer_ = cpp20::exchange(other.buffer_, nullptr);
162 Base::MoveAssignIndices(other);
163 }
164
165 void MoveItemsFrom(Deque& other) {
166 this->assign(std::make_move_iterator(other.begin()),
167 std::make_move_iterator(other.end()));
168 other.clear();
169 }
170
171 void SwapBufferWith(Deque& other) noexcept {
172 Base::SwapIndices(other);
173 std::swap(buffer_, other.buffer_);
174 }
175
176 void SwapValuesWith(Deque& other);
177
178 pointer data() { return std::launder(reinterpret_cast<pointer>(buffer_)); }
179 const_pointer data() const {
180 return std::launder(reinterpret_cast<const_pointer>(buffer_));
181 }
182
183 // Pointer to the buffer used for the deque. This may differ from the pointer
184 // passed into the constructor if it had to be shifted for alignment.
185 std::byte* buffer_;
186};
187
202template <typename T,
203 size_t kInlineCapacity = containers::kExternalStorage,
204 typename S = typename Deque<T>::size_type>
205class FixedDeque final : private containers::StorageBaseFor<T, kInlineCapacity>,
206 public Deque<T, S> {
207 public:
210 constexpr FixedDeque()
211 : containers::StorageBaseFor<T, kInlineCapacity>{},
212 Deque<T, S>(this->storage()) {}
213
214 FixedDeque(const FixedDeque&) = delete;
215 FixedDeque& operator=(const FixedDeque&) = delete;
216
217 constexpr FixedDeque(FixedDeque&& other) noexcept : FixedDeque() {
218 this->MoveItemsFrom(other);
219 }
220
221 constexpr FixedDeque& operator=(FixedDeque&& other) noexcept {
222 this->clear();
223 this->MoveItemsFrom(other);
224 return *this;
225 }
226
229 template <size_t kOtherCapacity,
230 typename = std::enable_if_t<kOtherCapacity <= kInlineCapacity>>
231 FixedDeque(FixedDeque<T, kOtherCapacity>&& other) noexcept : FixedDeque() {
232 this->MoveItemsFrom(other);
233 }
234
235 template <size_t kOtherCapacity,
236 typename = std::enable_if_t<kOtherCapacity <= kInlineCapacity>>
237 FixedDeque& operator=(FixedDeque<T, kOtherCapacity>&& other) noexcept {
238 this->clear();
239 this->MoveItemsFrom(other);
240 return *this;
241 }
242
245 template <size_t kOtherCapacity>
247 this->SwapValuesWith(other);
248 }
249
251 constexpr Deallocator* deallocator() const { return nullptr; }
252
253 private:
254 static_assert(
255 kInlineCapacity <= std::numeric_limits<S>::max(),
256 "The capacity is too large for the size_type; use a larger size_type");
257};
258
267template <typename T, typename S>
268class FixedDeque<T, containers::kExternalStorage, S> final
269 : public Deque<T, S> {
270 public:
276 static FixedDeque Allocate(Allocator& allocator, const S capacity) {
277 FixedDeque deque = TryAllocate(allocator, capacity);
278 PW_ASSERT(deque.capacity() == capacity);
279 return deque;
280 }
281
288 static FixedDeque TryAllocate(Allocator& allocator, S capacity) {
289 const allocator::Layout layout = allocator::Layout::Of<T[]>(capacity);
290 std::byte* array = static_cast<std::byte*>(allocator.Allocate(layout));
291 if (array == nullptr) {
292 return FixedDeque(Aligned(array, 0u), nullptr);
293 }
294 return FixedDeque(Aligned(array, layout.size()), &allocator);
295 }
296
301 static FixedDeque WithStorage(UniquePtr<std::byte[]>&& storage) {
302 const size_t size = storage.size();
303 Deallocator* deallocator = storage.deallocator();
304 return FixedDeque(Aligned::Assert(storage.Release(), size), deallocator);
305 }
306
314 explicit constexpr FixedDeque(span<std::byte> unaligned_buffer) noexcept
315 : Deque<T, S>(unaligned_buffer), deallocator_(nullptr) {}
316
321 template <size_t kAlignment, size_t kSizeBytes>
322 explicit constexpr FixedDeque(
324 : Deque<T, S>(buffer), deallocator_(nullptr) {}
325
327 FixedDeque(const FixedDeque&) = delete;
328 FixedDeque& operator=(const FixedDeque&) = delete;
329
334 constexpr FixedDeque(FixedDeque&& other) noexcept
335 : Deque<T, S>({}),
336 deallocator_(cpp20::exchange(other.deallocator_, nullptr)) {
337 this->MoveBufferFrom(other);
338 }
339
340 constexpr FixedDeque& operator=(FixedDeque&& other) noexcept {
341 if (this == &other) {
342 return *this;
343 }
344 T* const data = this->data();
345 this->MoveBufferFrom(other);
346
347 if (deallocator_ != nullptr) {
348 deallocator_->Deallocate(data);
349 }
350 deallocator_ = cpp20::exchange(other.deallocator_, nullptr);
351 return *this;
352 }
353
354 ~FixedDeque() {
355 // Clear the deque so that the base `Deque` destructor will not need to
356 // access its buffer, which will be freed.
357 this->clear();
358 if (deallocator_ != nullptr) {
359 deallocator_->Deallocate(this->data());
360 }
361 }
362
365 void swap(FixedDeque& other) noexcept {
366 this->SwapBufferWith(other);
367 std::swap(deallocator_, other.deallocator_);
368 }
369
374 template <size_t kInlineCapacity>
376 other.swap(*this); // Use the swap impl for statically allocated deques.
377 }
378
381 constexpr Deallocator* deallocator() const { return deallocator_; }
382
383 private:
384 using typename Deque<T, S>::Aligned;
385
386 constexpr FixedDeque(Aligned buffer, Deallocator* deallocator)
387 : Deque<T, S>(buffer), deallocator_(deallocator) {}
388
389 Deallocator* deallocator_;
390};
391
392template <typename T, typename SizeType>
393void Deque<T, SizeType>::SwapValuesWith(Deque& other) {
394 Deque* smaller;
395 Deque* larger;
396
397 if (this->size() < other.size()) {
398 smaller = this;
399 larger = &other;
400 } else {
401 smaller = &other;
402 larger = this;
403 }
404
405 const SizeType smaller_size = smaller->size();
406 for (auto it =
407 std::swap_ranges(smaller->begin(), smaller->end(), larger->begin());
408 it != larger->end();
409 ++it) {
410 smaller->push_back(std::move(*it));
411 }
412
413 while (larger->size() > smaller_size) {
414 larger->pop_back();
415 }
416}
417
418} // namespace pw
Definition: allocator.h:42
void * Allocate(Layout layout)
Abstract interface for releasing memory.
Definition: deallocator.h:30
Definition: deque.h:57
Definition: deque.h:206
Definition: unique_ptr.h:44
Definition: layout.h:64
Definition: storage.h:86
Definition: storage.h:39
Definition: generic_deque.h:195
constexpr size_type capacity() const noexcept
Returns the maximum number of elements in the deque.
Definition: generic_deque.h:72
constexpr size_type size() const noexcept
Returns the number of elements in the deque.
Definition: generic_deque.h:69
Definition: span_impl.h:235
constexpr FixedDeque()
Definition: deque.h:210
FixedDeque(const FixedDeque &)=delete
Copying is not supported since it can fail.
void swap(FixedDeque< T, kOtherCapacity, S > &other)
Definition: deque.h:246
constexpr Deallocator * deallocator() const
Definition: deque.h:381
Deque(const Deque &)=delete
Copying is not supported since it can fail.
constexpr Deallocator * deallocator() const
Returns nullptr; a FixedDeque with static storage never allocates.
Definition: deque.h:251
constexpr Deque(span< std::byte > unaligned_buffer) noexcept
Definition: deque.h:79
static FixedDeque Allocate(Allocator &allocator, const S capacity)
Definition: deque.h:276
constexpr FixedDeque(FixedDeque &&other) noexcept
Definition: deque.h:334
MoveItemsFrom(other)
constexpr Deque(containers::Storage< kAlignment, kSizeBytes > &buffer) noexcept
Definition: deque.h:85
static FixedDeque WithStorage(UniquePtr< std::byte[]> &&storage)
Definition: deque.h:301
constexpr FixedDeque(containers::Storage< kAlignment, kSizeBytes > &buffer) noexcept
Definition: deque.h:322
Deque(Deque &&)=delete
Move is not supported to avoid confusion about deque/buffer pairings.
void swap(FixedDeque< T, kInlineCapacity, S > &other)
Definition: deque.h:375
static FixedDeque TryAllocate(Allocator &allocator, S capacity)
Definition: deque.h:288
void swap(FixedDeque &other) noexcept
Definition: deque.h:365
constexpr FixedDeque(span< std::byte > unaligned_buffer) noexcept
Definition: deque.h:314
constexpr size_t kExternalStorage
Reserved capacity value for container specializations with external storage.
Definition: storage.h:79
#define PW_CONSTEXPR_CPP20
Definition: language_feature_macros.h:27
The Pigweed namespace.
Definition: alignment.h:27