1 // -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*-
2 // vim: ts=8 sw=2 smarttab
4 * Ceph - scalable distributed file system
6 * Copyright (C) 2017 Red Hat, Inc.
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.
19 #include <type_traits>
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.
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.
39 static std::size_t op_fun(op oper
, void* p1
, void* p2
)
41 auto me
= static_cast<T
*>(p1
);
45 new (p2
) T(std::move(*me
));
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.
64 // I *recommend* having a size constant in header files (or perhaps a
65 // using declaration, e.g.
67 // using StaticFoo = static_ptr<Foo, sizeof(Blah)>`
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.
73 template<typename Base
, std::size_t Size
= sizeof(Base
)>
75 template<typename U
, std::size_t S
>
76 friend class static_ptr
;
78 // Refuse to be set to anything with whose type we are
79 // incompatible. Also never try to eat anything bigger than you are.
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
>{} ||
89 "Cannot assign const pointer to non-const pointer.");
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
96 size_t (*operate
)(_mem::op
, void*, void*);
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
103 mutable typename
std::aligned_storage
<Size
>::type buf
;
106 using element_type
= Base
;
107 using pointer
= Base
*;
110 static_ptr() noexcept
: operate(nullptr) {}
111 static_ptr(std::nullptr_t
) noexcept
: operate(nullptr) {}
112 static_ptr
& operator =(std::nullptr_t
) noexcept
{
116 ~static_ptr() noexcept
{
120 // Since other pointer-ish types have it
121 void reset() noexcept
{
123 operate(_mem::op::destroy
, &buf
, nullptr);
128 // Set from another static pointer.
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
) {
134 operate(_mem::op::move
, &rhs
.buf
, &buf
);
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
) {
143 operate(_mem::op::move
, &rhs
.buf
, &buf
);
147 static_ptr
& operator =(static_ptr
&& rhs
)
148 noexcept(std::is_nothrow_move_constructible_v
<Base
>) {
151 operate
= rhs
.operate
;
152 operate(_mem::op::move
, &rhs
.buf
, &buf
);
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
>) {
163 operate
= rhs
.operate
;
164 operate(_mem::op::move
, &rhs
.buf
, &buf
);
169 // In-place construction!
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.
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
)...);
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
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
)>();
203 operate
= &_mem::op_fun
<T
>;
204 new (&buf
) T(std::forward
<Args
>(args
)...);
208 Base
* get() const noexcept
{
209 return operate
? reinterpret_cast<Base
*>(&buf
) : nullptr;
211 template<typename U
= Base
>
212 std::enable_if_t
<!std::is_void_v
<U
>, Base
*> operator->() const noexcept
{
215 template<typename U
= Base
>
216 std::enable_if_t
<!std::is_void_v
<U
>, Base
&> operator *() const noexcept
{
219 operator bool() const noexcept
{
223 // Big wall of friendship
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
);
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
);
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
);
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
);
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
);
251 // These are all modeled after the same ones for shared pointer.
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.
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
,
262 if (static_cast<U
*>(p
.get())) {
263 p
.operate(_mem::op::move
, &p
.buf
, &r
.buf
);
264 r
.operate
= p
.operate
;
269 // Here the conditional is actually important and ensures we have the
270 // same behavior as dynamic_cast.
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
,
277 if (dynamic_cast<U
*>(p
.get())) {
278 p
.operate(_mem::op::move
, &p
.buf
, &r
.buf
);
279 r
.operate
= p
.operate
;
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
,
289 if (const_cast<U
*>(p
.get())) {
290 p
.operate(_mem::op::move
, &p
.buf
, &r
.buf
);
291 r
.operate
= p
.operate
;
296 // I'm not sure if anyone will ever use this. I can imagine situations
297 // where they might. It works, though!
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
,
304 p
.operate(_mem::op::move
, &p
.buf
, &r
.buf
);
305 r
.operate
= p
.operate
;
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.
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.");
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
;
328 template<typename Base
, std::size_t Size
>
329 bool operator ==(const static_ptr
<Base
, Size
>& s
, std::nullptr_t
) {
332 template<typename Base
, std::size_t Size
>
333 bool operator ==(std::nullptr_t
, const static_ptr
<Base
, Size
>& s
) {
336 template<typename Base
, std::size_t Size
>
337 bool operator ==(static_ptr
<Base
, Size
>& s
, std::nullptr_t
) {
340 template<typename Base
, std::size_t Size
>
341 bool operator ==(std::nullptr_t
, static_ptr
<Base
, Size
>& s
) {
345 // Since `make_unique` and `make_shared` exist, we should follow their
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
)... };