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 <iterator>
8#include <memory> // std::allocator
9#include <type_traits>
10#include <utility>
11
12#include <userver/compiler/impl/lifetime.hpp>
13#include <userver/utils/assert.hpp>
14#include <userver/utils/impl/internal_tag.hpp>
15
16USERVER_NAMESPACE_BEGIN
17
18namespace utils {
19
20/// @ingroup userver_universal userver_containers
21///
22/// @brief A fixed-size array with the size determined at runtime.
23///
24/// The array allows initializing each of the array elements with the same parameters:
25/// @snippet src/utils/fixed_array_test.cpp Sample FixedArray
26///
27/// The array also allows initializing each of the array elements with the output of a generator function:
28/// @snippet src/utils/fixed_array_test.cpp Sample GenerateFixedArray
29template <class T>
30class FixedArray final {
31public:
32 using iterator = T*;
33 using const_iterator = const T*;
34
35 /// Make an empty array
36 FixedArray() = default;
37
38 /// Make an array and initialize each element with "args"
39 template <class... Args>
40 explicit FixedArray(std::size_t size, Args&&... args);
41
42 /// Make an array by copying elements from a forward iterator range [first, last).
43 template <std::forward_iterator ForwardIterator>
44 FixedArray(ForwardIterator first, ForwardIterator last);
45
46 FixedArray(FixedArray&& other) noexcept;
47 FixedArray& operator=(FixedArray&& other) noexcept;
48
49 FixedArray(const FixedArray&) = delete;
50 FixedArray& operator=(const FixedArray&) = delete;
51
52 ~FixedArray();
53
54 std::size_t size() const noexcept { return size_; }
55 bool empty() const noexcept { return size_ == 0; }
56
57 const T& operator[](std::size_t i) const noexcept USERVER_IMPL_LIFETIME_BOUND {
58 UASSERT(i < size_);
59 return data()[i];
60 }
61
62 T& operator[](std::size_t i) noexcept USERVER_IMPL_LIFETIME_BOUND {
63 UASSERT(i < size_);
64 return data()[i];
65 }
66
67 T& front() noexcept USERVER_IMPL_LIFETIME_BOUND { return *NonEmptyData(); }
68 const T& front() const noexcept USERVER_IMPL_LIFETIME_BOUND { return *NonEmptyData(); }
69
70 T& back() noexcept USERVER_IMPL_LIFETIME_BOUND { return *(NonEmptyData() + size_ - 1); }
71 const T& back() const noexcept USERVER_IMPL_LIFETIME_BOUND { return *(NonEmptyData() + size_ - 1); }
72
73 T* data() noexcept USERVER_IMPL_LIFETIME_BOUND { return storage_; }
74 const T* data() const noexcept USERVER_IMPL_LIFETIME_BOUND { return storage_; }
75
76 T* begin() noexcept USERVER_IMPL_LIFETIME_BOUND { return data(); }
77 T* end() noexcept USERVER_IMPL_LIFETIME_BOUND { return data() + size_; }
78 const T* begin() const noexcept USERVER_IMPL_LIFETIME_BOUND { return data(); }
79 const T* end() const noexcept USERVER_IMPL_LIFETIME_BOUND { return data() + size_; }
80 const T* cbegin() const noexcept USERVER_IMPL_LIFETIME_BOUND { return data(); }
81 const T* cend() const noexcept USERVER_IMPL_LIFETIME_BOUND { return data() + size_; }
82
83 /// @cond
84 template <class GeneratorFunc>
85 FixedArray(impl::InternalTag tag, std::size_t size, GeneratorFunc&& generator);
86 /// @endcond
87
88private:
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 invoked in the first-to-last order.
105///
106/// Usage example:
107/// @snippet src/utils/fixed_array_test.cpp Sample GenerateFixedArray
108///
109/// @param size How many objects to generate
110/// @param generator A functor that takes an index and returns an object for the
111/// `FixedArray`
112/// @returns `FixedArray` with the return objects of @p generator
113template <class GeneratorFunc>
114auto GenerateFixedArray(std::size_t size, GeneratorFunc&& generator);
115
116template <class T>
117template <class... Args>
118FixedArray<T>::FixedArray(std::size_t size, Args&&... args)
119 : size_(size)
120{
121 if (size_ == 0) {
122 return;
123 }
124 storage_ = std::allocator<T>{}.allocate(size_);
125
126 auto* begin = data();
127 try {
128 for (auto* end = begin + size - 1; begin != end; ++begin) {
129 new (begin) T(args...);
130 }
131 new (begin) T(std::forward<Args>(args)...);
132 } catch (...) {
133 std::destroy(data(), begin);
134 std::allocator<T>{}.deallocate(storage_, size);
135 throw;
136 }
137}
138
139template <class T>
140template <std::forward_iterator ForwardIterator>
141FixedArray<T>::FixedArray(ForwardIterator first, ForwardIterator last)
142 : FixedArray(
143 impl::InternalTag{},
144 static_cast<std::size_t>(std::distance(first, last)),
145 [it = first](std::size_t) mutable -> decltype(*first) { return *it++; }
146 )
147{}
148
149template <class T>
150template <class GeneratorFunc>
151// Accepting `generator` by forwarding reference to be able to be called with both prvalue and non-const references.
152// NOLINTNEXTLINE(cppcoreguidelines-missing-std-forward)
153FixedArray<T>::FixedArray(impl::InternalTag /*tag*/, std::size_t size, GeneratorFunc&& generator)
154 : size_(size)
155{
156 if (size_ == 0) {
157 return;
158 }
159 storage_ = std::allocator<T>{}.allocate(size_);
160
161 auto* our_begin = begin();
162 auto* const our_end = end();
163 std::size_t index = 0;
164
165 try {
166 for (; our_begin != our_end; ++our_begin) {
167 new (our_begin) T(generator(index));
168 ++index;
169 }
170 } catch (...) {
171 std::destroy(begin(), our_begin);
172 std::allocator<T>{}.deallocate(storage_, size_);
173 throw;
174 }
175}
176
177template <class T>
178FixedArray<T>::FixedArray(FixedArray&& other) noexcept
179 : storage_(std::exchange(other.storage_, nullptr)), size_(std::exchange(other.size_, 0)) {}
180
181template <class T>
182FixedArray<T>& FixedArray<T>::operator=(FixedArray&& other) noexcept {
183 std::swap(storage_, other.storage_);
184 std::swap(size_, other.size_);
185 return *this;
186}
187
188template <class T>
189FixedArray<T>::~FixedArray() {
190 std::destroy(begin(), end());
191 std::allocator<T>{}.deallocate(storage_, size_);
192}
193
194template <class GeneratorFunc>
195auto GenerateFixedArray(std::size_t size, GeneratorFunc&& generator) {
196 using ResultType = std::remove_reference_t<std::invoke_result_t<GeneratorFunc&, std::size_t>>;
197 return FixedArray<ResultType>(impl::InternalTag{}, size, std::forward<GeneratorFunc>(generator));
198}
199
200} // namespace utils
201
202USERVER_NAMESPACE_END