]> git.proxmox.com Git - ceph.git/blob - ceph/src/common/static_ptr.h
import quincy beta 17.1.0
[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 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::move:
45 new (p2) T(std::move(*me));
46 break;
47
48 case op::destroy:
49 me->~T();
50 break;
51
52 case op::size:
53 return sizeof(T);
54 }
55 return 0;
56 }
57 }
58 // The thing itself!
59 //
60 // The default value for Size may be wrong in almost all cases. You
61 // can change it to your heart's content. The upside is that you'll
62 // just get a compile error and you can bump it up.
63 //
64 // I *recommend* having a size constant in header files (or perhaps a
65 // using declaration, e.g.
66 // ```
67 // using StaticFoo = static_ptr<Foo, sizeof(Blah)>`
68 // ```
69 // in some header file that can be used multiple places) so that when
70 // you create a new derived class with a larger size, you only have to
71 // change it in one place.
72 //
73 template<typename Base, std::size_t Size = sizeof(Base)>
74 class static_ptr {
75 template<typename U, std::size_t S>
76 friend class static_ptr;
77
78 // Refuse to be set to anything with whose type we are
79 // incompatible. Also never try to eat anything bigger than you are.
80 //
81 template<typename T, std::size_t S>
82 constexpr static int create_ward() noexcept {
83 static_assert(std::is_void_v<Base> ||
84 std::is_base_of_v<Base, std::decay_t<T>>,
85 "Value to store must be a derivative of the base.");
86 static_assert(S <= Size, "Value too large.");
87 static_assert(std::is_void_v<Base> || !std::is_const<Base>{} ||
88 std::is_const_v<T>,
89 "Cannot assign const pointer to non-const pointer.");
90 return 0;
91 }
92 // Here we can store anything that has the same signature, which is
93 // relevant to the multiple-versions for move/copy support that I
94 // mentioned above.
95 //
96 size_t (*operate)(_mem::op, void*, void*);
97
98 // This is mutable so that get and the dereference operators can be
99 // const. Since we're modeling a pointer, we should preserve the
100 // difference in semantics between a pointer-to-const and a const
101 // pointer.
102 //
103 mutable typename std::aligned_storage<Size>::type buf;
104
105 public:
106 using element_type = Base;
107 using pointer = Base*;
108
109 // Empty
110 static_ptr() noexcept : operate(nullptr) {}
111 static_ptr(std::nullptr_t) noexcept : operate(nullptr) {}
112 static_ptr& operator =(std::nullptr_t) noexcept {
113 reset();
114 return *this;
115 }
116 ~static_ptr() noexcept {
117 reset();
118 }
119
120 // Since other pointer-ish types have it
121 void reset() noexcept {
122 if (operate) {
123 operate(_mem::op::destroy, &buf, nullptr);
124 operate = nullptr;
125 }
126 }
127
128 // Set from another static pointer.
129 //
130 // Since the templated versions don't count for overriding the defaults
131 static_ptr(static_ptr&& rhs)
132 noexcept(std::is_nothrow_move_constructible_v<Base>) : operate(rhs.operate) {
133 if (operate) {
134 operate(_mem::op::move, &rhs.buf, &buf);
135 }
136 }
137
138 template<typename U, std::size_t S>
139 static_ptr(static_ptr<U, S>&& rhs)
140 noexcept(std::is_nothrow_move_constructible_v<U>) : operate(rhs.operate) {
141 create_ward<U, S>();
142 if (operate) {
143 operate(_mem::op::move, &rhs.buf, &buf);
144 }
145 }
146
147 static_ptr& operator =(static_ptr&& rhs)
148 noexcept(std::is_nothrow_move_constructible_v<Base>) {
149 reset();
150 if (rhs) {
151 operate = rhs.operate;
152 operate(_mem::op::move, &rhs.buf, &buf);
153 }
154 return *this;
155 }
156
157 template<typename U, std::size_t S>
158 static_ptr& operator =(static_ptr<U, S>&& rhs)
159 noexcept(std::is_nothrow_move_constructible_v<U>) {
160 create_ward<U, S>();
161 reset();
162 if (rhs) {
163 operate = rhs.operate;
164 operate(_mem::op::move, &rhs.buf, &buf);
165 }
166 return *this;
167 }
168
169 // In-place construction!
170 //
171 // This is basically what you want, and I didn't include value
172 // construction because in-place construction renders it
173 // unnecessary. Also it doesn't fit the pointer idiom as well.
174 //
175 template<typename T, typename... Args>
176 static_ptr(std::in_place_type_t<T>, Args&& ...args)
177 noexcept(std::is_nothrow_constructible_v<T, Args...>)
178 : operate(&_mem::op_fun<T>){
179 static_assert((!std::is_nothrow_copy_constructible_v<Base> ||
180 std::is_nothrow_copy_constructible_v<T>) &&
181 (!std::is_nothrow_move_constructible_v<Base> ||
182 std::is_nothrow_move_constructible_v<T>),
183 "If declared type of static_ptr is nothrow "
184 "move/copy constructible, then any "
185 "type assigned to it must be as well. "
186 "You can use reinterpret_pointer_cast "
187 "to get around this limit, but don't "
188 "come crying to me when the C++ "
189 "runtime calls terminate().");
190 create_ward<T, sizeof(T)>();
191 new (&buf) T(std::forward<Args>(args)...);
192 }
193
194 // I occasionally get tempted to make an overload of the assignment
195 // operator that takes a tuple as its right-hand side to provide
196 // arguments.
197 //
198 template<typename T, typename... Args>
199 void emplace(Args&& ...args)
200 noexcept(std::is_nothrow_constructible_v<T, Args...>) {
201 create_ward<T, sizeof(T)>();
202 reset();
203 operate = &_mem::op_fun<T>;
204 new (&buf) T(std::forward<Args>(args)...);
205 }
206
207 // Access!
208 Base* get() const noexcept {
209 return operate ? reinterpret_cast<Base*>(&buf) : nullptr;
210 }
211 template<typename U = Base>
212 std::enable_if_t<!std::is_void_v<U>, Base*> operator->() const noexcept {
213 return get();
214 }
215 template<typename U = Base>
216 std::enable_if_t<!std::is_void_v<U>, Base&> operator *() const noexcept {
217 return *get();
218 }
219 operator bool() const noexcept {
220 return !!operate;
221 }
222
223 // Big wall of friendship
224 //
225 template<typename U, std::size_t Z, typename T, std::size_t S>
226 friend static_ptr<U, Z> static_pointer_cast(const static_ptr<T, S>& p);
227 template<typename U, std::size_t Z, typename T, std::size_t S>
228 friend static_ptr<U, Z> static_pointer_cast(static_ptr<T, S>&& p);
229
230 template<typename U, std::size_t Z, typename T, std::size_t S>
231 friend static_ptr<U, Z> dynamic_pointer_cast(const static_ptr<T, S>& p);
232 template<typename U, std::size_t Z, typename T, std::size_t S>
233 friend static_ptr<U, Z> dynamic_pointer_cast(static_ptr<T, S>&& p);
234
235 template<typename U, std::size_t Z, typename T, std::size_t S>
236 friend static_ptr<U, Z> const_pointer_cast(const static_ptr<T, S>& p);
237 template<typename U, std::size_t Z, typename T, std::size_t S>
238 friend static_ptr<U, Z> const_pointer_cast(static_ptr<T, S>&& p);
239
240 template<typename U, std::size_t Z, typename T, std::size_t S>
241 friend static_ptr<U, Z> reinterpret_pointer_cast(const static_ptr<T, S>& p);
242 template<typename U, std::size_t Z, typename T, std::size_t S>
243 friend static_ptr<U, Z> reinterpret_pointer_cast(static_ptr<T, S>&& p);
244
245 template<typename U, std::size_t Z, typename T, std::size_t S>
246 friend static_ptr<U, Z> resize_pointer_cast(const static_ptr<T, S>& p);
247 template<typename U, std::size_t Z, typename T, std::size_t S>
248 friend static_ptr<U, Z> resize_pointer_cast(static_ptr<T, S>&& p);
249 };
250
251 // These are all modeled after the same ones for shared pointer.
252 //
253 // Also I'm annoyed that the standard library doesn't have
254 // *_pointer_cast overloads for a move-only unique pointer. It's a
255 // nice idiom. Having to release and reconstruct is obnoxious.
256 //
257 template<typename U, std::size_t Z, typename T, std::size_t S>
258 static_ptr<U, Z> static_pointer_cast(static_ptr<T, S>&& p) {
259 static_assert(Z >= S,
260 "Value too large.");
261 static_ptr<U, Z> r;
262 if (static_cast<U*>(p.get())) {
263 p.operate(_mem::op::move, &p.buf, &r.buf);
264 r.operate = p.operate;
265 }
266 return r;
267 }
268
269 // Here the conditional is actually important and ensures we have the
270 // same behavior as dynamic_cast.
271 //
272 template<typename U, std::size_t Z, typename T, std::size_t S>
273 static_ptr<U, Z> dynamic_pointer_cast(static_ptr<T, S>&& p) {
274 static_assert(Z >= S,
275 "Value too large.");
276 static_ptr<U, Z> r;
277 if (dynamic_cast<U*>(p.get())) {
278 p.operate(_mem::op::move, &p.buf, &r.buf);
279 r.operate = p.operate;
280 }
281 return r;
282 }
283
284 template<typename U, std::size_t Z, typename T, std::size_t S>
285 static_ptr<U, Z> const_pointer_cast(static_ptr<T, S>&& p) {
286 static_assert(Z >= S,
287 "Value too large.");
288 static_ptr<U, Z> r;
289 if (const_cast<U*>(p.get())) {
290 p.operate(_mem::op::move, &p.buf, &r.buf);
291 r.operate = p.operate;
292 }
293 return r;
294 }
295
296 // I'm not sure if anyone will ever use this. I can imagine situations
297 // where they might. It works, though!
298 //
299 template<typename U, std::size_t Z, typename T, std::size_t S>
300 static_ptr<U, Z> reinterpret_pointer_cast(static_ptr<T, S>&& p) {
301 static_assert(Z >= S,
302 "Value too large.");
303 static_ptr<U, Z> r;
304 p.operate(_mem::op::move, &p.buf, &r.buf);
305 r.operate = p.operate;
306 return r;
307 }
308
309 // This is the only way to move from a bigger static pointer into a
310 // smaller static pointer. The size of the total data stored in the
311 // pointer is checked at runtime and if the destination size is large
312 // enough, we copy it over.
313 //
314 // I follow cast semantics. Since this is a pointer-like type, it
315 // returns a null value rather than throwing.
316 template<typename U, std::size_t Z, typename T, std::size_t S>
317 static_ptr<U, Z> resize_pointer_cast(static_ptr<T, S>&& p) {
318 static_assert(std::is_same_v<U, T>,
319 "resize_pointer_cast only changes size, not type.");
320 static_ptr<U, Z> r;
321 if (Z >= p.operate(_mem::op::size, &p.buf, nullptr)) {
322 p.operate(_mem::op::move, &p.buf, &r.buf);
323 r.operate = p.operate;
324 }
325 return r;
326 }
327
328 template<typename Base, std::size_t Size>
329 bool operator ==(const static_ptr<Base, Size>& s, std::nullptr_t) {
330 return !s;
331 }
332 template<typename Base, std::size_t Size>
333 bool operator ==(std::nullptr_t, const static_ptr<Base, Size>& s) {
334 return !s;
335 }
336 template<typename Base, std::size_t Size>
337 bool operator ==(static_ptr<Base, Size>& s, std::nullptr_t) {
338 return !s;
339 }
340 template<typename Base, std::size_t Size>
341 bool operator ==(std::nullptr_t, static_ptr<Base, Size>& s) {
342 return !s;
343 }
344
345 // Since `make_unique` and `make_shared` exist, we should follow their
346 // lead.
347 //
348 template<typename Base, typename Derived = Base,
349 std::size_t Size = sizeof(Derived), typename... Args>
350 static_ptr<Base, Size> make_static(Args&& ...args) {
351 return { std::in_place_type<Derived>, std::forward<Args>(args)... };
352 }
353 }