userver: userver/utils/any_movable.hpp Source File
Loading...
Searching...
No Matches
any_movable.hpp
Go to the documentation of this file.
1#pragma once
2
3/// @file userver/utils/any_movable.hpp
4/// @brief @copybrief utils::AnyMovable
5
6#include <any> // for std::bad_any_cast
7#include <initializer_list>
8#include <memory>
9#include <type_traits>
10#include <utility>
11
12#include <userver/compiler/impl/lifetime.hpp>
13
14USERVER_NAMESPACE_BEGIN
15
16/// Utilities
17namespace utils {
18
19/// @ingroup userver_universal userver_containers
20///
21/// @brief Replacement for `std::any` that is not copyable. It allows to store
22/// non-copyable and even non-movable types.
23///
24/// Usage example:
25/// @snippet universal/src/utils/any_movable_test.cpp AnyMovable example usage
26class AnyMovable final {
27public:
28 /// Creates an empty `AnyMovable`
29 constexpr AnyMovable() noexcept = default;
30
31 /// `AnyMovable` is movable, but not copyable
32 AnyMovable(AnyMovable&&) noexcept = default;
33 AnyMovable& operator=(AnyMovable&&) noexcept = default;
34
35 /// Copies or moves the provided object inside the `AnyMovable`. `const`,
36 /// reference, arrays and function pointers are decayed.
37 template <typename ValueType>
38 requires(!std::is_same_v<AnyMovable, std::decay_t<ValueType>>)
39 /*implicit*/ AnyMovable(ValueType&& value);
40
41 /// In-place constructs an object of the specified type
42 template <typename ValueType, typename... Args>
43 explicit AnyMovable(std::in_place_type_t<ValueType> tag, Args&&... args);
44
45 /// In-place constructs an object of the specified type
46 template <typename ValueType, typename Item, typename... Args>
47 explicit AnyMovable(std::in_place_type_t<ValueType> tag, std::initializer_list<Item> list, Args&&... args);
48
49 /// Copies or moves the provided object inside the `AnyMovable`. `const`,
50 /// reference, arrays and function pointers are decayed.
51 template <typename ValueType>
52 AnyMovable& operator=(ValueType&& rhs);
53
54 /// Check if the `AnyMovable` is empty
55 bool HasValue() const noexcept;
56
57 /// Destroy the old contents, making `*this` empty
58 void Reset() noexcept;
59
60 /// In-place constructs an object of the specified type
61 template <typename ValueType, typename... Args>
62 ValueType& Emplace(Args&&... args) USERVER_IMPL_LIFETIME_BOUND;
63
64 /// In-place constructs an object of the specified type
65 template <typename ValueType, typename Item, typename... Args>
66 ValueType& Emplace(std::initializer_list<Item> list, Args&&... args) USERVER_IMPL_LIFETIME_BOUND;
67
68private:
69 struct HolderBase;
70
71 template <typename ValueType>
72 struct Holder;
73
74 struct HolderDeleter {
75 void operator()(HolderBase* holder) noexcept;
76 };
77
78 template <typename ValueType>
79 friend ValueType* AnyCast(AnyMovable*) noexcept;
80
81 template <typename ValueType>
82 friend const ValueType* AnyCast(const AnyMovable*) noexcept;
83
84 std::unique_ptr<HolderBase, HolderDeleter> content_;
85};
86
87/// @brief The exception that is thrown when `AnyCast` fails
88class BadAnyMovableCast final : public std::bad_any_cast {
89public:
90 const char* what() const noexcept override;
91};
92
93/// @return nullptr if operand is nullptr or type of the data stored in operand
94/// does not match ValueType
95template <typename ValueType>
96ValueType* AnyCast(AnyMovable* operand) noexcept;
97
98/// @return nullptr if operand is nullptr or type of the data stored in operand
99/// does not match ValueType
100template <typename ValueType>
101const ValueType* AnyCast(const AnyMovable* operand) noexcept;
102
103/// @note Cast to a reference type to avoid extra copies
104/// @throw BadAnyMovableCast if type of the data stored in operand
105/// does not match ValueType
106template <typename ValueType>
107ValueType AnyCast(AnyMovable& operand);
108
109/// @note Cast to a reference type to avoid extra copies
110/// @throw BadAnyMovableCast if type of the data stored in operand
111/// does not match ValueType
112template <typename ValueType>
113ValueType AnyCast(const AnyMovable& operand);
114
115/// @note Cast to a reference type to avoid extra moves
116/// @throw BadAnyMovableCast if type of the data stored in operand
117/// does not match ValueType
118template <typename ValueType>
119ValueType AnyCast(AnyMovable&& operand);
120
121struct AnyMovable::HolderBase {
122 using DeleterType = void (*)(HolderBase&) noexcept;
123
124 DeleterType deleter;
125};
126
127template <typename ValueType>
128struct AnyMovable::Holder final : public HolderBase {
129 static_assert(
130 std::is_same_v<ValueType, std::decay_t<ValueType>>,
131 "The requested type can't be stored in an AnyMovable"
132 );
133
134 ValueType held;
135
136 static void Deleter(HolderBase& holder) noexcept { delete &static_cast<Holder&>(holder); }
137
138 template <typename... Args>
139 static std::unique_ptr<HolderBase, HolderDeleter> Make(Args&&... args) {
140 // NOLINTNEXTLINE(clang-analyzer-cplusplus.NewDeleteLeaks)
141 return std::unique_ptr<HolderBase, HolderDeleter>{
142 // intended raw ctor call, sometimes casts
143 // NOLINTNEXTLINE(google-readability-casting)
144 new Holder{{&Deleter}, ValueType(std::forward<Args>(args)...)}
145 };
146 }
147
148 static ValueType* GetIf(const AnyMovable* any) noexcept {
149 return (any && any->content_ && any->content_->deleter == &Deleter)
150 ? &static_cast<Holder&>(*any->content_).held
151 : nullptr;
152 }
153};
154
155template <typename ValueType>
156requires(!std::is_same_v<AnyMovable, std::decay_t<ValueType>>)
157AnyMovable::AnyMovable(ValueType&& value)
158 : content_(Holder<std::decay_t<ValueType>>::Make(std::forward<ValueType>(value)))
159{
160 static_assert(
161 !std::is_same_v<AnyMovable*, std::decay_t<ValueType>> &&
162 !std::is_same_v<const AnyMovable*, std::decay_t<ValueType>>,
163 "AnyMovable misuse detected: trying to wrap AnyMovable* in another "
164 "AnyMovable. The pointer was probably meant to be dereferenced."
165 );
166}
167
168template <typename ValueType, typename... Args>
169AnyMovable::AnyMovable(std::in_place_type_t<ValueType> /*tag*/, Args&&... args)
170 : content_(Holder<ValueType>::Make(std::forward<Args>(args)...))
171{}
172
173template <typename ValueType, typename Item, typename... Args>
174AnyMovable::AnyMovable(std::in_place_type_t<ValueType> /*tag*/, std::initializer_list<Item> list, Args&&... args)
175 : content_(Holder<ValueType>::Make(list, std::forward<Args>(args)...))
176{}
177
178template <typename ValueType>
179AnyMovable& AnyMovable::operator=(ValueType&& rhs) {
180 *this = AnyMovable(std::forward<ValueType>(rhs));
181 return *this;
182}
183
184template <typename ValueType, typename... Args>
185ValueType& AnyMovable::Emplace(Args&&... args) USERVER_IMPL_LIFETIME_BOUND {
186 content_ = Holder<ValueType>::Make(std::forward<Args>(args)...);
187 return static_cast<Holder<ValueType>&>(*content_).held;
188}
189
190template <typename ValueType, typename Item, typename... Args>
191ValueType& AnyMovable::Emplace(std::initializer_list<Item> list, Args&&... args) USERVER_IMPL_LIFETIME_BOUND {
192 content_ = Holder<ValueType>::Make(list, std::forward<Args>(args)...);
193 return static_cast<Holder<ValueType>&>(*content_).held;
194}
195
196template <typename ValueType>
197ValueType* AnyCast(AnyMovable* operand) noexcept {
198 return AnyMovable::Holder<ValueType>::GetIf(operand);
199}
200
201template <typename ValueType>
202const ValueType* AnyCast(const AnyMovable* operand) noexcept {
203 return AnyMovable::Holder<ValueType>::GetIf(operand);
204}
205
206template <typename ValueType>
207// might be requested by user
208// NOLINTNEXTLINE(readability-const-return-type)
209ValueType AnyCast(AnyMovable& operand) {
210 using NonRef = std::remove_cv_t<std::remove_reference_t<ValueType>>;
211 auto* result = AnyCast<NonRef>(&operand);
212 if (!result) {
213 throw BadAnyMovableCast();
214 }
215 return static_cast<ValueType>(*result);
216}
217
218template <typename ValueType>
219// might be requested by user
220// NOLINTNEXTLINE(readability-const-return-type)
221ValueType AnyCast(const AnyMovable& operand) {
222 using NonRef = std::remove_cv_t<std::remove_reference_t<ValueType>>;
223 auto* result = AnyCast<NonRef>(&operand);
224 if (!result) {
225 throw BadAnyMovableCast();
226 }
227 return static_cast<ValueType>(*result);
228}
229
230template <typename ValueType>
231ValueType AnyCast(AnyMovable&& operand) {
232 using NonRef = std::remove_cv_t<std::remove_reference_t<ValueType>>;
233 auto* result = AnyCast<NonRef>(&operand);
234 if (!result) {
235 throw BadAnyMovableCast();
236 }
237 return static_cast<ValueType>(std::move(*result));
238}
239
240} // namespace utils
241
242USERVER_NAMESPACE_END