sparrow 0.3.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
71 any_allocator(const any_allocator& rhs);
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 m_alloc.allocate(n);
115 }
116
117 void deallocate(T* p, std::size_t n) override
118 {
119 m_alloc.deallocate(p, n);
120 }
121
122 [[nodiscard]] std::unique_ptr<interface> clone() const override
123 {
124 return std::make_unique<impl<A>>(m_alloc);
125 }
126
127 [[nodiscard]] bool equal(const interface& rhs) const override
128 {
129 if (std::type_index(typeid(*this)) == std::type_index(typeid(rhs)))
130 {
131 return m_alloc == static_cast<const impl<A>*>(std::addressof(rhs))->m_alloc;
132 }
133 return false;
134 }
135 };
136
137 using storage_type = std::
138 variant<std::allocator<T>, std::pmr::polymorphic_allocator<T>, std::unique_ptr<interface>>;
139
140 template <class A>
141 [[nodiscard]] std::unique_ptr<interface> make_storage(A&& alloc) const
142 {
143 return std::make_unique<impl<std::decay_t<A>>>(std::forward<A>(alloc));
144 }
145
146 template <class A>
147 requires can_any_allocator_sbo<A, T>
148 [[nodiscard]] A&& make_storage(A&& alloc) const
149 {
150 return std::forward<A>(alloc);
151 }
152
153 [[nodiscard]] storage_type copy_storage(const storage_type& rhs) const
154 {
155 return std::visit(
157 [](const auto& arg) -> storage_type
158 {
159 return {std::decay_t<decltype(arg)>(arg)};
160 },
161 [](const std::unique_ptr<interface>& arg) -> storage_type
162 {
163 return {arg->clone()};
164 }
165 },
166 rhs
167 );
168 }
169
170 template <class F>
171 [[nodiscard]] decltype(auto) visit_storage(F&& f)
172 {
173 return std::visit(
174 [&f](auto&& arg)
175 {
176 using A = std::decay_t<decltype(arg)>;
177 if constexpr (can_any_allocator_sbo<A, T>)
178 {
179 return f(arg);
180 }
181 else
182 {
183 return f(*arg);
184 }
185 },
186 m_storage
187 );
188 }
189
190 storage_type m_storage;
191 };
192
193 /********************************
194 * any_allocator implementation *
195 ********************************/
196
197 template <class T>
199 : m_storage(make_storage(std::allocator<T>()))
200 {
201 }
202
203 template <class T>
205 : m_storage(copy_storage(rhs.m_storage))
206 {
207 }
208
209 template <class T>
210 [[nodiscard]] T* any_allocator<T>::allocate(std::size_t n)
211 {
212 return visit_storage(
213 [n](auto& allocator)
214 {
215 return allocator.allocate(n);
216 }
217 );
218 }
219
220 template <class T>
221 // This is needed to avoid AddressSanitizer false positives
222#if defined(_MSC_VER) && !defined(__clang__) // MSVC
223 __declspec(no_sanitize_address)
224#else
225# if defined(__has_feature)
226# if __has_feature(address_sanitizer)
227 __attribute__((no_sanitize("address")))
228# endif
229# endif
230#endif
231 void
232 any_allocator<T>::deallocate(T* p, std::size_t n)
233 {
234 return visit_storage(
235 [n, p](auto& allocator)
236 {
237#if defined(__GNUC__)
238# pragma GCC diagnostic push
239# pragma GCC diagnostic ignored "-Wmismatched-new-delete"
240#endif
241 return allocator.deallocate(p, n);
242#if defined(__GNUC__)
243# pragma GCC diagnostic pop
244#endif
245 }
246 );
247 }
248
249 template <class T>
254
255 template <class T>
257 {
258 // YOLO!!
259 return std::visit(
260 [&rhs](auto&& arg)
261 {
262 using A = std::decay_t<decltype(arg)>;
263 if constexpr (can_any_allocator_sbo<A, T>)
264 {
265 return std::visit(
266 [&arg](auto&& arg2)
267 {
268 using A2 = std::decay_t<decltype(arg2)>;
269 if constexpr (can_any_allocator_sbo<A2, T> && std::same_as<A, A2>)
270 {
271 return arg == arg2;
272 }
273 else
274 {
275 return false;
276 }
277 },
278 rhs.m_storage
279 );
280 }
281 else
282 {
283 return std::visit(
284 [&arg](auto&& arg2)
285 {
286 using A2 = std::decay_t<decltype(arg2)>;
287 if constexpr (can_any_allocator_sbo<A2, T>)
288 {
289 return false;
290 }
291 else
292 {
293 return arg->equal(*arg2);
294 }
295 },
296 rhs.m_storage
297 );
298 }
299 },
300 m_storage
301 );
302 }
303
304 template <class T>
305 bool operator==(const any_allocator<T>& lhs, const any_allocator<T>& rhs)
306 {
307 return lhs.equal(rhs);
308 }
309}
void deallocate(T *p, std::size_t n)
T * allocate(std::size_t n)
any_allocator(any_allocator &&)=default
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... >