userver: userver/utils/any_storage.hpp Source File
Loading...
Searching...
No Matches
any_storage.hpp
Go to the documentation of this file.
1#pragma once
2
3/// @file userver/utils/any_storage.hpp
4/// @brief @copybrief utils::AnyStorage
5
6#include <cstddef>
7#include <memory>
8#include <new>
9#include <stdexcept>
10#include <type_traits>
11#include <vector>
12
13USERVER_NAMESPACE_BEGIN
14
15namespace utils {
16
17namespace any_storage::impl {
18
19using Offset = std::size_t;
20
21template <typename StorageTag>
22inline Offset data_offset{0};
23
24template <typename StorageTag>
25inline std::size_t count{0};
26
27template <typename StorageTag>
28Offset RegisterData(std::size_t size, std::size_t alignment) noexcept {
29 data_offset<StorageTag> +=
30 (alignment - (data_offset<StorageTag> % alignment)) % alignment;
31 Offset result = data_offset<StorageTag>;
32 data_offset<StorageTag> += size;
33
34 count<StorageTag> ++;
35 return result;
36}
37
38void AssertStaticRegistrationAllowed();
39
40template <typename T>
41void Delete(std::byte* data) noexcept {
42 reinterpret_cast<T*>(data)->~T();
43}
44
45} // namespace any_storage::impl
46
47template <typename StorageTag>
48class AnyStorage;
49
50template <typename StorageTag, typename Data>
51class AnyStorageDataTag final {
52 public:
53 AnyStorageDataTag() noexcept
54 : number_(any_storage::impl::count<StorageTag>),
55 offset_(any_storage::impl::RegisterData<StorageTag>(sizeof(Data),
56 alignof(Data))) {
57 static_assert(!std::is_reference_v<Data>);
58 static_assert(!std::is_const_v<Data>);
59 static_assert(__STDCPP_DEFAULT_NEW_ALIGNMENT__ >= alignof(Data),
60 "Overaligned data members are not supported by AnyStorage");
61
62 any_storage::impl::AssertStaticRegistrationAllowed();
63 }
64
65 private:
66 const std::size_t number_;
67 const any_storage::impl::Offset offset_;
68
69 friend class AnyStorage<StorageTag>;
70};
71
72/// @ingroup userver_universal userver_containers
73///
74/// @brief map-like heterogeneous data storage
75///
76/// ## Usage example
77/// @snippet utils/any_storage_test.cpp AnyStorage
78template <typename StorageTag>
79class AnyStorage final {
80 public:
81 AnyStorage();
82
83 ~AnyStorage();
84
85 /// @returns Stored data.
86 template <typename Data>
87 const Data& Get(const AnyStorageDataTag<StorageTag, Data>& tag) const;
88
89 /// @returns Stored data.
90 /// @throws std::runtime_error if no data was stored
91 template <typename Data>
92 Data& Get(const AnyStorageDataTag<StorageTag, Data>& tag);
93
94 /// @brief Stores the data.
95 template <typename Data>
96 Data& Set(AnyStorageDataTag<StorageTag, Data> tag, Data data);
97
98 /// @brief Emplaces the data. The data is rewritten if
99 /// already stored.
100 template <typename Data, typename... Args>
101 Data& Emplace(const AnyStorageDataTag<StorageTag, Data>& tag, Args&&... args);
102
103 /// @returns Pointer to stored data or nullptr if
104 /// no data is found.
105 template <typename Data>
106 Data* GetOptional(const AnyStorageDataTag<StorageTag, Data>& tag) noexcept;
107
108 /// @returns Pointer to stored data or nullptr if
109 /// no data found.
110 template <typename Data>
111 const Data* GetOptional(const AnyStorageDataTag<StorageTag, Data>& tag) const
112 noexcept;
113
114 /// @brief Erase data.
115 template <typename Data>
116 void Erase(const AnyStorageDataTag<StorageTag, Data>& tag);
117
118 private:
119 std::unique_ptr<std::byte[]> raw_data_;
120
121 struct AllocRecord {
122 void (*deleter)(std::byte*) noexcept;
123 std::size_t offset;
124 };
125
126 AllocRecord* GetRecords() noexcept;
127};
128
129template <typename StorageTag>
130AnyStorage<StorageTag>::AnyStorage()
131 : raw_data_(new std::byte[any_storage::impl::data_offset<StorageTag> +
132 sizeof(AllocRecord) *
133 any_storage::impl::count<StorageTag>]) {
134 static_assert(std::is_trivial_v<AllocRecord>);
135
136 auto records = GetRecords();
137 for (std::size_t i = 0; i < any_storage::impl::count<StorageTag>; i++) {
138 auto& record = records[i];
139 record.deleter = nullptr;
140 }
141}
142
143template <typename StorageTag>
144AnyStorage<StorageTag>::~AnyStorage() {
145 auto records = GetRecords();
146 for (std::size_t i = 0; i < any_storage::impl::count<StorageTag>; i++) {
147 auto& record = records[i];
148 if (record.deleter) record.deleter(&raw_data_[record.offset]);
149 }
150}
151
152template <typename StorageTag>
153template <typename Data>
154Data& AnyStorage<StorageTag>::Set(const AnyStorageDataTag<StorageTag, Data> tag,
155 Data data) {
156 auto number = tag.number_;
157 if (!GetRecords()[number].deleter) return Emplace(tag, std::move(data));
158
159 auto offset = tag.offset_;
160 return *reinterpret_cast<Data*>(&raw_data_[offset]) = std::move(data);
161}
162
163template <typename StorageTag>
164template <typename Data, typename... Args>
165Data& AnyStorage<StorageTag>::Emplace(
166 const AnyStorageDataTag<StorageTag, Data>& tag, Args&&... args) {
167 auto number = tag.number_;
168 auto& record = GetRecords()[number];
169 if (record.deleter) record.deleter(&raw_data_[tag.offset_]);
170
171 auto offset = tag.offset_;
172 auto ptr = new (&raw_data_[offset]) Data(std::forward<Args>(args)...);
173 record = {&any_storage::impl::Delete<Data>, offset};
174 return *ptr;
175}
176
177template <typename StorageTag>
178template <typename Data>
179Data& AnyStorage<StorageTag>::Get(
180 const AnyStorageDataTag<StorageTag, Data>& tag) {
181 auto ptr = GetOptional(tag);
182 if (ptr) return *ptr;
183 throw std::runtime_error("No data");
184}
185
186template <typename StorageTag>
187template <typename Data>
188const Data& AnyStorage<StorageTag>::Get(
189 const AnyStorageDataTag<StorageTag, Data>& tag) const {
190 // NOLINTNEXTLINE(cppcoreguidelines-pro-type-const-cast)
191 return const_cast<AnyStorage<StorageTag>*>(this)->Get<Data>(tag);
192}
193
194template <typename StorageTag>
195template <typename Data>
196Data* AnyStorage<StorageTag>::GetOptional(
197 const AnyStorageDataTag<StorageTag, Data>& tag) noexcept {
198 auto number = tag.number_;
199 auto offset = tag.offset_;
200 if (!GetRecords()[number].deleter) return nullptr;
201 return reinterpret_cast<Data*>(&raw_data_[offset]);
202}
203
204template <typename StorageTag>
205template <typename Data>
206const Data* AnyStorage<StorageTag>::GetOptional(
207 const AnyStorageDataTag<StorageTag, Data>& tag) const noexcept {
208 // NOLINTNEXTLINE(cppcoreguidelines-pro-type-const-cast)
209 return const_cast<AnyStorage*>(this)->GetOptional<Data>(tag);
210}
211
212template <typename StorageTag>
213typename AnyStorage<StorageTag>::AllocRecord*
214AnyStorage<StorageTag>::GetRecords() noexcept {
215 return reinterpret_cast<AllocRecord*>(
216 &raw_data_[any_storage::impl::data_offset<StorageTag>]);
217}
218
219} // namespace utils
220
221USERVER_NAMESPACE_END