userver: userver/utils/any_storage.hpp Source File
⚠️ This is the documentation for an old userver version. Click here to switch to the latest version.
All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Friends Macros Modules Pages Concepts
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