userver: userver/utils/fixed_array.hpp Source File
Loading...
Searching...
No Matches
fixed_array.hpp
Go to the documentation of this file.
1#pragma once
2
3/// @file userver/utils/fixed_array.hpp
4/// @brief @copybrief utils::FixedArray
5
6#include <cstddef>
7#include <memory> // std::allocator
8#include <type_traits>
9#include <utility>
10
11#include <userver/utils/assert.hpp>
12
13USERVER_NAMESPACE_BEGIN
14
15namespace utils {
16
17namespace impl {
18
19struct GenerateTag final {
20 explicit GenerateTag() = default;
21};
22
23} // namespace impl
24
25/// @ingroup userver_universal userver_containers
26///
27/// @brief A fixed-size array with the size determined at runtime.
28///
29/// The array also allows initializing each of the array elements with the same
30/// parameters:
31/// @snippet src/utils/fixed_array_test.cpp Sample FixedArray
32template <class T>
33class FixedArray final {
34 public:
35 using iterator = T*;
36 using const_iterator = const T*;
37
38 /// Make an empty array
39 FixedArray() = default;
40
41 /// Make an array and initialize each element with "args"
42 template <class... Args>
43 explicit FixedArray(std::size_t size, Args&&... args);
44
45 FixedArray(FixedArray&& other) noexcept;
46 FixedArray& operator=(FixedArray&& other) noexcept;
47
48 FixedArray(const FixedArray&) = delete;
49 FixedArray& operator=(const FixedArray&) = delete;
50
51 ~FixedArray();
52
53 std::size_t size() const noexcept { return size_; }
54 bool empty() const noexcept { return size_ == 0; }
55
56 const T& operator[](std::size_t i) const noexcept {
57 UASSERT(i < size_);
58 return data()[i];
59 }
60
61 T& operator[](std::size_t i) noexcept {
62 UASSERT(i < size_);
63 return data()[i];
64 }
65
66 T& front() noexcept { return *NonEmptyData(); }
67 const T& front() const noexcept { return *NonEmptyData(); }
68
69 T& back() noexcept { return *(NonEmptyData() + size_ - 1); }
70 const T& back() const noexcept { return *(NonEmptyData() + size_ - 1); }
71
72 T* data() noexcept { return storage_; }
73 const T* data() const noexcept { return storage_; }
74
75 T* begin() noexcept { return data(); }
76 T* end() noexcept { return data() + size_; }
77 const T* begin() const noexcept { return data(); }
78 const T* end() const noexcept { return data() + size_; }
79 const T* cbegin() const noexcept { return data(); }
80 const T* cend() const noexcept { return data() + size_; }
81
82 /// @cond
83 template <class GeneratorFunc>
84 FixedArray(impl::GenerateTag tag, std::size_t size,
85 GeneratorFunc&& generator);
86 /// @endcond
87
88 private:
89 T* NonEmptyData() noexcept {
90 UASSERT(size_);
91 return data();
92 }
93
94 const T* NonEmptyData() const noexcept {
95 UASSERT(size_);
96 return data();
97 }
98
99 T* storage_{nullptr};
100 std::size_t size_{0};
101};
102
103/// @brief Applies @p generator to indices in the range `[0, size)`, storing the
104/// results in a new utils::FixedArray. The generator is guaranteed to be
105/// invoked in the first-to-last order.
106/// @param size How many objects to generate
107/// @param generator A functor that takes an index and returns an object for the
108/// `FixedArray`
109/// @returns `FixedArray` with the return objects of @p generator
110template <class GeneratorFunc>
111auto GenerateFixedArray(std::size_t size, GeneratorFunc&& generator);
112
113template <class T>
114template <class... Args>
115FixedArray<T>::FixedArray(std::size_t size, Args&&... args) : size_(size) {
116 if (size_ == 0) return;
117 storage_ = std::allocator<T>{}.allocate(size_);
118
119 auto* begin = data();
120 try {
121 for (auto* end = begin + size - 1; begin != end; ++begin) {
122 new (begin) T(args...);
123 }
124 new (begin) T(std::forward<Args>(args)...);
125 } catch (...) {
126 std::destroy(data(), begin);
127 std::allocator<T>{}.deallocate(storage_, size);
128 throw;
129 }
130}
131
132template <class T>
133template <class GeneratorFunc>
134FixedArray<T>::FixedArray(impl::GenerateTag /*tag*/, std::size_t size,
135 GeneratorFunc&& generator)
136 : size_(size) {
137 if (size_ == 0) return;
138 storage_ = std::allocator<T>{}.allocate(size_);
139
140 auto* our_begin = begin();
141 auto* const our_end = end();
142 std::size_t index = 0;
143
144 try {
145 for (; our_begin != our_end; ++our_begin) {
146 new (our_begin) T(generator(index));
147 ++index;
148 }
149 } catch (...) {
150 std::destroy(begin(), our_begin);
151 std::allocator<T>{}.deallocate(storage_, size_);
152 throw;
153 }
154}
155
156template <class T>
157FixedArray<T>::FixedArray(FixedArray&& other) noexcept
158 : storage_(std::exchange(other.storage_, nullptr)),
159 size_(std::exchange(other.size_, 0)) {}
160
161template <class T>
162FixedArray<T>& FixedArray<T>::operator=(FixedArray&& other) noexcept {
163 std::swap(storage_, other.storage_);
164 std::swap(size_, other.size_);
165 return *this;
166}
167
168template <class T>
169FixedArray<T>::~FixedArray() {
170 std::destroy(begin(), end());
171 std::allocator<T>{}.deallocate(storage_, size_);
172}
173
174template <class GeneratorFunc>
175auto GenerateFixedArray(std::size_t size, GeneratorFunc&& generator) {
176 using ResultType = std::remove_reference_t<
177 std::invoke_result_t<GeneratorFunc&, std::size_t>>;
178 return FixedArray<ResultType>(impl::GenerateTag{}, size,
179 std::forward<GeneratorFunc>(generator));
180}
181
182} // namespace utils
183
184USERVER_NAMESPACE_END