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) 2018 Adam C. Emerson <aemerson@redhat.com>
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.
15 #ifndef INCLUDE_STATIC_ANY
16 #define INCLUDE_STATIC_ANY
20 #include <initializer_list>
23 #include <type_traits>
25 #include <boost/smart_ptr/shared_ptr.hpp>
26 #include <boost/smart_ptr/make_shared.hpp>
32 // Shared Functionality
33 // --------------------
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.
39 // We are using the Curiously Recurring Template Pattern! We require
40 // that all classes inheriting from us provide:
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
49 // - `void* alloc_storage(const std::size_t)`: allocate storage
50 // - `void free_storage() noexcept`: free storage. Must be idempotent.
52 // We provide most of the public interface, as well as the operator function,
53 // cast_helper, and the type() call.
55 // Set `capacity` to this value to indicate that there is no fixed
58 inline constexpr std::size_t dynamic
= ~0;
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.
68 // Moved out here so the type of `func_t` isn't dependent on the
71 enum class op
{ type
, destroy
};
73 inline void op_func(const op o
, void* p
) noexcept
{
74 static const std::type_info
& type
= typeid(T
);
77 *(reinterpret_cast<const std::type_info
**>(p
)) = &type
;
80 reinterpret_cast<T
*>(p
)->~T();
84 using func_t
= void (*)(const op
, void* p
) noexcept
;
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.
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
100 template<typename D
, typename storage_t
>
102 // Make definitions from our superclass visible
103 // --------------------------------------------
105 // And check that they fit the requirements. At least those that are
106 // statically checkable.
108 static constexpr std::size_t capacity
= D::capacity
;
110 void* ptr() const noexcept
{
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();
118 void* alloc_storage(const std::size_t z
) {
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
);
125 void free_storage() noexcept
{
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();
137 // These are just verbose and better typed once than twice. They're
138 // used for SFINAE and declaring noexcept.
141 struct is_in_place_type_helper
: std::false_type
{};
143 struct is_in_place_type_helper
<std::in_place_type_t
<T
>> : std::true_type
{};
146 static constexpr bool is_in_place_type_v
=
147 is_in_place_type_helper
<std::decay_t
<T
>>::value
;
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
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
>>>;
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.
165 static constexpr bool value_noexcept_v
=
166 std::is_nothrow_constructible_v
<std::decay_t
<T
>, T
> && capacity
!= dynamic
;
168 // SFINAE condition for in-place constructors/assigners
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
...>>;
174 // Analogous to the above. Give noexcept to immobile_any::emplace
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
...> &&
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
191 func_t func
= nullptr;
193 // Construct an object within ourselves. As you can see we give the
194 // weak exception safety guarantee.
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.");
203 return *new (reinterpret_cast<Td
*>(alloc_storage(sizeof(Td
))))
204 Td(std::forward
<Args
>(args
)...);
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.
221 base() noexcept
= default;
227 // Since some of our derived classes /can/ be copied or moved.
229 base(const base
& rhs
) noexcept
: func(rhs
.func
) {
230 if constexpr (std::is_copy_assignable_v
<storage_t
>) {
231 storage
= rhs
.storage
;
234 base
& operator =(const base
& rhs
) noexcept
{
237 if constexpr (std::is_copy_assignable_v
<storage_t
>) {
238 storage
= rhs
.storage
;
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
);
249 base
& operator =(base
&& rhs
) noexcept
{
252 if constexpr (std::is_move_assignable_v
<storage_t
>) {
253 storage
= std::move(rhs
.storage
);
261 // Value construct/assign
262 // ----------------------
265 typename
= value_condition_t
<T
>>
266 base(T
&& t
) noexcept(value_noexcept_v
<T
>) {
267 construct
<T
>(std::forward
<T
>(t
));
270 // On exception, *this is set to empty.
273 typename
= value_condition_t
<T
>>
274 base
& operator =(T
&& t
) noexcept(value_noexcept_v
<T
>) {
276 construct
<T
>(std::forward
<T
>(t
));
280 // In-place construct/assign
281 // -------------------------
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.
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.
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.
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.
311 // At the moment, I'm maintaining compatibility with the standard
312 // library except for copy/move semantics.
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
)...);
322 // On exception, *this is set to empty.
326 typename
= in_place_condition_t
<T
>>
327 std::decay_t
<T
>& emplace(Args
&& ...args
) noexcept(in_place_noexcept_v
<
330 return construct
<T
>(std::forward
<Args
>(args
)...);
336 typename
= in_place_condition_t
<T
, std::initializer_list
<U
>,
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
>,
342 construct
<T
>(i
, std::forward
<Args
>(args
)...);
345 // On exception, *this is set to empty.
350 typename
= in_place_condition_t
<T
, std::initializer_list
<U
>,
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
>,
357 return construct
<T
>(i
,std::forward
<Args
>(args
)...);
360 // Empty ourselves, using the subclass to free any storage.
362 void reset() noexcept
{
364 func(op::destroy
, ptr());
370 template<typename U
= storage_t
,
371 typename
= std::enable_if
<std::is_swappable_v
<storage_t
>>>
372 void swap(base
& rhs
) {
374 swap(func
, rhs
.func
);
375 swap(storage
, rhs
.storage
);
378 // All other functions should use this function to test emptiness
379 // rather than examining `func` directly.
381 bool has_value() const noexcept
{
385 // Returns the type of the value stored, if any.
387 const std::type_info
& type() const noexcept
{
389 const std::type_info
* t
;
390 func(op::type
, reinterpret_cast<void*>(&t
));
397 template<typename T
, typename U
, typename V
>
398 friend inline void* cast_helper(const base
<U
, V
>& b
) noexcept
;
401 // Function used by all `any_cast` functions
403 // Returns a void* to the contents if they exist and match the
404 // requested type, otherwise `nullptr`.
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
)))) {
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
427 template<typename T
, typename U
, typename V
>
428 inline T
* any_cast(_any::base
<U
, V
>* a
) noexcept
{
430 return static_cast<T
*>(_any::cast_helper
<std::decay_t
<T
>>(*a
));
435 template<typename T
, typename U
, typename V
>
436 inline const T
* any_cast(const _any::base
<U
, V
>* a
) noexcept
{
438 return static_cast<T
*>(_any::cast_helper
<std::decay_t
<T
>>(*a
));
443 // While we disallow copying the immobile any itself, we can allow
444 // anything with an extracted value that the type supports.
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
);
454 return static_cast<T
>(*p
);
456 throw std::bad_any_cast();
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
);
467 return static_cast<T
>(*p
);
469 throw std::bad_any_cast();
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
);
479 return std::move((*p
));
481 throw std::bad_any_cast();
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
);
489 return static_cast<T
>(*p
);
491 throw std::bad_any_cast();
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.
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.
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.
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
>>;
516 using _any::base
<immobile_any
<S
>, std::aligned_storage_t
<S
>>::storage
;
518 // Superclass requirements!
519 // ------------------------
521 // Simple as anything. We have a buffer of fixed size and return the
522 // pointer to it when asked.
524 static constexpr std::size_t capacity
= S
;
525 void* ptr() const noexcept
{
526 return const_cast<void*>(static_cast<const void*>(&storage
));
528 void* alloc_storage(std::size_t) noexcept
{
531 void free_storage() noexcept
{}
533 static_assert(capacity
!= _any::dynamic
,
534 "That is not a valid size for an immobile_any.");
538 immobile_any() noexcept
= default;
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;
546 using base::operator =;
548 void swap(immobile_any
&) = delete;
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
)...);
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
)...);
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
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…
575 // **Anyway**, this is movable but not copyable, as you should expect
576 // from anything with ‘unique’ in the name.
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
[]>>;
584 // Superclass requirements
585 // -----------------------
587 // Our storage is a single chunk of RAM owned by a
588 // `std::unique_ptr`.
590 static constexpr std::size_t capacity
= _any::dynamic
;
591 void* ptr() const noexcept
{
592 return static_cast<void*>(storage
.get());
596 void* alloc_storage(const std::size_t z
) {
597 storage
.reset(new std::byte
[z
]);
601 void free_storage() noexcept
{
607 unique_any() noexcept
= default;
608 ~unique_any() noexcept
= default;
610 unique_any(const unique_any
&) = delete;
611 unique_any
& operator =(const unique_any
&) = delete;
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.
616 unique_any(unique_any
&& rhs
) noexcept
= default;
617 unique_any
& operator =(unique_any
&& rhs
) = default;
620 using base::operator =;
623 inline void swap(unique_any
& lhs
, unique_any
& rhs
) noexcept
{
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
)...);
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
)...);
640 // Once more with feeling!
642 // This is both copyable *and* movable. In case you need that sort of
643 // thing. It seemed a reasonable completion.
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
[]>>;
651 // Superclass requirements
652 // -----------------------
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`.)
660 static constexpr std::size_t capacity
= _any::dynamic
;
661 void* ptr() const noexcept
{
662 return static_cast<void*>(storage
.get());
665 void* alloc_storage(std::size_t n
) {
666 storage
= boost::make_shared_noinit
<std::byte
[]>(n
);
670 void free_storage() noexcept
{
676 shared_any() noexcept
= default;
677 ~shared_any() noexcept
= default;
679 shared_any(const shared_any
& rhs
) noexcept
= default;
680 shared_any
& operator =(const shared_any
&) noexcept
= default;
682 shared_any(shared_any
&& rhs
) noexcept
= default;
683 shared_any
& operator =(shared_any
&& rhs
) noexcept
= default;
686 using base::operator =;
689 inline void swap(shared_any
& lhs
, shared_any
& rhs
) noexcept
{
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
)...);
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
)...);
704 #endif // INCLUDE_STATIC_ANY