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 {
34public:
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, GeneratorFunc&& generator);
85 /// @endcond
86
87private:
88 T* NonEmptyData() noexcept {
89 UASSERT(size_);
90 return data();
91 }
92
93 const T* NonEmptyData() const noexcept {
94 UASSERT(size_);
95 return data();
96 }
97
98 T* storage_{nullptr};
99 std::size_t size_{0};
100};
101
102/// @brief Applies @p generator to indices in the range `[0, size)`, storing the
103/// results in a new utils::FixedArray. The generator is guaranteed to be
104/// invoked in the first-to-last order.
105/// @param size How many objects to generate
106/// @param generator A functor that takes an index and returns an object for the
107/// `FixedArray`
108/// @returns `FixedArray` with the return objects of @p generator
109template <class GeneratorFunc>
110auto GenerateFixedArray(std::size_t size, GeneratorFunc&& generator);
111
112template <class T>
113template <class... Args>
114FixedArray<T>::FixedArray(std::size_t size, Args&&... args) : size_(size) {
115 if (size_ == 0) return;
116 storage_ = std::allocator<T>{}.allocate(size_);
117
118 auto* begin = data();
119 try {
120 for (auto* end = begin + size - 1; begin != end; ++begin) {
121 new (begin) T(args...);
122 }
123 new (begin) T(std::forward<Args>(args)...);
124 } catch (...) {
125 std::destroy(data(), begin);
126 std::allocator<T>{}.deallocate(storage_, size);
127 throw;
128 }
129}
130
131template <class T>
132template <class GeneratorFunc>
133FixedArray<T>::FixedArray(impl::GenerateTag /*tag*/, std::size_t size, GeneratorFunc&& generator) : size_(size) {
134 if (size_ == 0) return;
135 storage_ = std::allocator<T>{}.allocate(size_);
136
137 auto* our_begin = begin();
138 auto* const our_end = end();
139 std::size_t index = 0;
140
141 try {
142 for (; our_begin != our_end; ++our_begin) {
143 new (our_begin) T(generator(index));
144 ++index;
145 }
146 } catch (...) {
147 std::destroy(begin(), our_begin);
148 std::allocator<T>{}.deallocate(storage_, size_);
149 throw;
150 }
151}
152
153template <class T>
154FixedArray<T>::FixedArray(FixedArray&& other) noexcept
155 : storage_(std::exchange(other.storage_, nullptr)), size_(std::exchange(other.size_, 0)) {}
156
157template <class T>
158FixedArray<T>& FixedArray<T>::operator=(FixedArray&& other) noexcept {
159 std::swap(storage_, other.storage_);
160 std::swap(size_, other.size_);
161 return *this;
162}
163
164template <class T>
165FixedArray<T>::~FixedArray() {
166 std::destroy(begin(), end());
167 std::allocator<T>{}.deallocate(storage_, size_);
168}
169
170template <class GeneratorFunc>
171auto GenerateFixedArray(std::size_t size, GeneratorFunc&& generator) {
172 using ResultType = std::remove_reference_t<std::invoke_result_t<GeneratorFunc&, std::size_t>>;
173 return FixedArray<ResultType>(impl::GenerateTag{}, size, std::forward<GeneratorFunc>(generator));
174}
175
176} // namespace utils
177
178USERVER_NAMESPACE_END