| Line |
Branch |
Exec |
Source |
| 1 |
|
|
#pragma once |
| 2 |
|
|
|
| 3 |
|
|
#include <algorithm> |
| 4 |
|
|
#include <array> |
| 5 |
|
|
#include <concepts> |
| 6 |
|
|
#include <memory> |
| 7 |
|
|
#include <variant> |
| 8 |
|
|
|
| 9 |
|
|
#include "Overloaded.hpp" |
| 10 |
|
|
|
| 11 |
|
|
namespace utils { |
| 12 |
|
|
|
| 13 |
|
|
template <typename EmplacedType, typename Type> |
| 14 |
|
|
concept EmplacableType = |
| 15 |
|
|
std::derived_from<EmplacedType, Type> || std::same_as<EmplacedType, Type>; |
| 16 |
|
|
|
| 17 |
|
|
template <typename Type, int Size> |
| 18 |
|
|
class SmallStorage { |
| 19 |
|
|
public: |
| 20 |
|
18 |
SmallStorage() = default; |
| 21 |
|
|
|
| 22 |
|
|
template <EmplacableType<Type> EmplacedType = Type, typename... Args> |
| 23 |
|
36 |
SmallStorage& emplace(Args&&... args) { |
| 24 |
|
36 |
reset(); |
| 25 |
|
|
if (sizeof(EmplacedType) > StackStorageSize) { |
| 26 |
|
8 |
storage_.template emplace<HeapStorage>( |
| 27 |
3/7
✓ Branch 1 taken 2 times.
✗ Branch 2 not taken.
✓ Branch 5 taken 1 times.
✓ Branch 6 taken 1 times.
✗ Branch 7 not taken.
✗ Branch 8 not taken.
✗ Branch 9 not taken.
|
4 |
new EmplacedType(std::forward<Args>(args)...)); |
| 28 |
|
|
} else { |
| 29 |
|
32 |
storage_.template emplace<StackStorage>(std::in_place_type<EmplacedType>, |
| 30 |
|
|
std::forward<Args>(args)...); |
| 31 |
|
|
} |
| 32 |
|
36 |
return *this; |
| 33 |
|
|
} |
| 34 |
|
|
|
| 35 |
|
42 |
Type* get() const { |
| 36 |
|
42 |
return std::visit( |
| 37 |
|
|
utils::Overloaded{ |
| 38 |
|
1 |
[](std::monostate) -> Type* { return nullptr; }, |
| 39 |
|
20 |
[](const auto& storage) -> Type* { return storage.get(); }, |
| 40 |
|
|
}, |
| 41 |
1/2
✓ Branch 1 taken 21 times.
✗ Branch 2 not taken.
|
84 |
storage_); |
| 42 |
|
|
} |
| 43 |
|
|
|
| 44 |
|
44 |
Type* operator->() const { |
| 45 |
2/2
✓ Branch 1 taken 2 times.
✓ Branch 2 taken 20 times.
|
44 |
if (empty()) { |
| 46 |
1/2
✓ Branch 2 taken 2 times.
✗ Branch 3 not taken.
|
4 |
throw std::runtime_error("Dereferencing empty SmallStorage"); |
| 47 |
|
|
} |
| 48 |
|
40 |
return get(); |
| 49 |
|
|
} |
| 50 |
|
|
|
| 51 |
|
20 |
Type& operator*() const { return *operator->(); } |
| 52 |
|
|
|
| 53 |
|
104 |
bool empty() const { |
| 54 |
|
104 |
return std::visit( |
| 55 |
|
5 |
utils::Overloaded{[](std::monostate) -> bool { return true; }, |
| 56 |
|
47 |
[](const auto& storage) -> bool { |
| 57 |
|
47 |
return !storage.operator bool(); |
| 58 |
|
|
}}, |
| 59 |
1/2
✓ Branch 1 taken 52 times.
✗ Branch 2 not taken.
|
208 |
storage_); |
| 60 |
|
|
} |
| 61 |
|
|
|
| 62 |
|
38 |
void reset() { |
| 63 |
|
91 |
std::visit(utils::Overloaded{[](std::monostate) {}, |
| 64 |
|
4 |
[](auto& storage) { storage.reset(); }}, |
| 65 |
1/2
✓ Branch 1 taken 19 times.
✗ Branch 2 not taken.
|
38 |
storage_); |
| 66 |
|
38 |
} |
| 67 |
|
|
|
| 68 |
|
16 |
bool isStackAllocated() const { |
| 69 |
3/4
✓ Branch 1 taken 6 times.
✓ Branch 2 taken 2 times.
✓ Branch 4 taken 6 times.
✗ Branch 5 not taken.
|
16 |
return std::holds_alternative<StackStorage>(storage_) && !empty(); |
| 70 |
|
|
} |
| 71 |
|
6 |
bool isHeapAllocated() const { |
| 72 |
3/4
✓ Branch 1 taken 1 times.
✓ Branch 2 taken 2 times.
✓ Branch 4 taken 1 times.
✗ Branch 5 not taken.
|
6 |
return std::holds_alternative<HeapStorage>(storage_) && !empty(); |
| 73 |
|
|
} |
| 74 |
|
|
|
| 75 |
|
|
private: |
| 76 |
|
|
using HeapStorage = std::unique_ptr<Type>; |
| 77 |
|
|
// Set stack size to whichever is larger: the size of the heap storage or the |
| 78 |
|
|
// size of the Size parameter minus the size of variant index. |
| 79 |
|
|
static constexpr auto StackStorageSize = |
| 80 |
|
|
std::max(sizeof(HeapStorage), Size - sizeof(std::size_t)); |
| 81 |
|
|
|
| 82 |
|
|
class StackStorage { |
| 83 |
|
|
public: |
| 84 |
|
|
template <EmplacableType<Type> EmplacedType, typename... Args> |
| 85 |
|
32 |
StackStorage(std::in_place_type_t<EmplacedType>, Args&&... args) { |
| 86 |
1/4
✓ Branch 3 taken 2 times.
✗ Branch 4 not taken.
✗ Branch 5 not taken.
✗ Branch 6 not taken.
|
64 |
new (storage_.data()) EmplacedType(std::forward<Args>(args)...); |
| 87 |
|
32 |
} |
| 88 |
|
38 |
~StackStorage() { reset(); } |
| 89 |
|
|
StackStorage(const StackStorage&) = delete; |
| 90 |
|
3 |
StackStorage(StackStorage&& other) noexcept { transferOwnership(other); }; |
| 91 |
|
|
StackStorage& operator=(const StackStorage&) = delete; |
| 92 |
|
2 |
StackStorage& operator=(StackStorage&& other) noexcept { |
| 93 |
2/2
✓ Branch 0 taken 1 times.
✓ Branch 1 taken 1 times.
|
2 |
if (this == &other) { |
| 94 |
|
1 |
return *this; // If other is empty or self-assignment, do nothing |
| 95 |
|
|
} |
| 96 |
|
1 |
transferOwnership(other); |
| 97 |
|
1 |
return *this; |
| 98 |
|
|
}; |
| 99 |
|
140 |
Type* get() const { return reinterpret_cast<Type*>(storage_.data()); } |
| 100 |
|
142 |
operator bool() const { |
| 101 |
|
|
// Check for non-zero bytes. |
| 102 |
|
284 |
return std::ranges::any_of( |
| 103 |
|
1151 |
storage_, [](std::byte b) -> bool { return b != std::byte{}; }); |
| 104 |
|
|
} |
| 105 |
|
54 |
void reset() { |
| 106 |
2/2
✓ Branch 1 taken 16 times.
✓ Branch 2 taken 11 times.
|
54 |
if (operator bool()) { |
| 107 |
|
32 |
get()->~Type(); |
| 108 |
1/2
✓ Branch 1 taken 16 times.
✗ Branch 2 not taken.
|
32 |
storage_.fill(std::byte{}); |
| 109 |
|
|
} |
| 110 |
|
54 |
} |
| 111 |
|
|
|
| 112 |
|
|
private: |
| 113 |
|
4 |
void transferOwnership(StackStorage& other) { |
| 114 |
|
4 |
reset(); |
| 115 |
|
4 |
storage_ = other.storage_; |
| 116 |
1/2
✓ Branch 1 taken 4 times.
✗ Branch 2 not taken.
|
4 |
other.storage_.fill(std::byte{}); |
| 117 |
|
4 |
} |
| 118 |
|
|
// Make this mutable so it behaves like heap storage. |
| 119 |
|
|
mutable std::array<std::byte, StackStorageSize> storage_{}; |
| 120 |
|
|
}; |
| 121 |
|
|
std::variant<std::monostate, StackStorage, HeapStorage> storage_{}; |
| 122 |
|
|
}; |
| 123 |
|
|
} // namespace utils |
| 124 |
|
|
|