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