]> git.proxmox.com Git - ceph.git/blob - ceph/src/include/any.h
update ceph source to reef 18.2.1
[ceph.git] / ceph / src / include / any.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) 2018 Adam C. Emerson <aemerson@redhat.com>
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 #ifndef INCLUDE_STATIC_ANY
16 #define INCLUDE_STATIC_ANY
17
18 #include <any>
19 #include <cstddef>
20 #include <initializer_list>
21 #include <memory>
22 #include <typeinfo>
23 #include <type_traits>
24
25 #include <boost/smart_ptr/shared_ptr.hpp>
26 #include <boost/smart_ptr/make_shared.hpp>
27
28 namespace ceph {
29
30 namespace _any {
31
32 // Shared Functionality
33 // --------------------
34 //
35 // Common implementation details. Most functionality is here. We
36 // assume that destructors do not throw. Some of them might and
37 // they'll invoke terminate and that's fine.
38 //
39 // We are using the Curiously Recurring Template Pattern! We require
40 // that all classes inheriting from us provide:
41 //
42 // - `static constexpr size_t capacity`: Maximum capacity. No object
43 // larger than this may be
44 // stored. `dynamic` for dynamic.
45 // - `void* ptr() const noexcept`: returns a pointer to storage.
46 // (`alloc_storage` must have been called.
47 // `free_storage` must not have been called
48 // since.)
49 // - `void* alloc_storage(const std::size_t)`: allocate storage
50 // - `void free_storage() noexcept`: free storage. Must be idempotent.
51 //
52 // We provide most of the public interface, as well as the operator function,
53 // cast_helper, and the type() call.
54
55 // Set `capacity` to this value to indicate that there is no fixed
56 // capacity.
57 //
58 inline constexpr std::size_t dynamic = ~0;
59
60 // Driver Function
61 // ---------------
62 //
63 // The usual type-erasure control function trick. This one is simpler
64 // than usual since we punt on moving and copying. We could dispense
65 // with this and just store a deleter and a pointer to a typeinfo, but
66 // that would be twice the space.
67 //
68 // Moved out here so the type of `func_t` isn't dependent on the
69 // enclosing class.
70 //
71 enum class op { type, destroy };
72 template<typename T>
73 inline void op_func(const op o, void* p) noexcept {
74 static const std::type_info& type = typeid(T);
75 switch (o) {
76 case op::type:
77 *(reinterpret_cast<const std::type_info**>(p)) = &type;
78 break;
79 case op::destroy:
80 reinterpret_cast<T*>(p)->~T();
81 break;
82 }
83 }
84 using func_t = void (*)(const op, void* p) noexcept;
85
86 // The base class
87 // --------------
88 //
89 // The `storage_t` parameter gives the type of the value that manages
90 // storage and allocation. We use it to create a protected data member
91 // (named `storage`). This allows us to sidestep the problem in
92 // initialization order where, where exposed constructors were using
93 // trying to allocate or free storage *before* the data members of the
94 // derived class were initialized.
95 //
96 // Making storage_t a member type of the derived class won't work, due
97 // to C++'s rules for nested types being *horrible*. Just downright
98 // *horrible*.
99 //
100 template<typename D, typename storage_t>
101 class base {
102 // Make definitions from our superclass visible
103 // --------------------------------------------
104 //
105 // And check that they fit the requirements. At least those that are
106 // statically checkable.
107 //
108 static constexpr std::size_t capacity = D::capacity;
109
110 void* ptr() const noexcept {
111 static_assert(
112 noexcept(static_cast<const D*>(this)->ptr()) &&
113 std::is_same_v<decltype(static_cast<const D*>(this)->ptr()), void*>,
114 "‘void* ptr() const noexcept’ missing from superclass");
115 return static_cast<const D*>(this)->ptr();
116 }
117
118 void* alloc_storage(const std::size_t z) {
119 static_assert(
120 std::is_same_v<decltype(static_cast<D*>(this)->alloc_storage(z)), void*>,
121 "‘void* alloc_storage(const size_t)’ missing from superclass.");
122 return static_cast<D*>(this)->alloc_storage(z);
123 }
124
125 void free_storage() noexcept {
126 static_assert(
127 noexcept(static_cast<D*>(this)->free_storage()) &&
128 std::is_void_v<decltype(static_cast<D*>(this)->free_storage())>,
129 "‘void free_storage() noexcept’ missing from superclass.");
130 static_cast<D*>(this)->free_storage();
131 }
132
133
134 // Pile O' Templates
135 // -----------------
136 //
137 // These are just verbose and better typed once than twice. They're
138 // used for SFINAE and declaring noexcept.
139 //
140 template<class T>
141 struct is_in_place_type_helper : std::false_type {};
142 template<class T>
143 struct is_in_place_type_helper<std::in_place_type_t<T>> : std::true_type {};
144
145 template<class T>
146 static constexpr bool is_in_place_type_v =
147 is_in_place_type_helper<std::decay_t<T>>::value;
148
149 // SFINAE condition for value initialized
150 // constructors/assigners. This is analogous to the standard's
151 // requirement that this overload only participate in overload
152 // resolution if std::decay_t<T> is not the same type as the
153 // any-type, nor a specialization of std::in_place_type_t
154 //
155 template<typename T>
156 using value_condition_t = std::enable_if_t<
157 !std::is_same_v<std::decay_t<T>, D> &&
158 !is_in_place_type_v<std::decay_t<T>>>;
159
160 // This `noexcept` condition for value construction lets
161 // `immobile_any`'s value constructor/assigner be noexcept, so long
162 // as the type's copy or move constructor cooperates.
163 //
164 template<typename T>
165 static constexpr bool value_noexcept_v =
166 std::is_nothrow_constructible_v<std::decay_t<T>, T> && capacity != dynamic;
167
168 // SFINAE condition for in-place constructors/assigners
169 //
170 template<typename T, typename... Args>
171 using in_place_condition_t = std::enable_if_t<std::is_constructible_v<
172 std::decay_t<T>, Args...>>;
173
174 // Analogous to the above. Give noexcept to immobile_any::emplace
175 // when possible.
176 //
177 template<typename T, typename... Args>
178 static constexpr bool in_place_noexcept_v =
179 std::is_nothrow_constructible_v<std::decay_t<T>, Args...> &&
180 capacity != dynamic;
181
182 private:
183
184 // Functionality!
185 // --------------
186
187 // The driver function for the currently stored object. Whether this
188 // is null is the canonical way to know whether an instance has a
189 // value.
190 //
191 func_t func = nullptr;
192
193 // Construct an object within ourselves. As you can see we give the
194 // weak exception safety guarantee.
195 //
196 template<typename T, typename ...Args>
197 std::decay_t<T>& construct(Args&& ...args) {
198 using Td = std::decay_t<T>;
199 static_assert(capacity == dynamic || sizeof(Td) <= capacity,
200 "Supplied type is too large for this specialization.");
201 try {
202 func = &op_func<Td>;
203 return *new (reinterpret_cast<Td*>(alloc_storage(sizeof(Td))))
204 Td(std::forward<Args>(args)...);
205 } catch (...) {
206 reset();
207 throw;
208 }
209 }
210
211 protected:
212
213 // We hold the storage, even if the superclass class manipulates it,
214 // so that its default initialization comes soon enough for us to
215 // use it in our constructors.
216 //
217 storage_t storage;
218
219 public:
220
221 base() noexcept = default;
222 ~base() noexcept {
223 reset();
224 }
225
226 protected:
227 // Since some of our derived classes /can/ be copied or moved.
228 //
229 base(const base& rhs) noexcept : func(rhs.func) {
230 if constexpr (std::is_copy_assignable_v<storage_t>) {
231 storage = rhs.storage;
232 }
233 }
234 base& operator =(const base& rhs) noexcept {
235 reset();
236 func = rhs.func;
237 if constexpr (std::is_copy_assignable_v<storage_t>) {
238 storage = rhs.storage;
239 }
240 return *this;
241 }
242
243 base(base&& rhs) noexcept : func(std::move(rhs.func)) {
244 if constexpr (std::is_move_assignable_v<storage_t>) {
245 storage = std::move(rhs.storage);
246 }
247 rhs.func = nullptr;
248 }
249 base& operator =(base&& rhs) noexcept {
250 reset();
251 func = rhs.func;
252 if constexpr (std::is_move_assignable_v<storage_t>) {
253 storage = std::move(rhs.storage);
254 }
255 rhs.func = nullptr;
256 return *this;
257 }
258
259 public:
260
261 // Value construct/assign
262 // ----------------------
263 //
264 template<typename T,
265 typename = value_condition_t<T>>
266 base(T&& t) noexcept(value_noexcept_v<T>) {
267 construct<T>(std::forward<T>(t));
268 }
269
270 // On exception, *this is set to empty.
271 //
272 template<typename T,
273 typename = value_condition_t<T>>
274 base& operator =(T&& t) noexcept(value_noexcept_v<T>) {
275 reset();
276 construct<T>(std::forward<T>(t));
277 return *this;
278 }
279
280 // In-place construct/assign
281 // -------------------------
282 //
283 // I really hate the way the C++ standard library treats references
284 // as if they were stepchildren in a Charles Dickens novel. I am
285 // quite upset that std::optional lacks a specialization for
286 // references. There's no legitimate reason for it. The whole
287 // 're-seat or refuse' debate is simply a canard. The optional is
288 // effectively a container, so of course it can be emptied or
289 // reassigned. No, pointers are not an acceptable substitute. A
290 // pointer gives an address in memory which may be null and which
291 // may represent an object or may a location in which an object is
292 // to be created. An optional reference, on the other hand, is a
293 // reference to an initialized, live object or /empty/. This is an
294 // obvious difference that should be communicable to any programmer
295 // reading the code through the type system.
296 //
297 // `std::any`, even in the case of in-place construction,
298 // only stores the decayed type. I suspect this was to get around
299 // the question of whether, for a std::any holding a T&,
300 // std::any_cast<T> should return a copy or throw
301 // std::bad_any_cast.
302 //
303 // I think the appropriate response in that case would be to make a
304 // copy if the type supports it and fail otherwise. Once a concrete
305 // type is known the problem solves itself.
306 //
307 // If one were inclined, one could easily load the driver function
308 // with a heavy subset of the type traits (those that depend only on
309 // the type in question) and simply /ask/ whether it's a reference.
310 //
311 // At the moment, I'm maintaining compatibility with the standard
312 // library except for copy/move semantics.
313 //
314 template<typename T,
315 typename... Args,
316 typename = in_place_condition_t<T, Args...>>
317 base(std::in_place_type_t<T>,
318 Args&& ...args) noexcept(in_place_noexcept_v<T, Args...>) {
319 construct<T>(std::forward<Args>(args)...);
320 }
321
322 // On exception, *this is set to empty.
323 //
324 template<typename T,
325 typename... Args,
326 typename = in_place_condition_t<T>>
327 std::decay_t<T>& emplace(Args&& ...args) noexcept(in_place_noexcept_v<
328 T, Args...>) {
329 reset();
330 return construct<T>(std::forward<Args>(args)...);
331 }
332
333 template<typename T,
334 typename U,
335 typename... Args,
336 typename = in_place_condition_t<T, std::initializer_list<U>,
337 Args...>>
338 base(std::in_place_type_t<T>,
339 std::initializer_list<U> i,
340 Args&& ...args) noexcept(in_place_noexcept_v<T, std::initializer_list<U>,
341 Args...>) {
342 construct<T>(i, std::forward<Args>(args)...);
343 }
344
345 // On exception, *this is set to empty.
346 //
347 template<typename T,
348 typename U,
349 typename... Args,
350 typename = in_place_condition_t<T, std::initializer_list<U>,
351 Args...>>
352 std::decay_t<T>& emplace(std::initializer_list<U> i,
353 Args&& ...args) noexcept(in_place_noexcept_v<T,
354 std::initializer_list<U>,
355 Args...>) {
356 reset();
357 return construct<T>(i,std::forward<Args>(args)...);
358 }
359
360 // Empty ourselves, using the subclass to free any storage.
361 //
362 void reset() noexcept {
363 if (has_value()) {
364 func(op::destroy, ptr());
365 func = nullptr;
366 }
367 free_storage();
368 }
369
370 template<typename U = storage_t,
371 typename = std::enable_if<std::is_swappable_v<storage_t>>>
372 void swap(base& rhs) {
373 using std::swap;
374 swap(func, rhs.func);
375 swap(storage, rhs.storage);
376 }
377
378 // All other functions should use this function to test emptiness
379 // rather than examining `func` directly.
380 //
381 bool has_value() const noexcept {
382 return !!func;
383 }
384
385 // Returns the type of the value stored, if any.
386 //
387 const std::type_info& type() const noexcept {
388 if (has_value()) {
389 const std::type_info* t;
390 func(op::type, reinterpret_cast<void*>(&t));
391 return *t;
392 } else {
393 return typeid(void);
394 }
395 }
396
397 template<typename T, typename U, typename V>
398 friend inline void* cast_helper(const base<U, V>& b) noexcept;
399 };
400
401 // Function used by all `any_cast` functions
402 //
403 // Returns a void* to the contents if they exist and match the
404 // requested type, otherwise `nullptr`.
405 //
406 template<typename T, typename U, typename V>
407 inline void* cast_helper(const base<U, V>& b) noexcept {
408 if (b.func && ((&op_func<T> == b.func) ||
409 (b.type() == typeid(T)))) {
410 return b.ptr();
411 } else {
412 return nullptr;
413 }
414 }
415 }
416
417 // `any_cast`
418 // ==========
419 //
420 // Just the usual gamut of `any_cast` overloads. These get a bit
421 // repetitive and it would be nice to think of a way to collapse them
422 // down a bit.
423 //
424
425 // The pointer pair!
426 //
427 template<typename T, typename U, typename V>
428 inline T* any_cast(_any::base<U, V>* a) noexcept {
429 if (a) {
430 return static_cast<T*>(_any::cast_helper<std::decay_t<T>>(*a));
431 }
432 return nullptr;
433 }
434
435 template<typename T, typename U, typename V>
436 inline const T* any_cast(const _any::base<U, V>* a) noexcept {
437 if (a) {
438 return static_cast<T*>(_any::cast_helper<std::decay_t<T>>(*a));
439 }
440 return nullptr;
441 }
442
443 // While we disallow copying the immobile any itself, we can allow
444 // anything with an extracted value that the type supports.
445 //
446 template<typename T, typename U, typename V>
447 inline T any_cast(_any::base<U, V>& a) {
448 static_assert(std::is_reference_v<T> ||
449 std::is_copy_constructible_v<T>,
450 "The supplied type must be either a reference or "
451 "copy constructible.");
452 auto p = any_cast<std::decay_t<T>>(&a);
453 if (p) {
454 return static_cast<T>(*p);
455 }
456 throw std::bad_any_cast();
457 }
458
459 template<typename T, typename U, typename V>
460 inline T any_cast(const _any::base<U, V>& a) {
461 static_assert(std::is_reference_v<T> ||
462 std::is_copy_constructible_v<T>,
463 "The supplied type must be either a reference or "
464 "copy constructible.");
465 auto p = any_cast<std::decay_t<T>>(&a);
466 if (p) {
467 return static_cast<T>(*p);
468 }
469 throw std::bad_any_cast();
470 }
471
472 template<typename T, typename U, typename V>
473 inline std::enable_if_t<(std::is_move_constructible_v<T> ||
474 std::is_copy_constructible_v<T>) &&
475 !std::is_rvalue_reference_v<T>, T>
476 any_cast(_any::base<U, V>&& a) {
477 auto p = any_cast<std::decay_t<T>>(&a);
478 if (p) {
479 return std::move((*p));
480 }
481 throw std::bad_any_cast();
482 }
483
484 template<typename T, typename U, typename V>
485 inline std::enable_if_t<std::is_rvalue_reference_v<T>, T>
486 any_cast(_any::base<U, V>&& a) {
487 auto p = any_cast<std::decay_t<T>>(&a);
488 if (p) {
489 return static_cast<T>(*p);
490 }
491 throw std::bad_any_cast();
492 }
493
494 // `immobile_any`
495 // ==============
496 //
497 // Sometimes, uncopyable objects exist and I want to do things with
498 // them. The C++ standard library is really quite keen on insisting
499 // things be copyable before it deigns to work. I find this annoying.
500 //
501 // Also, the allocator, while useful, is really not considerate of
502 // other people's time. Every time we go to visit it, it takes us
503 // quite an awfully long time to get away again. As such, I've been
504 // trying to avoid its company whenever it is convenient and seemly.
505 //
506 // We accept any type that will fit in the declared capacity. You may
507 // store types with throwing destructors, but terminate will be
508 // invoked when they throw.
509 //
510 template<std::size_t S>
511 class immobile_any : public _any::base<immobile_any<S>,
512 std::aligned_storage_t<S>> {
513 using base = _any::base<immobile_any<S>, std::aligned_storage_t<S>>;
514 friend base;
515
516 using _any::base<immobile_any<S>, std::aligned_storage_t<S>>::storage;
517
518 // Superclass requirements!
519 // ------------------------
520 //
521 // Simple as anything. We have a buffer of fixed size and return the
522 // pointer to it when asked.
523 //
524 static constexpr std::size_t capacity = S;
525 void* ptr() const noexcept {
526 return const_cast<void*>(static_cast<const void*>(&storage));
527 }
528 void* alloc_storage(std::size_t) noexcept {
529 return ptr();
530 }
531 void free_storage() noexcept {}
532
533 static_assert(capacity != _any::dynamic,
534 "That is not a valid size for an immobile_any.");
535
536 public:
537
538 immobile_any() noexcept = default;
539
540 immobile_any(const immobile_any&) = delete;
541 immobile_any& operator =(const immobile_any&) = delete;
542 immobile_any(immobile_any&&) = delete;
543 immobile_any& operator =(immobile_any&&) = delete;
544
545 using base::base;
546 using base::operator =;
547
548 void swap(immobile_any&) = delete;
549 };
550
551 template<typename T, std::size_t S, typename... Args>
552 inline immobile_any<S> make_immobile_any(Args&& ...args) {
553 return immobile_any<S>(std::in_place_type<T>, std::forward<Args>(args)...);
554 }
555
556 template<typename T, std::size_t S, typename U, typename... Args>
557 inline immobile_any<S> make_immobile_any(std::initializer_list<U> i, Args&& ...args) {
558 return immobile_any<S>(std::in_place_type<T>, i, std::forward<Args>(args)...);
559 }
560
561 // `unique_any`
562 // ============
563 //
564 // Oh dear. Now we're getting back into allocation. You don't think
565 // the allocator noticed all those mean things we said about it, do
566 // you?
567 //
568 // Well. Okay, allocator. Sometimes when it's the middle of the night
569 // and you're writing template code you say things you don't exactly
570 // mean. If it weren't for you, we wouldn't have any memory to run all
571 // our programs in at all. Really, I'm just being considerate of
572 // *your* needs, trying to avoid having to run to you every time we
573 // instantiate a type, making a few that can be self-sufficient…uh…
574 //
575 // **Anyway**, this is movable but not copyable, as you should expect
576 // from anything with ‘unique’ in the name.
577 //
578 class unique_any : public _any::base<unique_any, std::unique_ptr<std::byte[]>> {
579 using base = _any::base<unique_any, std::unique_ptr<std::byte[]>>;
580 friend base;
581
582 using base::storage;
583
584 // Superclass requirements
585 // -----------------------
586 //
587 // Our storage is a single chunk of RAM owned by a
588 // `std::unique_ptr`.
589 //
590 static constexpr std::size_t capacity = _any::dynamic;
591 void* ptr() const noexcept {
592 return static_cast<void*>(storage.get());
593 return nullptr;
594 }
595
596 void* alloc_storage(const std::size_t z) {
597 storage.reset(new std::byte[z]);
598 return ptr();
599 }
600
601 void free_storage() noexcept {
602 storage.reset();
603 }
604
605 public:
606
607 unique_any() noexcept = default;
608 ~unique_any() noexcept = default;
609
610 unique_any(const unique_any&) = delete;
611 unique_any& operator =(const unique_any&) = delete;
612
613 // We can rely on the behavior of `unique_ptr` and the base class to
614 // give us a default move constructor that does the right thing.
615 //
616 unique_any(unique_any&& rhs) noexcept = default;
617 unique_any& operator =(unique_any&& rhs) = default;
618
619 using base::base;
620 using base::operator =;
621 };
622
623 inline void swap(unique_any& lhs, unique_any& rhs) noexcept {
624 lhs.swap(rhs);
625 }
626
627 template<typename T, typename... Args>
628 inline unique_any make_unique_any(Args&& ...args) {
629 return unique_any(std::in_place_type<T>, std::forward<Args>(args)...);
630 }
631
632 template<typename T, typename U, typename... Args>
633 inline unique_any make_unique_any(std::initializer_list<U> i, Args&& ...args) {
634 return unique_any(std::in_place_type<T>, i, std::forward<Args>(args)...);
635 }
636
637 // `shared_any`
638 // ============
639 //
640 // Once more with feeling!
641 //
642 // This is both copyable *and* movable. In case you need that sort of
643 // thing. It seemed a reasonable completion.
644 //
645 class shared_any : public _any::base<shared_any, boost::shared_ptr<std::byte[]>> {
646 using base = _any::base<shared_any, boost::shared_ptr<std::byte[]>>;
647 friend base;
648
649 using base::storage;
650
651 // Superclass requirements
652 // -----------------------
653 //
654 // Our storage is a single chunk of RAM allocated from the
655 // heap. This time it's owned by a `boost::shared_ptr` so we can use
656 // `boost::make_shared_noinit`. (This lets us get the optimization
657 // that allocates array and control block in one without wasting
658 // time on `memset`.)
659 //
660 static constexpr std::size_t capacity = _any::dynamic;
661 void* ptr() const noexcept {
662 return static_cast<void*>(storage.get());
663 }
664
665 void* alloc_storage(std::size_t n) {
666 storage = boost::make_shared_noinit<std::byte[]>(n);
667 return ptr();
668 }
669
670 void free_storage() noexcept {
671 storage.reset();
672 }
673
674 public:
675
676 shared_any() noexcept = default;
677 ~shared_any() noexcept = default;
678
679 shared_any(const shared_any& rhs) noexcept = default;
680 shared_any& operator =(const shared_any&) noexcept = default;
681
682 shared_any(shared_any&& rhs) noexcept = default;
683 shared_any& operator =(shared_any&& rhs) noexcept = default;
684
685 using base::base;
686 using base::operator =;
687 };
688
689 inline void swap(shared_any& lhs, shared_any& rhs) noexcept {
690 lhs.swap(rhs);
691 }
692
693 template<typename T, typename... Args>
694 inline shared_any make_shared_any(Args&& ...args) {
695 return shared_any(std::in_place_type<T>, std::forward<Args>(args)...);
696 }
697
698 template<typename T, typename U, typename... Args>
699 inline shared_any make_shared_any(std::initializer_list<U> i, Args&& ...args) {
700 return shared_any(std::in_place_type<T>, i, std::forward<Args>(args)...);
701 }
702 }
703
704 #endif // INCLUDE_STATIC_ANY