userver: userver/dump/common_containers.hpp Source File
Loading...
Searching...
No Matches
common_containers.hpp
Go to the documentation of this file.
1#pragma once
2
3/// @file userver/dump/common_containers.hpp
4/// @brief Dump support for C++ Standard Library and Boost containers,
5/// `std::optional`, utils::StrongTypedef, `std::{unique,shared}_ptr`
6///
7/// @note There are no traits in `CachingComponentBase`. If `T`
8/// is writable/readable, we have to generate the code for dumps
9/// regardless of `dump: enabled`. So it's important that all Read-Write
10/// operations for containers are SFINAE-correct.
11///
12/// @ingroup userver_dump_read_write
13
14#include <cstddef>
15#include <memory>
16#include <optional>
17#include <type_traits>
18#include <typeinfo>
19#include <utility>
20#include <variant>
21
22#include <userver/utils/constexpr_indices.hpp>
23#include <userver/utils/lazy_prvalue.hpp>
24#include <userver/utils/meta.hpp>
25#include <userver/utils/strong_typedef.hpp>
26
27#include <userver/dump/common.hpp>
28#include <userver/dump/meta.hpp>
29#include <userver/dump/meta_containers.hpp>
30#include <userver/dump/operations.hpp>
31
32/// @cond
33namespace boost {
34
35namespace bimaps {
36
37template <typename L, typename R, typename AP1, typename AP2, typename AP3>
38class bimap;
39
40} // namespace bimaps
41
42namespace multi_index {
43
44template <typename Value, typename IndexSpecifierList, typename Allocator>
45class multi_index_container;
46
47} // namespace multi_index
48
49using bimaps::bimap;
50using multi_index::multi_index_container;
51
52} // namespace boost
53/// @endcond
54
55USERVER_NAMESPACE_BEGIN
56
57namespace dump {
58
59namespace impl {
60
61template <typename L, typename R, typename... Args>
62using BoostBimap = boost::bimap<L, R, Args...>;
63
64template <typename L, typename R, typename... Args>
65using BoostBimapLeftKey = typename BoostBimap<L, R, Args...>::left_key_type;
66
67template <typename L, typename R, typename... Args>
68using BoostBimapRightKey = typename BoostBimap<L, R, Args...>::right_key_type;
69
70template <typename T>
71auto ReadLazyPrvalue(Reader& reader) {
72 return utils::LazyPrvalue([&reader] { return reader.Read<T>(); });
73}
74
75[[noreturn]] void ThrowInvalidVariantIndex(const std::type_info& type, std::size_t index);
76
77template <typename VariantType>
78VariantType ReadVariant(Reader& reader, std::size_t index) {
79 static constexpr auto kVariantSize = std::variant_size_v<VariantType>;
80 std::optional<VariantType> result;
81
82 utils::WithConstexprIndex<kVariantSize>(index, [&](auto index_constant) {
83 static constexpr auto kIndex = decltype(index_constant)::value;
84 using Alternative = std::variant_alternative_t<kIndex, VariantType>;
85 // Not using ReadLazyPrvalue because of stdlib issues on some compilers.
86 result.emplace(std::in_place_index<kIndex>, reader.Read<Alternative>());
87 });
88
89 return std::move(*result);
90}
91
92} // namespace impl
93
94/// @brief Container serialization support
95template <typename T>
96requires(IsContainer<T> && kIsWritable<meta::RangeValueType<T>>)
97void Write(Writer& writer, const T& value) {
98 writer.Write(std::size(value));
99 for (const auto& item : value) {
100 // explicit cast for vector<bool> shenanigans
101 writer.Write(static_cast<const meta::RangeValueType<T>&>(item));
102 }
103}
104
105/// @brief Container deserialization support
106template <typename T>
107requires(IsContainer<T> && kIsReadable<meta::RangeValueType<T>>)
108T Read(Reader& reader, To<T>) {
109 const auto size = reader.Read<std::size_t>();
110 T result{};
111 if constexpr (meta::kIsReservable<T>) {
112 result.reserve(size);
113 }
114 for (std::size_t i = 0; i < size; ++i) {
115 dump::Insert(result, reader.Read<meta::RangeValueType<T>>());
116 }
117 return result;
118}
119
120/// @brief Pair serialization support (for maps)
121template <typename T, typename U>
122requires(kIsWritable<T> && kIsWritable<U>)
123void Write(Writer& writer, const std::pair<T, U>& value) {
124 writer.Write(value.first);
125 writer.Write(value.second);
126}
127
128/// @brief Pair deserialization support (for maps)
129template <typename T, typename U>
130requires(kIsReadable<T> && kIsReadable<U>)
131std::pair<T, U> Read(Reader& reader, To<std::pair<T, U>>) {
132 return {reader.Read<T>(), reader.Read<U>()};
133}
134
135/// @brief `std::optional` serialization support
136template <typename T>
137requires kIsWritable<T>
138void Write(Writer& writer, const std::optional<T>& value) {
139 writer.Write(value.has_value());
140 if (value) {
141 writer.Write(*value);
142 }
143}
144
145/// @brief `std::optional` deserialization support
146template <typename T>
147requires kIsReadable<T>
148std::optional<T> Read(Reader& reader, To<std::optional<T>>) {
149 if (!reader.Read<bool>()) {
150 return std::nullopt;
151 }
152 return impl::ReadLazyPrvalue<T>(reader);
153}
154
155/// @brief `std::variant` serialization support
156template <typename... Args>
157requires(true && ... && kIsWritable<Args>)
158void Write(Writer& writer, const std::variant<Args...>& value) {
159 writer.Write(value.index());
160 std::visit([&writer](const auto& inner) { writer.Write(inner); }, value);
161}
162
163/// @brief `std::variant` deserialization support
164template <typename... Args>
165requires(true && ... && (std::is_move_constructible_v<Args> && kIsReadable<Args>))
166std::variant<Args...> Read(Reader& reader, To<std::variant<Args...>>) {
167 const auto index = reader.Read<std::size_t>();
168 if (index >= sizeof...(Args)) {
169 impl::ThrowInvalidVariantIndex(typeid(std::variant<Args...>), index);
170 }
171 return impl::ReadVariant<std::variant<Args...>>(reader, index);
172}
173
174/// Allows reading `const T`, which is usually encountered as a member of some
175/// container
176template <typename T>
177requires kIsReadable<T>
178T Read(Reader& reader, To<const T>) {
179 return Read(reader, To<T>{});
180}
181
182/// @brief utils::StrongTypedef serialization support
183template <typename Tag, typename T, utils::StrongTypedefOps Ops>
184requires kIsWritable<T>
185void Write(Writer& writer, const utils::StrongTypedef<Tag, T, Ops>& object) {
186 writer.Write(object.GetUnderlying());
187}
188
189/// @brief utils::StrongTypedef deserialization support
190template <typename Tag, typename T, utils::StrongTypedefOps Ops>
191requires kIsReadable<T>
192utils::StrongTypedef<Tag, T, Ops> Read(Reader& reader, To<utils::StrongTypedef<Tag, T, Ops>>) {
193 return utils::StrongTypedef<Tag, T, Ops>{reader.Read<T>()};
194}
195
196/// @brief `std::unique_ptr` serialization support
197template <typename T>
198requires kIsWritable<T>
199void Write(Writer& writer, const std::unique_ptr<T>& ptr) {
200 writer.Write(static_cast<bool>(ptr));
201 if (ptr) {
202 writer.Write(*ptr);
203 }
204}
205
206/// @brief `std::unique_ptr` deserialization support
207template <typename T>
208requires kIsReadable<T>
209std::unique_ptr<T> Read(Reader& reader, To<std::unique_ptr<T>>) {
210 if (!reader.Read<bool>()) {
211 return {};
212 }
213 return std::make_unique<T>(impl::ReadLazyPrvalue<T>(reader));
214}
215
216/// @brief `std::shared_ptr` serialization support
217/// @warning If two or more `shared_ptr` within a single dumped entity point to
218/// the same object, they will point to its distinct copies after loading a dump
219template <typename T>
220requires kIsWritable<T>
221void Write(Writer& writer, const std::shared_ptr<T>& ptr) {
222 writer.Write(static_cast<bool>(ptr));
223 if (ptr) {
224 writer.Write(*ptr);
225 }
226}
227
228/// @brief `std::shared_ptr` deserialization support
229/// @warning If two or more `shared_ptr` within a single dumped entity point to
230/// the same object, they will point to its distinct copies after loading a dump
231template <typename T>
232requires kIsReadable<T>
233std::shared_ptr<T> Read(Reader& reader, To<std::shared_ptr<T>>) {
234 if (!reader.Read<bool>()) {
235 return {};
236 }
237 return std::make_shared<T>(impl::ReadLazyPrvalue<T>(reader));
238}
239
240/// @brief `boost::bimap` serialization support
241template <typename L, typename R, typename... Args>
242requires(kIsWritable<impl::BoostBimapLeftKey<L, R, Args...>> && kIsWritable<impl::BoostBimapRightKey<L, R, Args...>>)
243void Write(Writer& writer, const boost::bimap<L, R, Args...>& map) {
244 writer.Write(map.size());
245
246 for (const auto& [left, right] : map) {
247 writer.Write(left);
248 writer.Write(right);
249 }
250}
251
252/// @brief `boost::bimap` deserialization support
253template <typename L, typename R, typename... Args>
254requires(kIsReadable<impl::BoostBimapLeftKey<L, R, Args...>> && kIsReadable<impl::BoostBimapRightKey<L, R, Args...>>)
255boost::bimap<L, R, Args...> Read(Reader& reader, To<boost::bimap<L, R, Args...>>) {
256 using BoostBimap = impl::BoostBimap<L, R, Args...>;
257
258 using BoostBimapLeftKey = impl::BoostBimapLeftKey<L, R, Args...>;
259 using BoostBimapRightKey = impl::BoostBimapRightKey<L, R, Args...>;
260
261 using BoostBimapSizeType = typename BoostBimap::size_type;
262
263 BoostBimap map;
264 // bimap doesn't have reserve :(
265
266 const auto size = reader.Read<BoostBimapSizeType>();
267 for (BoostBimapSizeType i = 0; i < size; ++i) {
268 // `Read`s are guaranteed to occur left-to-right in brace-init
269 map.insert({
270 reader.Read<BoostBimapLeftKey>(),
271 reader.Read<BoostBimapRightKey>(),
272 });
273 }
274
275 return map;
276}
277
278/// @brief `boost::multi_index_container` serialization support
279template <typename T, typename Index, typename Alloc>
280requires kIsWritable<T>
281void Write(Writer& writer, const boost::multi_index_container<T, Index, Alloc>& container) {
282 writer.Write(container.template get<0>().size());
283 for (auto& item : container.template get<0>()) {
284 writer.Write(item);
285 }
286}
287
288/// @brief `boost::multi_index_container` deserialization support
289template <typename T, typename Index, typename Alloc>
290requires kIsReadable<T>
291boost::multi_index_container<T, Index, Alloc> Read(Reader& reader, To<boost::multi_index_container<T, Index, Alloc>>) {
292 const auto size = reader.Read<std::size_t>();
293 boost::multi_index_container<T, Index, Alloc> container;
294
295 // boost::multi_index_container has reserve() with some, but not all, configs
296 if constexpr (meta::kIsReservable<boost::multi_index_container<T, Index, Alloc>>) {
297 container.reserve(size);
298 }
299
300 for (std::size_t i = 0; i < size; ++i) {
301 container.insert(reader.Read<T>());
302 }
303
304 return container;
305}
306
307} // namespace dump
308
309USERVER_NAMESPACE_END