GCC Code Coverage Report


include/
File: include/SmallStorage.hpp
Date: 2025-06-29 22:48:06
Lines:
56/56
100.0%
Functions:
92/97
94.8%
Branches:
22/37
59.5%

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