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 |
|
|
|