sparrow 0.9.0
Loading...
Searching...
No Matches
allocator.hpp
Go to the documentation of this file.
1// Copyright 2024 Man Group Operations Limited
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7// http://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,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15#pragma once
16
17#include <memory>
18#include <memory_resource>
19#include <type_traits>
20#include <typeindex>
21#include <variant>
22
24
25namespace sparrow
26{
27 /*
28 * allocator concept, based on the requirements specified by the standard:
29 * https://en.cppreference.com/w/cpp/named_req/Allocator.
30 */
31 template <class T>
32 concept allocator = std::copy_constructible<std::remove_cvref_t<T>>
33 and std::move_constructible<std::remove_cvref_t<T>>
34 and std::equality_comparable<std::remove_cvref_t<T>>
35 and requires(
36 std::remove_cvref_t<T>& alloc,
37 typename std::remove_cvref_t<T>::value_type* p,
38 std::size_t n
39 ) {
40 typename std::remove_cvref_t<T>::value_type;
41 {
42 alloc.allocate(n)
43 } -> std::same_as<typename std::remove_cvref_t<T>::value_type*>;
44 { alloc.deallocate(p, n) } -> std::same_as<void>;
45 };
46
47 /*
48 * When the allocator A with value_type T satisfies this concept, any_allocator
49 * can store it as a value in a small buffer instead of having to type-erased it
50 * (Small Buffer Optimization).
51 */
52 template <class A, class T>
54 && (std::same_as<A, std::allocator<T>>
55 || std::same_as<A, std::pmr::polymorphic_allocator<T>>);
56
57 /*
58 * Type erasure class for allocators. This allows to use any kind of allocator
59 * (standard, polymorphic) without having to expose it as a template parameter.
60 *
61 * @tparam T value_type of the allocator
62 */
63 template <class T>
65 {
66 public:
67
68 using value_type = T;
69
70 template <allocator A>
71 using allocator_value_type = typename std::allocator_traits<std::decay_t<A>>::value_type;
72
76
77 any_allocator& operator=(const any_allocator& rhs) = delete;
78 any_allocator& operator=(any_allocator&& rhs) = delete;
79
80 template <class A>
81 any_allocator(A&& alloc)
82 requires(
83 not std::same_as<std::remove_cvref_t<A>, any_allocator> and allocator<A>
84 and std::same_as<allocator_value_type<A>, T>
85 )
86 : m_storage(make_storage(std::forward<A>(alloc)))
87 {
88 }
89
90 [[nodiscard]] T* allocate(std::size_t n);
91 void deallocate(T* p, std::size_t n);
92
94
95 [[nodiscard]] bool equal(const any_allocator& rhs) const;
96
97 private:
98
99 struct interface
100 {
101 [[nodiscard]] virtual T* allocate(std::size_t) = 0;
102 virtual void deallocate(T*, std::size_t) = 0;
103 [[nodiscard]] virtual std::unique_ptr<interface> clone() const = 0;
104 [[nodiscard]] virtual bool equal(const interface&) const = 0;
105 virtual ~interface() = default;
106 };
107
108 template <allocator A>
109 struct impl : interface
110 {
111 A m_alloc;
112
113 explicit impl(A alloc)
114 : m_alloc(std::move(alloc))
115 {
116 }
117
118 [[nodiscard]] T* allocate(std::size_t n) override
119 {
120 return m_alloc.allocate(n);
121 }
122
123 void deallocate(T* p, std::size_t n) override
124 {
125 m_alloc.deallocate(p, n);
126 }
127
128 [[nodiscard]] std::unique_ptr<interface> clone() const override
129 {
130 return std::make_unique<impl<A>>(m_alloc);
131 }
132
133 [[nodiscard]] bool equal(const interface& rhs) const override
134 {
135 if (std::type_index(typeid(*this)) == std::type_index(typeid(rhs)))
136 {
137 return m_alloc == static_cast<const impl<A>*>(std::addressof(rhs))->m_alloc;
138 }
139 return false;
140 }
141 };
142
143 using storage_type = std::
144 variant<std::allocator<T>, std::pmr::polymorphic_allocator<T>, std::unique_ptr<interface>>;
145
146 template <class A>
147 [[nodiscard]] std::unique_ptr<interface> make_storage(A&& alloc) const
148 {
149 return std::make_unique<impl<std::decay_t<A>>>(std::forward<A>(alloc));
150 }
151
152 template <class A>
153 requires can_any_allocator_sbo<A, T>
154 [[nodiscard]] A&& make_storage(A&& alloc) const
155 {
156 return std::forward<A>(alloc);
157 }
158
159 [[nodiscard]] storage_type copy_storage(const storage_type& rhs) const
160 {
161 return std::visit(
163 [](const auto& arg) -> storage_type
164 {
165 return {std::decay_t<decltype(arg)>(arg)};
166 },
167 [](const std::unique_ptr<interface>& arg) -> storage_type
168 {
169 return {arg->clone()};
170 }
171 },
172 rhs
173 );
174 }
175
176 template <class F>
177 [[nodiscard]] decltype(auto) visit_storage(F&& f)
178 {
179 return std::visit(
180 [&f](auto&& arg)
181 {
182 using A = std::decay_t<decltype(arg)>;
183 if constexpr (can_any_allocator_sbo<A, T>)
184 {
185 return f(arg);
186 }
187 else
188 {
189 return f(*arg);
190 }
191 },
192 m_storage
193 );
194 }
195
196 storage_type m_storage;
197 };
198
199 /********************************
200 * any_allocator implementation *
201 ********************************/
202
203 template <class T>
205 : m_storage(make_storage(std::allocator<T>()))
206 {
207 }
208
209 template <class T>
211 : m_storage(copy_storage(rhs.m_storage))
212 {
213 }
214
215 template <class T>
217 : m_storage(std::move(rhs.m_storage))
218 {
219 // The possible fix would be to not propagate the allocator,
220 // and copy it when the container using it is moved.
221 // However, this means potentially cloning a polymorphic
222 // allocator, hitting performance drastically. A moved
223 // allocator might be used for trying to deallocate an empty
224 // memory zone (the memory of the moved container), wich is
225 // a noop, but the object must exist so that alloc.deallocate
226 // is valid.
227 rhs.m_storage = std::allocator<T>();
228 }
229
230 template <class T>
231 [[nodiscard]] T* any_allocator<T>::allocate(std::size_t n)
232 {
233 return visit_storage(
234 [n](auto& allocator)
235 {
236 return allocator.allocate(n);
237 }
238 );
239 }
240
241 template <class T>
242 // This is needed to avoid AddressSanitizer false positives
243#if defined(_MSC_VER) && !defined(__clang__) // MSVC
244 __declspec(no_sanitize_address)
245#else
246# if defined(__has_feature)
247# if __has_feature(address_sanitizer)
248 __attribute__((no_sanitize("address")))
249# endif
250# endif
251#endif
252 void
253 any_allocator<T>::deallocate(T* p, std::size_t n)
254 {
255 return visit_storage(
256 [n, p](auto& allocator)
257 {
258#if defined(__GNUC__)
259# pragma GCC diagnostic push
260# pragma GCC diagnostic ignored "-Wmismatched-new-delete"
261# pragma GCC diagnostic ignored "-Wnull-dereference"
262#endif
263 return allocator.deallocate(p, n);
264#if defined(__GNUC__)
265# pragma GCC diagnostic pop
266#endif
267 }
268 );
269 }
270
271 template <class T>
276
277 template <class T>
279 {
280 // YOLO!!
281 return std::visit(
282 [&rhs](auto&& arg)
283 {
284 using A = std::decay_t<decltype(arg)>;
285 if constexpr (can_any_allocator_sbo<A, T>)
286 {
287 return std::visit(
288 [&arg](auto&& arg2)
289 {
290 using A2 = std::decay_t<decltype(arg2)>;
291 if constexpr (can_any_allocator_sbo<A2, T> && std::same_as<A, A2>)
292 {
293 return arg == arg2;
294 }
295 else
296 {
297 return false;
298 }
299 },
300 rhs.m_storage
301 );
302 }
303 else
304 {
305 return std::visit(
306 [&arg](auto&& arg2)
307 {
308 using A2 = std::decay_t<decltype(arg2)>;
309 if constexpr (can_any_allocator_sbo<A2, T>)
310 {
311 return false;
312 }
313 else
314 {
315 return arg->equal(*arg2);
316 }
317 },
318 rhs.m_storage
319 );
320 }
321 },
322 m_storage
323 );
324 }
325
326 template <class T>
327 bool operator==(const any_allocator<T>& lhs, const any_allocator<T>& rhs)
328 {
329 return lhs.equal(rhs);
330 }
331}
void deallocate(T *p, std::size_t n)
T * allocate(std::size_t n)
any_allocator(const any_allocator &rhs)
typename std::allocator_traits< std::decay_t< A > >::value_type allocator_value_type
Definition allocator.hpp:71
bool equal(const any_allocator &rhs) const
any_allocator(any_allocator &&) noexcept
any_allocator select_on_container_copy_construction() const
SPARROW_API bool operator==(const array &lhs, const array &rhs)
Compares the content of two arrays.
overloaded(Ts...) -> overloaded< Ts... >