sparrow 0.6.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
73
74 any_allocator& operator=(const any_allocator& rhs) = delete;
76
77 template <class A>
78 any_allocator(A&& alloc)
79 requires(not std::same_as<std::remove_cvref_t<A>, any_allocator> and sparrow::allocator<A>)
80 : m_storage(make_storage(std::forward<A>(alloc)))
81 {
82 }
83
84 [[nodiscard]] T* allocate(std::size_t n);
85 void deallocate(T* p, std::size_t n);
86
88
89 [[nodiscard]] bool equal(const any_allocator& rhs) const;
90
91 private:
92
93 struct interface
94 {
95 [[nodiscard]] virtual T* allocate(std::size_t) = 0;
96 virtual void deallocate(T*, std::size_t) = 0;
97 [[nodiscard]] virtual std::unique_ptr<interface> clone() const = 0;
98 [[nodiscard]] virtual bool equal(const interface&) const = 0;
99 virtual ~interface() = default;
100 };
101
102 template <allocator A>
103 struct impl : interface
104 {
105 A m_alloc;
106
107 explicit impl(A alloc)
108 : m_alloc(std::move(alloc))
109 {
110 }
111
112 [[nodiscard]] T* allocate(std::size_t n) override
113 {
114 return reinterpret_cast<T*>(m_alloc.allocate(n)); // TODO: To refactor. The allocator should
115 // return the right type
116 }
117
118 void deallocate(T* p, std::size_t n) override
119 {
120#if defined(__GNUC__)
121# pragma GCC diagnostic push
122# pragma GCC diagnostic ignored "-Wcast-align"
123#endif
124 m_alloc.deallocate(reinterpret_cast<A::value_type*>(p), n);
125#if defined(__GNUC__)
126# pragma GCC diagnostic pop
127#endif
128 }
129
130 [[nodiscard]] std::unique_ptr<interface> clone() const override
131 {
132 return std::make_unique<impl<A>>(m_alloc);
133 }
134
135 [[nodiscard]] bool equal(const interface& rhs) const override
136 {
137 if (std::type_index(typeid(*this)) == std::type_index(typeid(rhs)))
138 {
139 return m_alloc == static_cast<const impl<A>*>(std::addressof(rhs))->m_alloc;
140 }
141 return false;
142 }
143 };
144
145 using storage_type = std::
146 variant<std::allocator<T>, std::pmr::polymorphic_allocator<T>, std::unique_ptr<interface>>;
147
148 template <class A>
149 [[nodiscard]] std::unique_ptr<interface> make_storage(A&& alloc) const
150 {
151 return std::make_unique<impl<std::decay_t<A>>>(std::forward<A>(alloc));
152 }
153
154 template <class A>
155 requires can_any_allocator_sbo<A, T>
156 [[nodiscard]] A&& make_storage(A&& alloc) const
157 {
158 return std::forward<A>(alloc);
159 }
160
161 [[nodiscard]] storage_type copy_storage(const storage_type& rhs) const
162 {
163 return std::visit(
165 [](const auto& arg) -> storage_type
166 {
167 return {std::decay_t<decltype(arg)>(arg)};
168 },
169 [](const std::unique_ptr<interface>& arg) -> storage_type
170 {
171 return {arg->clone()};
172 }
173 },
174 rhs
175 );
176 }
177
178 template <class F>
179 [[nodiscard]] decltype(auto) visit_storage(F&& f)
180 {
181 return std::visit(
182 [&f](auto&& arg)
183 {
184 using A = std::decay_t<decltype(arg)>;
185 if constexpr (can_any_allocator_sbo<A, T>)
186 {
187 return f(arg);
188 }
189 else
190 {
191 return f(*arg);
192 }
193 },
194 m_storage
195 );
196 }
197
198 storage_type m_storage;
199 };
200
201 /********************************
202 * any_allocator implementation *
203 ********************************/
204
205 template <class T>
207 : m_storage(make_storage(std::allocator<T>()))
208 {
209 }
210
211 template <class T>
213 : m_storage(copy_storage(rhs.m_storage))
214 {
215 }
216
217 template <class T>
218 [[nodiscard]] T* any_allocator<T>::allocate(std::size_t n)
219 {
220 return visit_storage(
221 [n](auto& allocator)
222 {
223 return allocator.allocate(n);
224 }
225 );
226 }
227
228 template <class T>
229 // This is needed to avoid AddressSanitizer false positives
230#if defined(_MSC_VER) && !defined(__clang__) // MSVC
231 __declspec(no_sanitize_address)
232#else
233# if defined(__has_feature)
234# if __has_feature(address_sanitizer)
235 __attribute__((no_sanitize("address")))
236# endif
237# endif
238#endif
239 void
240 any_allocator<T>::deallocate(T* p, std::size_t n)
241 {
242 return visit_storage(
243 [n, p](auto& allocator)
244 {
245#if defined(__GNUC__)
246# pragma GCC diagnostic push
247# pragma GCC diagnostic ignored "-Wmismatched-new-delete"
248# pragma GCC diagnostic ignored "-Wnull-dereference"
249#endif
250 return allocator.deallocate(p, n);
251#if defined(__GNUC__)
252# pragma GCC diagnostic pop
253#endif
254 }
255 );
256 }
257
258 template <class T>
263
264 template <class T>
266 {
267 // YOLO!!
268 return std::visit(
269 [&rhs](auto&& arg)
270 {
271 using A = std::decay_t<decltype(arg)>;
272 if constexpr (can_any_allocator_sbo<A, T>)
273 {
274 return std::visit(
275 [&arg](auto&& arg2)
276 {
277 using A2 = std::decay_t<decltype(arg2)>;
278 if constexpr (can_any_allocator_sbo<A2, T> && std::same_as<A, A2>)
279 {
280 return arg == arg2;
281 }
282 else
283 {
284 return false;
285 }
286 },
287 rhs.m_storage
288 );
289 }
290 else
291 {
292 return std::visit(
293 [&arg](auto&& arg2)
294 {
295 using A2 = std::decay_t<decltype(arg2)>;
296 if constexpr (can_any_allocator_sbo<A2, T>)
297 {
298 return false;
299 }
300 else
301 {
302 return arg->equal(*arg2);
303 }
304 },
305 rhs.m_storage
306 );
307 }
308 },
309 m_storage
310 );
311 }
312
313 template <class T>
314 bool operator==(const any_allocator<T>& lhs, const any_allocator<T>& rhs)
315 {
316 return lhs.equal(rhs);
317 }
318}
void deallocate(T *p, std::size_t n)
T * allocate(std::size_t n)
any_allocator(any_allocator &&)=default
any_allocator(const any_allocator &rhs)
any_allocator & operator=(any_allocator &&rhs)=delete
any_allocator & operator=(const any_allocator &rhs)=delete
bool equal(const any_allocator &rhs) const
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... >