summaryrefslogtreecommitdiffstats
path: root/src/include/any.h
blob: da59c88f482dcd05365baa936d915ee2703ca7bc (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*-
// vim: ts=8 sw=2 smarttab
/*
 * Ceph - scalable distributed file system
 *
 * Copyright (C) 2018 Adam C. Emerson <aemerson@redhat.com>
 *
 * This is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License version 2.1, as published by the Free Software
 * Foundation.  See file COPYING.
 *
 */

#ifndef INCLUDE_STATIC_ANY
#define INCLUDE_STATIC_ANY

#include <any>
#include <cstddef>
#include <initializer_list>
#include <memory>
#include <typeinfo>
#include <type_traits>

#include <boost/smart_ptr/shared_ptr.hpp>
#include <boost/smart_ptr/make_shared.hpp>

namespace ceph {

namespace _any {

// Shared Functionality
// --------------------
//
// Common implementation details. Most functionality is here. We
// assume that destructors do not throw. Some of them might and
// they'll invoke terminate and that's fine.
//
// We are using the Curiously Recurring Template Pattern! We require
// that all classes inheriting from us provide:
//
//   - `static constexpr size_t capacity`: Maximum capacity. No object
//                                         larger than this may be
//                                         stored. `dynamic` for dynamic.
//   - `void* ptr() const noexcept`: returns a pointer to storage.
//                                   (`alloc_storage` must have been called.
//                                   `free_storage` must not have been called
//                                   since.)
//   - `void* alloc_storage(const std::size_t)`: allocate storage
//   - `void free_storage() noexcept`: free storage. Must be idempotent.
//
// We provide most of the public interface, as well as the operator function,
// cast_helper, and the type() call.

// Set `capacity` to this value to indicate that there is no fixed
// capacity.
//
inline constexpr std::size_t dynamic = ~0;

// Driver Function
// ---------------
//
// The usual type-erasure control function trick. This one is simpler
// than usual since we punt on moving and copying. We could dispense
// with this and just store a deleter and a pointer to a typeinfo, but
// that would be twice the space.
//
// Moved out here so the type of `func_t` isn't dependent on the
// enclosing class.
//
enum class op { type, destroy };
template<typename T>
inline void op_func(const op o, void* p) noexcept {
  static const std::type_info& type = typeid(T);
  switch (o) {
  case op::type:
    *(reinterpret_cast<const std::type_info**>(p)) = &type;
    break;
  case op::destroy:
    reinterpret_cast<T*>(p)->~T();
    break;
  }
}
using func_t = void (*)(const op, void* p) noexcept;

// The base class 
// --------------
//
// The `storage_t` parameter gives the type of the value that manages
// storage and allocation. We use it to create a protected data member
// (named `storage`). This allows us to sidestep the problem in
// initialization order where, where exposed constructors were using
// trying to allocate or free storage *before* the data members of the
// derived class were initialized.
//
// Making storage_t a member type of the derived class won't work, due
// to C++'s rules for nested types being *horrible*. Just downright
// *horrible*.
//
template<typename D, typename storage_t>
class base {
  // Make definitions from our superclass visible
  // --------------------------------------------
  //
  // And check that they fit the requirements. At least those that are
  // statically checkable.
  //
  static constexpr std::size_t capacity = D::capacity;

  void* ptr() const noexcept {
    static_assert(
      noexcept(static_cast<const D*>(this)->ptr()) &&
      std::is_same_v<decltype(static_cast<const D*>(this)->ptr()), void*>,
      "‘void* ptr() const noexcept’ missing from superclass");
    return static_cast<const D*>(this)->ptr();
  }

  void* alloc_storage(const std::size_t z) {
    static_assert(
      std::is_same_v<decltype(static_cast<D*>(this)->alloc_storage(z)), void*>,
      "‘void* alloc_storage(const size_t)’ missing from superclass.");
    return static_cast<D*>(this)->alloc_storage(z);
  }

  void free_storage() noexcept {
    static_assert(
      noexcept(static_cast<D*>(this)->free_storage()) &&
      std::is_void_v<decltype(static_cast<D*>(this)->free_storage())>,
      "‘void free_storage() noexcept’ missing from superclass.");
    static_cast<D*>(this)->free_storage();
  }


  // Pile O' Templates
  // -----------------
  //
  // These are just verbose and better typed once than twice. They're
  // used for SFINAE and declaring noexcept.
  //
  template<class T>
  struct is_in_place_type_helper : std::false_type {};
  template<class T>
  struct is_in_place_type_helper<std::in_place_type_t<T>> : std::true_type {};

  template<class T>
  static constexpr bool is_in_place_type_v =
    is_in_place_type_helper<std::decay_t<T>>::value;

  // SFINAE condition for value initialized
  // constructors/assigners. This is analogous to the standard's
  // requirement that this overload only participate in overload
  // resolution if std::decay_t<T> is not the same type as the
  // any-type, nor a specialization of std::in_place_type_t
  //
  template<typename T>
  using value_condition_t = std::enable_if_t<
    !std::is_same_v<std::decay_t<T>, D> &&
    !is_in_place_type_v<std::decay_t<T>>>;

  // This `noexcept` condition for value construction lets
  // `immobile_any`'s value constructor/assigner be noexcept, so long
  // as the type's copy or move constructor cooperates.
  //
  template<typename T>
  static constexpr bool value_noexcept_v =
    std::is_nothrow_constructible_v<std::decay_t<T>, T> && capacity != dynamic;

  // SFINAE condition for in-place constructors/assigners
  //
  template<typename T, typename... Args>
  using in_place_condition_t = std::enable_if_t<std::is_constructible_v<
						  std::decay_t<T>, Args...>>;

  // Analogous to the above. Give noexcept to immobile_any::emplace
  // when possible.
  //
  template<typename T, typename... Args>
  static constexpr bool in_place_noexcept_v =
    std::is_nothrow_constructible_v<std::decay_t<T>, Args...> &&
    capacity != dynamic;

private:

  // Functionality!
  // --------------

  // The driver function for the currently stored object. Whether this
  // is null is the canonical way to know whether an instance has a
  // value.
  //
  func_t func = nullptr;

  // Construct an object within ourselves. As you can see we give the
  // weak exception safety guarantee.
  //
  template<typename T, typename ...Args>
  std::decay_t<T>& construct(Args&& ...args) {
    using Td = std::decay_t<T>;
    static_assert(capacity == dynamic || sizeof(Td) <= capacity,
		  "Supplied type is too large for this specialization.");
    try {
      func = &op_func<Td>;
      return *new (reinterpret_cast<Td*>(alloc_storage(sizeof(Td))))
	Td(std::forward<Args>(args)...);
    } catch (...) {
      reset();
      throw;
    }
  }

protected:

  // We hold the storage, even if the superclass class manipulates it,
  // so that its default initialization comes soon enough for us to
  // use it in our constructors.
  //
  storage_t storage;

public:

  base() noexcept = default;
  ~base() noexcept {
    reset();
  }

protected:
  // Since some of our derived classes /can/ be copied or moved.
  //
  base(const base& rhs) noexcept : func(rhs.func) {
    if constexpr (std::is_copy_assignable_v<storage_t>) {
      storage = rhs.storage;
    }
  }
  base& operator =(const base& rhs) noexcept {
    reset();
    func = rhs.func;
    if constexpr (std::is_copy_assignable_v<storage_t>) {
      storage = rhs.storage;
    }
    return *this;
  }

  base(base&& rhs) noexcept : func(std::move(rhs.func)) {
    if constexpr (std::is_move_assignable_v<storage_t>) {
      storage = std::move(rhs.storage);
    }
    rhs.func = nullptr;
  }
  base& operator =(base&& rhs) noexcept {
    reset();
    func = rhs.func;
    if constexpr (std::is_move_assignable_v<storage_t>) {
      storage = std::move(rhs.storage);
    }
    rhs.func = nullptr;
    return *this;
  }

public:

  // Value construct/assign
  // ----------------------
  //
  template<typename T,
	   typename = value_condition_t<T>>
  base(T&& t) noexcept(value_noexcept_v<T>) {
    construct<T>(std::forward<T>(t));
  }

  // On exception, *this is set to empty.
  //
  template<typename T,
           typename = value_condition_t<T>>
  base& operator =(T&& t) noexcept(value_noexcept_v<T>) {
    reset();
    construct<T>(std::forward<T>(t));
    return *this;
  }

  // In-place construct/assign
  // -------------------------
  //
  // I really hate the way the C++ standard library treats references
  // as if they were stepchildren in a Charles Dickens novel. I am
  // quite upset that std::optional lacks a specialization for
  // references. There's no legitimate reason for it. The whole
  // 're-seat or refuse' debate is simply a canard. The optional is
  // effectively a container, so of course it can be emptied or
  // reassigned. No, pointers are not an acceptable substitute. A
  // pointer gives an address in memory which may be null and which
  // may represent an object or may a location in which an object is
  // to be created. An optional reference, on the other hand, is a
  // reference to an initialized, live object or /empty/. This is an
  // obvious difference that should be communicable to any programmer
  // reading the code through the type system.
  //
  // `std::any`, even in the case of in-place construction,
  // only stores the decayed type. I suspect this was to get around
  // the question of whether, for a std::any holding a T&,
  // std::any_cast<T> should return a copy or throw
  // std::bad_any_cast.
  //
  // I think the appropriate response in that case would be to make a
  // copy if the type supports it and fail otherwise. Once a concrete
  // type is known the problem solves itself.
  //
  // If one were inclined, one could easily load the driver function
  // with a heavy subset of the type traits (those that depend only on
  // the type in question) and simply /ask/ whether it's a reference.
  //
  // At the moment, I'm maintaining compatibility with the standard
  // library except for copy/move semantics.
  //
  template<typename T,
           typename... Args,
           typename = in_place_condition_t<T, Args...>>
  base(std::in_place_type_t<T>,
       Args&& ...args) noexcept(in_place_noexcept_v<T, Args...>) {
    construct<T>(std::forward<Args>(args)...);
  }

  // On exception, *this is set to empty.
  //
  template<typename T,
           typename... Args,
           typename = in_place_condition_t<T>>
  std::decay_t<T>& emplace(Args&& ...args) noexcept(in_place_noexcept_v<
						    T, Args...>) {
    reset();
    return construct<T>(std::forward<Args>(args)...);
  }

  template<typename T,
           typename U,
           typename... Args,
           typename = in_place_condition_t<T, std::initializer_list<U>,
					   Args...>>
  base(std::in_place_type_t<T>,
       std::initializer_list<U> i,
       Args&& ...args) noexcept(in_place_noexcept_v<T, std::initializer_list<U>,
				Args...>) {
    construct<T>(i, std::forward<Args>(args)...);
  }

  // On exception, *this is set to empty.
  //
  template<typename T,
           typename U,
           typename... Args,
           typename = in_place_condition_t<T, std::initializer_list<U>,
					   Args...>>
  std::decay_t<T>& emplace(std::initializer_list<U> i,
                           Args&& ...args) noexcept(in_place_noexcept_v<T,
						    std::initializer_list<U>,
						    Args...>) {
    reset();
    return construct<T>(i,std::forward<Args>(args)...);
  }

  // Empty ourselves, using the subclass to free any storage.
  //
  void reset() noexcept {
    if (has_value()) {
      func(op::destroy, ptr());
      func = nullptr;
    }
    free_storage();
  }

  template<typename U = storage_t,
	   typename = std::enable_if<std::is_swappable_v<storage_t>>>
  void swap(base& rhs) {
    using std::swap;
    swap(func, rhs.func);
    swap(storage, rhs.storage);
  }

  // All other functions should use this function to test emptiness
  // rather than examining `func` directly.
  //
  bool has_value() const noexcept {
    return !!func;
  }

  // Returns the type of the value stored, if any.
  //
  const std::type_info& type() const noexcept {
    if (has_value()) {
      const std::type_info* t;
      func(op::type, reinterpret_cast<void*>(&t));
      return *t;
    } else {
      return typeid(void);
    }
  }

  template<typename T, typename U, typename V>
  friend inline void* cast_helper(const base<U, V>& b) noexcept;
};

// Function used by all `any_cast` functions
//
// Returns a void* to the contents if they exist and match the
// requested type, otherwise `nullptr`.
//
template<typename T, typename U, typename V>
inline void* cast_helper(const base<U, V>& b) noexcept {
  if (b.func && ((&op_func<T> == b.func) ||
		 (b.type() == typeid(T)))) {
    return b.ptr();
  } else {
    return nullptr;
  }
}
}

// `any_cast`
// ==========
//
// Just the usual gamut of `any_cast` overloads. These get a bit
// repetitive and it would be nice to think of a way to collapse them
// down a bit.
//

// The pointer pair!
//
template<typename T, typename U, typename V>
inline T* any_cast(_any::base<U, V>* a) noexcept {
  if (a) {
    return static_cast<T*>(_any::cast_helper<std::decay_t<T>>(*a));
  }
  return nullptr;
}

template<typename T, typename U, typename V>
inline const T* any_cast(const _any::base<U, V>* a) noexcept {
  if (a) {
    return static_cast<T*>(_any::cast_helper<std::decay_t<T>>(*a));
  }
  return nullptr;
}

// While we disallow copying the immobile any itself, we can allow
// anything with an extracted value that the type supports.
//
template<typename T, typename U, typename V>
inline T any_cast(_any::base<U, V>& a) {
  static_assert(std::is_reference_v<T> ||
                std::is_copy_constructible_v<T>,
                "The supplied type must be either a reference or "
                "copy constructible.");
  auto p = any_cast<std::decay_t<T>>(&a);
  if (p) {
    return static_cast<T>(*p);
  }
  throw std::bad_any_cast();
}

template<typename T, typename U, typename V>
inline T any_cast(const _any::base<U, V>& a) {
  static_assert(std::is_reference_v<T> ||
                std::is_copy_constructible_v<T>,
                "The supplied type must be either a reference or "
                "copy constructible.");
  auto p = any_cast<std::decay_t<T>>(&a);
  if (p) {
    return static_cast<T>(*p);
  }
  throw std::bad_any_cast();
}

template<typename T, typename U, typename V>
inline std::enable_if_t<(std::is_move_constructible_v<T> ||
			 std::is_copy_constructible_v<T>) &&
			!std::is_rvalue_reference_v<T>, T>
any_cast(_any::base<U, V>&& a) {
  auto p = any_cast<std::decay_t<T>>(&a);
  if (p) {
    return std::move((*p));
  }
  throw std::bad_any_cast();
}

template<typename T, typename U, typename V>
inline std::enable_if_t<std::is_rvalue_reference_v<T>, T>
any_cast(_any::base<U, V>&& a) {
  auto p = any_cast<std::decay_t<T>>(&a);
  if (p) {
    return static_cast<T>(*p);
  }
  throw std::bad_any_cast();
}

// `immobile_any`
// ==============
//
// Sometimes, uncopyable objects exist and I want to do things with
// them. The C++ standard library is really quite keen on insisting
// things be copyable before it deigns to work. I find this annoying.
//
// Also, the allocator, while useful, is really not considerate of
// other people's time. Every time we go to visit it, it takes us
// quite an awfully long time to get away again. As such, I've been
// trying to avoid its company whenever it is convenient and seemly.
//
// We accept any type that will fit in the declared capacity. You may
// store types with throwing destructors, but terminate will be
// invoked when they throw.
//
template<std::size_t S>
class immobile_any : public _any::base<immobile_any<S>,
				       std::aligned_storage_t<S>> {
  using base = _any::base<immobile_any<S>, std::aligned_storage_t<S>>;
  friend base;

  using _any::base<immobile_any<S>, std::aligned_storage_t<S>>::storage;

  // Superclass requirements!
  // ------------------------
  //
  // Simple as anything. We have a buffer of fixed size and return the
  // pointer to it when asked.
  //
  static constexpr std::size_t capacity = S;
  void* ptr() const noexcept {
    return const_cast<void*>(static_cast<const void*>(&storage));
  }
  void* alloc_storage(std::size_t) noexcept {
    return ptr();
  }
  void free_storage() noexcept {}

  static_assert(capacity != _any::dynamic,
		"That is not a valid size for an immobile_any.");

public:

  immobile_any() noexcept = default;

  immobile_any(const immobile_any&) = delete;
  immobile_any& operator =(const immobile_any&) = delete;
  immobile_any(immobile_any&&) = delete;
  immobile_any& operator =(immobile_any&&) = delete;

  using base::base;
  using base::operator =;

  void swap(immobile_any&) = delete;
};

template<typename T, std::size_t S, typename... Args>
inline immobile_any<S> make_immobile_any(Args&& ...args) {
  return immobile_any<S>(std::in_place_type<T>, std::forward<Args>(args)...);
}

template<typename T, std::size_t S, typename U, typename... Args>
inline immobile_any<S> make_immobile_any(std::initializer_list<U> i, Args&& ...args) {
  return immobile_any<S>(std::in_place_type<T>, i, std::forward<Args>(args)...);
}

// `unique_any`
// ============
//
// Oh dear. Now we're getting back into allocation. You don't think
// the allocator noticed all those mean things we said about it, do
// you?
//
// Well. Okay, allocator. Sometimes when it's the middle of the night
// and you're writing template code you say things you don't exactly
// mean. If it weren't for you, we wouldn't have any memory to run all
// our programs in at all. Really, I'm just being considerate of
// *your* needs, trying to avoid having to run to you every time we
// instantiate a type, making a few that can be self-sufficient…uh…
//
// **Anyway**, this is movable but not copyable, as you should expect
// from anything with ‘unique’ in the name.
//
class unique_any : public _any::base<unique_any, std::unique_ptr<std::byte[]>> {
  using base = _any::base<unique_any, std::unique_ptr<std::byte[]>>;
  friend base;

  using base::storage;

  // Superclass requirements
  // -----------------------
  //
  // Our storage is a single chunk of RAM owned by a
  // `std::unique_ptr`.
  //
  static constexpr std::size_t capacity = _any::dynamic;
  void* ptr() const noexcept {
    return static_cast<void*>(storage.get());
    return nullptr;
  }

  void* alloc_storage(const std::size_t z) {
    storage.reset(new std::byte[z]);
    return ptr();
  }

  void free_storage() noexcept {
    storage.reset();
  }

public:

  unique_any() noexcept = default;
  ~unique_any() noexcept = default;

  unique_any(const unique_any&) = delete;
  unique_any& operator =(const unique_any&) = delete;

  // We can rely on the behavior of `unique_ptr` and the base class to
  // give us a default move constructor that does the right thing.
  //
  unique_any(unique_any&& rhs) noexcept = default;
  unique_any& operator =(unique_any&& rhs) = default;

  using base::base;
  using base::operator =;
};

inline void swap(unique_any& lhs, unique_any& rhs) noexcept {
  lhs.swap(rhs);
}

template<typename T, typename... Args>
inline unique_any make_unique_any(Args&& ...args) {
  return unique_any(std::in_place_type<T>, std::forward<Args>(args)...);
}

template<typename T, typename U, typename... Args>
inline unique_any make_unique_any(std::initializer_list<U> i, Args&& ...args) {
  return unique_any(std::in_place_type<T>, i, std::forward<Args>(args)...);
}

// `shared_any`
// ============
//
// Once more with feeling!
//
// This is both copyable *and* movable. In case you need that sort of
// thing. It seemed a reasonable completion.
//
class shared_any : public _any::base<shared_any, boost::shared_ptr<std::byte[]>> {
  using base = _any::base<shared_any, boost::shared_ptr<std::byte[]>>;
  friend base;

  using base::storage;

  // Superclass requirements
  // -----------------------
  //
  // Our storage is a single chunk of RAM allocated from the
  // heap. This time it's owned by a `boost::shared_ptr` so we can use
  // `boost::make_shared_noinit`. (This lets us get the optimization
  // that allocates array and control block in one without wasting
  // time on `memset`.)
  //
  static constexpr std::size_t capacity = _any::dynamic;
  void* ptr() const noexcept {
    return static_cast<void*>(storage.get());
  }

  void* alloc_storage(std::size_t n) {
    storage = boost::make_shared_noinit<std::byte[]>(n);
    return ptr();
  }

  void free_storage() noexcept {
    storage.reset();
  }

public:

  shared_any() noexcept = default;
  ~shared_any() noexcept = default;

  shared_any(const shared_any& rhs) noexcept = default;
  shared_any& operator =(const shared_any&) noexcept = default;

  shared_any(shared_any&& rhs) noexcept = default;
  shared_any& operator =(shared_any&& rhs) noexcept = default;

  using base::base;
  using base::operator =;
};

inline void swap(shared_any& lhs, shared_any& rhs) noexcept {
  lhs.swap(rhs);
}

template<typename T, typename... Args>
inline shared_any make_shared_any(Args&& ...args) {
  return shared_any(std::in_place_type<T>, std::forward<Args>(args)...);
}

template<typename T, typename U, typename... Args>
inline shared_any make_shared_any(std::initializer_list<U> i, Args&& ...args) {
  return shared_any(std::in_place_type<T>, i, std::forward<Args>(args)...);
}
}

#endif // INCLUDE_STATIC_ANY