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>
96std::enable_if_t<kIsContainer<T> && kIsWritable<meta::RangeValueType<T>>> Write(Writer& writer, const T& value) {
97 writer.Write(std::size(value));
98 for (const auto& item : value) {
99 // explicit cast for vector<bool> shenanigans
100 writer.Write(static_cast<const meta::RangeValueType<T>&>(item));
101 }
102}
103
104/// @brief Container deserialization support
105template <typename T>
106std::enable_if_t<kIsContainer<T> && kIsReadable<meta::RangeValueType<T>>, T> Read(Reader& reader, To<T>) {
107 const auto size = reader.Read<std::size_t>();
108 T result{};
109 if constexpr (meta::kIsReservable<T>) {
110 result.reserve(size);
111 }
112 for (std::size_t i = 0; i < size; ++i) {
113 dump::Insert(result, reader.Read<meta::RangeValueType<T>>());
114 }
115 return result;
116}
117
118/// @brief Pair serialization support (for maps)
119template <typename T, typename U>
120std::enable_if_t<kIsWritable<T> && kIsWritable<U>, void> Write(Writer& writer, const std::pair<T, U>& value) {
121 writer.Write(value.first);
122 writer.Write(value.second);
123}
124
125/// @brief Pair deserialization support (for maps)
126template <typename T, typename U>
127std::enable_if_t<kIsReadable<T> && kIsReadable<U>, std::pair<T, U>> Read(Reader& reader, To<std::pair<T, U>>) {
128 return {reader.Read<T>(), reader.Read<U>()};
129}
130
131/// @brief `std::optional` serialization support
132template <typename T>
133std::enable_if_t<kIsWritable<T>> Write(Writer& writer, const std::optional<T>& value) {
134 writer.Write(value.has_value());
135 if (value) {
136 writer.Write(*value);
137 }
138}
139
140/// @brief `std::optional` deserialization support
141template <typename T>
142std::enable_if_t<kIsReadable<T>, std::optional<T>> Read(Reader& reader, To<std::optional<T>>) {
143 if (!reader.Read<bool>()) {
144 return std::nullopt;
145 }
146 return impl::ReadLazyPrvalue<T>(reader);
147}
148
149/// @brief `std::variant` serialization support
150template <typename... Args>
151std::enable_if_t<(true && ... && kIsWritable<Args>)> Write(Writer& writer, const std::variant<Args...>& value) {
152 writer.Write(value.index());
153 std::visit([&writer](const auto& inner) { writer.Write(inner); }, value);
154}
155
156/// @brief `std::variant` deserialization support
157template <typename... Args>
158std::enable_if_t<(true && ... && (std::is_move_constructible_v<Args> && kIsReadable<Args>)), std::variant<Args...>>
159Read(Reader& reader, To<std::variant<Args...>>) {
160 const auto index = reader.Read<std::size_t>();
161 if (index >= sizeof...(Args)) {
162 impl::ThrowInvalidVariantIndex(typeid(std::variant<Args...>), index);
163 }
164 return impl::ReadVariant<std::variant<Args...>>(reader, index);
165}
166
167/// Allows reading `const T`, which is usually encountered as a member of some
168/// container
169template <typename T>
170std::enable_if_t<kIsReadable<T>, T> Read(Reader& reader, To<const T>) {
171 return Read(reader, To<T>{});
172}
173
174/// @brief utils::StrongTypedef serialization support
175template <typename Tag, typename T, utils::StrongTypedefOps Ops>
176std::enable_if_t<kIsWritable<T>> Write(Writer& writer, const utils::StrongTypedef<Tag, T, Ops>& object) {
177 writer.Write(object.GetUnderlying());
178}
179
180/// @brief utils::StrongTypedef deserialization support
181template <typename Tag, typename T, utils::StrongTypedefOps Ops>
182std::enable_if_t<kIsReadable<T>, utils::StrongTypedef<Tag, T, Ops>>
183Read(Reader& reader, To<utils::StrongTypedef<Tag, T, Ops>>) {
184 return utils::StrongTypedef<Tag, T, Ops>{reader.Read<T>()};
185}
186
187/// @brief `std::unique_ptr` serialization support
188template <typename T>
189std::enable_if_t<kIsWritable<T>> Write(Writer& writer, const std::unique_ptr<T>& ptr) {
190 writer.Write(static_cast<bool>(ptr));
191 if (ptr) {
192 writer.Write(*ptr);
193 }
194}
195
196/// @brief `std::unique_ptr` deserialization support
197template <typename T>
198std::enable_if_t<kIsReadable<T>, std::unique_ptr<T>> Read(Reader& reader, To<std::unique_ptr<T>>) {
199 if (!reader.Read<bool>()) {
200 return {};
201 }
202 return std::make_unique<T>(impl::ReadLazyPrvalue<T>(reader));
203}
204
205/// @brief `std::shared_ptr` serialization support
206/// @warning If two or more `shared_ptr` within a single dumped entity point to
207/// the same object, they will point to its distinct copies after loading a dump
208template <typename T>
209std::enable_if_t<kIsWritable<T>> Write(Writer& writer, const std::shared_ptr<T>& ptr) {
210 writer.Write(static_cast<bool>(ptr));
211 if (ptr) {
212 writer.Write(*ptr);
213 }
214}
215
216/// @brief `std::shared_ptr` deserialization 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>
220std::enable_if_t<kIsReadable<T>, std::shared_ptr<T>> Read(Reader& reader, To<std::shared_ptr<T>>) {
221 if (!reader.Read<bool>()) {
222 return {};
223 }
224 return std::make_shared<T>(impl::ReadLazyPrvalue<T>(reader));
225}
226
227/// @brief `boost::bimap` serialization support
228template <typename L, typename R, typename... Args>
229std::enable_if_t<
230 kIsWritable<impl::BoostBimapLeftKey<L, R, Args...>> && kIsWritable<impl::BoostBimapRightKey<L, R, Args...>>>
231Write(Writer& writer, const boost::bimap<L, R, Args...>& map) {
232 writer.Write(map.size());
233
234 for (const auto& [left, right] : map) {
235 writer.Write(left);
236 writer.Write(right);
237 }
238}
239
240/// @brief `boost::bimap` deserialization support
241template <typename L, typename R, typename... Args>
242std::enable_if_t<
243 kIsReadable<impl::BoostBimapLeftKey<L, R, Args...>> && kIsReadable<impl::BoostBimapRightKey<L, R, Args...>>,
244 boost::bimap<L, R, Args...>>
245Read(Reader& reader, To<boost::bimap<L, R, Args...>>) {
246 using BoostBimap = impl::BoostBimap<L, R, Args...>;
247
248 using BoostBimapLeftKey = impl::BoostBimapLeftKey<L, R, Args...>;
249 using BoostBimapRightKey = impl::BoostBimapRightKey<L, R, Args...>;
250
251 using BoostBimapSizeType = typename BoostBimap::size_type;
252
253 BoostBimap map;
254 // bimap doesn't have reserve :(
255
256 const auto size = reader.Read<BoostBimapSizeType>();
257 for (BoostBimapSizeType i = 0; i < size; ++i) {
258 // `Read`s are guaranteed to occur left-to-right in brace-init
259 map.insert({
260 reader.Read<BoostBimapLeftKey>(),
261 reader.Read<BoostBimapRightKey>(),
262 });
263 }
264
265 return map;
266}
267
268/// @brief `boost::multi_index_container` serialization support
269template <typename T, typename Index, typename Alloc>
270std::enable_if_t<kIsWritable<T>> Write(Writer& writer, const boost::multi_index_container<T, Index, Alloc>& container) {
271 writer.Write(container.template get<0>().size());
272 for (auto& item : container.template get<0>()) {
273 writer.Write(item);
274 }
275}
276
277/// @brief `boost::multi_index_container` deserialization support
278template <typename T, typename Index, typename Alloc>
279std::enable_if_t<kIsReadable<T>, boost::multi_index_container<T, Index, Alloc>>
280Read(Reader& reader, To<boost::multi_index_container<T, Index, Alloc>>) {
281 const auto size = reader.Read<std::size_t>();
282 boost::multi_index_container<T, Index, Alloc> container;
283
284 // boost::multi_index_container has reserve() with some, but not all, configs
285 if constexpr (meta::kIsReservable<boost::multi_index_container<T, Index, Alloc>>) {
286 container.reserve(size);
287 }
288
289 for (std::size_t i = 0; i < size; ++i) {
290 container.insert(reader.Read<T>());
291 }
292
293 return container;
294}
295
296} // namespace dump
297
298USERVER_NAMESPACE_END