]> git.proxmox.com Git - ceph.git/blob - ceph/src/common/static_ptr.h
import 15.2.0 Octopus source
[ceph.git] / ceph / src / common / static_ptr.h
1 // -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*-
2 // vim: ts=8 sw=2 smarttab
3 /*
4 * Ceph - scalable distributed file system
5 *
6 * Copyright (C) 2017 Red Hat, Inc.
7 *
8 * This is free software; you can redistribute it and/or
9 * modify it under the terms of the GNU Lesser General Public
10 * License version 2.1, as published by the Free Software
11 * Foundation. See file COPYING.
12 *
13 */
14
15 #pragma once
16
17 #include <cstddef>
18 #include <utility>
19 #include <type_traits>
20
21 namespace ceph {
22 // `static_ptr`
23 // ===========
24 //
25 // It would be really nice if polymorphism didn't require a bunch of
26 // mucking about with the heap. So let's build something where we
27 // don't have to do that.
28 //
29 namespace _mem {
30
31 // This, an operator function, is one of the canonical ways to do type
32 // erasure in C++ so long as all operations can be done with subsets
33 // of the same arguments (which is not true for function type erasure)
34 // it's a pretty good one.
35 enum class op {
36 copy, move, destroy, size
37 };
38 template<typename T>
39 static std::size_t op_fun(op oper, void* p1, void* p2)
40 {
41 auto me = static_cast<T*>(p1);
42
43 switch (oper) {
44 case op::copy:
45 // One conspicuous downside is that immovable/uncopyable functions
46 // kill compilation right here, even if nobody ever calls the move
47 // or copy methods. Working around this is a pain, since we'd need
48 // four operator functions and a top-level class to
49 // provide/withhold copy/move operations as appropriate.
50 new (p2) T(*me);
51 break;
52
53 case op::move:
54 new (p2) T(std::move(*me));
55 break;
56
57 case op::destroy:
58 me->~T();
59 break;
60
61 case op::size:
62 return sizeof(T);
63 }
64 return 0;
65 }
66 }
67 // The thing itself!
68 //
69 // The default value for Size may be wrong in almost all cases. You
70 // can change it to your heart's content. The upside is that you'll
71 // just get a compile error and you can bump it up.
72 //
73 // I *recommend* having a size constant in header files (or perhaps a
74 // using declaration, e.g.
75 // ```
76 // using StaticFoo = static_ptr<Foo, sizeof(Blah)>`
77 // ```
78 // in some header file that can be used multiple places) so that when
79 // you create a new derived class with a larger size, you only have to
80 // change it in one place.
81 //
82 template<typename Base, std::size_t Size = sizeof(Base)>
83 class static_ptr {
84 template<typename U, std::size_t S>
85 friend class static_ptr;
86
87 // Refuse to be set to anything with whose type we are
88 // incompatible. Also never try to eat anything bigger than you are.
89 //
90 template<typename T, std::size_t S>
91 constexpr static int create_ward() noexcept {
92 static_assert(std::is_void_v<Base> ||
93 std::is_base_of_v<Base, std::decay_t<T>>,
94 "Value to store must be a derivative of the base.");
95 static_assert(S <= Size, "Value too large.");
96 static_assert(std::is_void_v<Base> || !std::is_const<Base>{} ||
97 std::is_const_v<T>,
98 "Cannot assign const pointer to non-const pointer.");
99 return 0;
100 }
101 // Here we can store anything that has the same signature, which is
102 // relevant to the multiple-versions for move/copy support that I
103 // mentioned above.
104 //
105 size_t (*operate)(_mem::op, void*, void*);
106
107 // This is mutable so that get and the dereference operators can be
108 // const. Since we're modeling a pointer, we should preserve the
109 // difference in semantics between a pointer-to-const and a const
110 // pointer.
111 //
112 mutable typename std::aligned_storage<Size>::type buf;
113
114 public:
115 using element_type = Base;
116 using pointer = Base*;
117
118 // Empty
119 static_ptr() noexcept : operate(nullptr) {}
120 static_ptr(std::nullptr_t) noexcept : operate(nullptr) {}
121 static_ptr& operator =(std::nullptr_t) noexcept {
122 reset();
123 return *this;
124 }
125 ~static_ptr() noexcept {
126 reset();
127 }
128
129 // Since other pointer-ish types have it
130 void reset() noexcept {
131 if (operate) {
132 operate(_mem::op::destroy, &buf, nullptr);
133 operate = nullptr;
134 }
135 }
136
137 // Set from another static pointer.
138 //
139 // Since the templated versions don't count for overriding the defaults
140 static_ptr(const static_ptr& rhs)
141 noexcept(std::is_nothrow_copy_constructible_v<Base>) : operate(rhs.operate) {
142 if (operate) {
143 operate(_mem::op::copy, &rhs.buf, &buf);
144 }
145 }
146 static_ptr(static_ptr&& rhs)
147 noexcept(std::is_nothrow_move_constructible_v<Base>) : operate(rhs.operate) {
148 if (operate) {
149 operate(_mem::op::move, &rhs.buf, &buf);
150 }
151 }
152
153 template<typename U, std::size_t S>
154 static_ptr(const static_ptr<U, S>& rhs)
155 noexcept(std::is_nothrow_copy_constructible_v<U>) : operate(rhs.operate) {
156 create_ward<U, S>();
157 if (operate) {
158 operate(_mem::op::copy, &rhs.buf, &buf);
159 }
160 }
161 template<typename U, std::size_t S>
162 static_ptr(static_ptr<U, S>&& rhs)
163 noexcept(std::is_nothrow_move_constructible_v<U>) : operate(rhs.operate) {
164 create_ward<U, S>();
165 if (operate) {
166 operate(_mem::op::move, &rhs.buf, &buf);
167 }
168 }
169
170 static_ptr& operator =(const static_ptr& rhs)
171 noexcept(std::is_nothrow_copy_constructible_v<Base>) {
172 reset();
173 if (rhs) {
174 operate = rhs.operate;
175 operate(_mem::op::copy,
176 const_cast<void*>(static_cast<const void*>(&rhs.buf)), &buf);
177 }
178 return *this;
179 }
180 static_ptr& operator =(static_ptr&& rhs)
181 noexcept(std::is_nothrow_move_constructible_v<Base>) {
182 reset();
183 if (rhs) {
184 operate = rhs.operate;
185 operate(_mem::op::move, &rhs.buf, &buf);
186 }
187 return *this;
188 }
189
190 template<typename U, std::size_t S>
191 static_ptr& operator =(const static_ptr<U, S>& rhs)
192 noexcept(std::is_nothrow_copy_constructible_v<U>) {
193 create_ward<U, S>();
194 reset();
195 if (rhs) {
196 operate = rhs.operate;
197 operate(_mem::op::copy,
198 const_cast<void*>(static_cast<const void*>(&rhs.buf)), &buf);
199 }
200 return *this;
201 }
202 template<typename U, std::size_t S>
203 static_ptr& operator =(static_ptr<U, S>&& rhs)
204 noexcept(std::is_nothrow_move_constructible_v<U>) {
205 create_ward<U, S>();
206 reset();
207 if (rhs) {
208 operate = rhs.operate;
209 operate(_mem::op::move, &rhs.buf, &buf);
210 }
211 return *this;
212 }
213
214 // In-place construction!
215 //
216 // This is basically what you want, and I didn't include value
217 // construction because in-place construction renders it
218 // unnecessary. Also it doesn't fit the pointer idiom as well.
219 //
220 template<typename T, typename... Args>
221 static_ptr(std::in_place_type_t<T>, Args&& ...args)
222 noexcept(std::is_nothrow_constructible_v<T, Args...>)
223 : operate(&_mem::op_fun<T>){
224 static_assert((!std::is_nothrow_copy_constructible_v<Base> ||
225 std::is_nothrow_copy_constructible_v<T>) &&
226 (!std::is_nothrow_move_constructible_v<Base> ||
227 std::is_nothrow_move_constructible_v<T>),
228 "If declared type of static_ptr is nothrow "
229 "move/copy constructible, then any "
230 "type assigned to it must be as well. "
231 "You can use reinterpret_pointer_cast "
232 "to get around this limit, but don't "
233 "come crying to me when the C++ "
234 "runtime calls terminate().");
235 create_ward<T, sizeof(T)>();
236 new (&buf) T(std::forward<Args>(args)...);
237 }
238
239 // I occasionally get tempted to make an overload of the assignment
240 // operator that takes a tuple as its right-hand side to provide
241 // arguments.
242 //
243 template<typename T, typename... Args>
244 void emplace(Args&& ...args)
245 noexcept(std::is_nothrow_constructible_v<T, Args...>) {
246 create_ward<T, sizeof(T)>();
247 reset();
248 operate = &_mem::op_fun<T>;
249 new (&buf) T(std::forward<Args>(args)...);
250 }
251
252 // Access!
253 Base* get() const noexcept {
254 return operate ? reinterpret_cast<Base*>(&buf) : nullptr;
255 }
256 template<typename U = Base>
257 std::enable_if_t<!std::is_void_v<U>, Base*> operator->() const noexcept {
258 return get();
259 }
260 template<typename U = Base>
261 std::enable_if_t<!std::is_void_v<U>, Base&> operator *() const noexcept {
262 return *get();
263 }
264 operator bool() const noexcept {
265 return !!operate;
266 }
267
268 // Big wall of friendship
269 //
270 template<typename U, std::size_t Z, typename T, std::size_t S>
271 friend static_ptr<U, Z> static_pointer_cast(const static_ptr<T, S>& p);
272 template<typename U, std::size_t Z, typename T, std::size_t S>
273 friend static_ptr<U, Z> static_pointer_cast(static_ptr<T, S>&& p);
274
275 template<typename U, std::size_t Z, typename T, std::size_t S>
276 friend static_ptr<U, Z> dynamic_pointer_cast(const static_ptr<T, S>& p);
277 template<typename U, std::size_t Z, typename T, std::size_t S>
278 friend static_ptr<U, Z> dynamic_pointer_cast(static_ptr<T, S>&& p);
279
280 template<typename U, std::size_t Z, typename T, std::size_t S>
281 friend static_ptr<U, Z> const_pointer_cast(const static_ptr<T, S>& p);
282 template<typename U, std::size_t Z, typename T, std::size_t S>
283 friend static_ptr<U, Z> const_pointer_cast(static_ptr<T, S>&& p);
284
285 template<typename U, std::size_t Z, typename T, std::size_t S>
286 friend static_ptr<U, Z> reinterpret_pointer_cast(const static_ptr<T, S>& p);
287 template<typename U, std::size_t Z, typename T, std::size_t S>
288 friend static_ptr<U, Z> reinterpret_pointer_cast(static_ptr<T, S>&& p);
289
290 template<typename U, std::size_t Z, typename T, std::size_t S>
291 friend static_ptr<U, Z> resize_pointer_cast(const static_ptr<T, S>& p);
292 template<typename U, std::size_t Z, typename T, std::size_t S>
293 friend static_ptr<U, Z> resize_pointer_cast(static_ptr<T, S>&& p);
294 };
295
296 // These are all modeled after the same ones for shared pointer.
297 //
298 // Also I'm annoyed that the standard library doesn't have
299 // *_pointer_cast overloads for a move-only unique pointer. It's a
300 // nice idiom. Having to release and reconstruct is obnoxious.
301 //
302 template<typename U, std::size_t Z, typename T, std::size_t S>
303 static_ptr<U, Z> static_pointer_cast(const static_ptr<T, S>& p) {
304 static_assert(Z >= S,
305 "Value too large.");
306 static_ptr<U, Z> r;
307 // Really, this is always true because static_cast either succeeds
308 // or fails to compile, but it prevents an unused variable warning
309 // and should be optimized out.
310 if (static_cast<U*>(p.get())) {
311 p.operate(_mem::op::copy, &p.buf, &r.buf);
312 r.operate = p.operate;
313 }
314 return r;
315 }
316 template<typename U, std::size_t Z, typename T, std::size_t S>
317 static_ptr<U, Z> static_pointer_cast(static_ptr<T, S>&& p) {
318 static_assert(Z >= S,
319 "Value too large.");
320 static_ptr<U, Z> r;
321 if (static_cast<U*>(p.get())) {
322 p.operate(_mem::op::move, &p.buf, &r.buf);
323 r.operate = p.operate;
324 }
325 return r;
326 }
327
328 // Here the conditional is actually important and ensures we have the
329 // same behavior as dynamic_cast.
330 //
331 template<typename U, std::size_t Z, typename T, std::size_t S>
332 static_ptr<U, Z> dynamic_pointer_cast(const static_ptr<T, S>& p) {
333 static_assert(Z >= S,
334 "Value too large.");
335 static_ptr<U, Z> r;
336 if (dynamic_cast<U*>(p.get())) {
337 p.operate(_mem::op::copy, &p.buf, &r.buf);
338 r.operate = p.operate;
339 }
340 return r;
341 }
342 template<typename U, std::size_t Z, typename T, std::size_t S>
343 static_ptr<U, Z> dynamic_pointer_cast(static_ptr<T, S>&& p) {
344 static_assert(Z >= S,
345 "Value too large.");
346 static_ptr<U, Z> r;
347 if (dynamic_cast<U*>(p.get())) {
348 p.operate(_mem::op::move, &p.buf, &r.buf);
349 r.operate = p.operate;
350 }
351 return r;
352 }
353
354 template<typename U, std::size_t Z, typename T, std::size_t S>
355 static_ptr<U, Z> const_pointer_cast(const static_ptr<T, S>& p) {
356 static_assert(Z >= S,
357 "Value too large.");
358 static_ptr<U, Z> r;
359 if (const_cast<U*>(p.get())) {
360 p.operate(_mem::op::copy, &p.buf, &r.buf);
361 r.operate = p.operate;
362 }
363 return r;
364 }
365 template<typename U, std::size_t Z, typename T, std::size_t S>
366 static_ptr<U, Z> const_pointer_cast(static_ptr<T, S>&& p) {
367 static_assert(Z >= S,
368 "Value too large.");
369 static_ptr<U, Z> r;
370 if (const_cast<U*>(p.get())) {
371 p.operate(_mem::op::move, &p.buf, &r.buf);
372 r.operate = p.operate;
373 }
374 return r;
375 }
376
377 // I'm not sure if anyone will ever use this. I can imagine situations
378 // where they might. It works, though!
379 //
380 template<typename U, std::size_t Z, typename T, std::size_t S>
381 static_ptr<U, Z> reinterpret_pointer_cast(const static_ptr<T, S>& p) {
382 static_assert(Z >= S,
383 "Value too large.");
384 static_ptr<U, Z> r;
385 p.operate(_mem::op::copy, &p.buf, &r.buf);
386 r.operate = p.operate;
387 return r;
388 }
389 template<typename U, std::size_t Z, typename T, std::size_t S>
390 static_ptr<U, Z> reinterpret_pointer_cast(static_ptr<T, S>&& p) {
391 static_assert(Z >= S,
392 "Value too large.");
393 static_ptr<U, Z> r;
394 p.operate(_mem::op::move, &p.buf, &r.buf);
395 r.operate = p.operate;
396 return r;
397 }
398
399 // This is the only way to move from a bigger static pointer into a
400 // smaller static pointer. The size of the total data stored in the
401 // pointer is checked at runtime and if the destination size is large
402 // enough, we copy it over.
403 //
404 // I follow cast semantics. Since this is a pointer-like type, it
405 // returns a null value rather than throwing.
406 template<typename U, std::size_t Z, typename T, std::size_t S>
407 static_ptr<U, Z> resize_pointer_cast(const static_ptr<T, S>& p) {
408 static_assert(std::is_same_v<U, T>,
409 "resize_pointer_cast only changes size, not type.");
410 static_ptr<U, Z> r;
411 if (Z >= p.operate(_mem::op::size, &p.buf, nullptr)) {
412 p.operate(_mem::op::copy, &p.buf, &r.buf);
413 r.operate = p.operate;
414 }
415 return r;
416 }
417 template<typename U, std::size_t Z, typename T, std::size_t S>
418 static_ptr<U, Z> resize_pointer_cast(static_ptr<T, S>&& p) {
419 static_assert(std::is_same_v<U, T>,
420 "resize_pointer_cast only changes size, not type.");
421 static_ptr<U, Z> r;
422 if (Z >= p.operate(_mem::op::size, &p.buf, nullptr)) {
423 p.operate(_mem::op::move, &p.buf, &r.buf);
424 r.operate = p.operate;
425 }
426 return r;
427 }
428
429 template<typename Base, std::size_t Size>
430 bool operator ==(static_ptr<Base, Size> s, std::nullptr_t) {
431 return !s;
432 }
433 template<typename Base, std::size_t Size>
434 bool operator ==(std::nullptr_t, static_ptr<Base, Size> s) {
435 return !s;
436 }
437
438 // Since `make_unique` and `make_shared` exist, we should follow their
439 // lead.
440 //
441 template<typename Base, typename Derived = Base,
442 std::size_t Size = sizeof(Derived), typename... Args>
443 static_ptr<Base, Size> make_static(Args&& ...args) {
444 return { std::in_place_type<Derived>, std::forward<Args>(args)... };
445 }
446 }