userver: userver/utils/box.hpp Source File
Loading...
Searching...
No Matches
box.hpp
Go to the documentation of this file.
1#pragma once
2
3/// @file userver/utils/box.hpp
4/// @brief @copybrief utils::Box
5
6#include <memory>
7#include <type_traits>
8#include <utility>
9
10#include <userver/formats/parse/to.hpp>
11#include <userver/formats/serialize/to.hpp>
12#include <userver/logging/log_helper_fwd.hpp>
13#include <userver/utils/assert.hpp>
14
15USERVER_NAMESPACE_BEGIN
16
17namespace utils {
18
19namespace impl {
20
21template <typename Self, typename... Args>
22inline constexpr bool kArgsAreNotSelf = ((sizeof...(Args) > 1) || ... || !std::is_same_v<std::decay_t<Args>, Self>);
23
24template <bool Condition, template <typename...> typename Trait, typename... Args>
25constexpr bool ConjunctionWithTrait() noexcept {
26 if constexpr (Condition) {
27 return Trait<Args...>::value;
28 } else {
29 return false;
30 }
31}
32
33} // namespace impl
34
35/// @brief Remote storage for a single item. Implemented as a unique pointer
36/// that is never `null`, except when moved from.
37///
38/// Has the semantics of non-optional `T`.
39/// Copies the content on copy, compares by the contained value.
40///
41/// Use in the following cases:
42/// - to create recursive types while maintaining value semantics;
43/// - to hide the implementation of a class in cpp;
44/// - to prevent the large size or alignment of a field from inflating the size
45/// or alignment of an object.
46///
47/// Use utils::UniqueRef instead:
48/// - to add a non-movable field to a movable object;
49/// - to own an object of a polymorphic base class.
50///
51/// Usage example:
52/// @snippet utils/box_test.cpp sample
53template <typename T>
54class Box {
55public:
56 /// Allocate a default-constructed value.
57 // Would like to use SFINAE here, but std::optional<Box> requests tests for
58 // default construction eagerly, which errors out for a forward-declared T.
60
61 /// Allocate a `T`, copying or moving @a arg.
62 template <
63 typename U = T,
64 std::enable_if_t<
65 impl::ConjunctionWithTrait<
66 // Protection against hiding special
67 // constructors.
68 impl::kArgsAreNotSelf<Box, U>,
69 // Only allow the implicit conversion to Box<T>
70 // if U is implicitly convertible to T. Also,
71 // support SFINAE.
72 std::is_convertible,
73 U&&,
74 T>(),
75 int> = 0>
76 /*implicit*/ Box(U&& arg) : data_(std::make_unique<T>(std::forward<U>(arg))) {}
77
78 /// Allocate the value, emplacing it with the given @a args.
79 template <
80 typename... Args,
81 std::enable_if_t<
82 impl::ConjunctionWithTrait<
83 // Protection against hiding special
84 // constructors.
85 impl::kArgsAreNotSelf<Box, Args...>,
86 // Support SFINAE.
87 std::is_constructible,
88 T,
89 Args&&...>(),
90 int> = 0>
91 explicit Box(Args&&... args) : data_(std::make_unique<T>(std::forward<Args>(args)...)) {}
92
93 /// Allocate the value as constructed by the given @a factory.
94 /// Allows to save an extra move of the contained value.
95 template <typename Factory>
96 static Box MakeWithFactory(Factory&& factory) {
97 return Box(EmplaceFactory{}, std::forward<Factory>(factory));
98 }
99
100 Box(Box&& other) noexcept = default;
101 Box& operator=(Box&& other) noexcept = default;
102
103 Box(const Box& other) : data_(std::make_unique<T>(*other)) {}
104
105 Box& operator=(const Box& other) {
106 *this = Box{other};
107 return *this;
108 }
109
110 /// Assigns-through to the contained value.
111 template <
112 typename U = T,
113 std::enable_if_t<
114 impl::ConjunctionWithTrait< //
115 impl::ConjunctionWithTrait<
116 // Protection against hiding
117 // special constructors.
118 impl::kArgsAreNotSelf<Box, U>,
119 // Support SFINAE.
120 std::is_constructible,
121 T,
122 U>(),
123 std::is_assignable,
124 T&,
125 U>(),
126 int> = 0>
127 Box& operator=(U&& other) {
128 if (data_) {
129 *data_ = std::forward<U>(other);
130 } else {
131 data_ = std::make_unique<T>(std::forward<U>(other));
132 }
133 return *this;
134 }
135
136 // Box is always engaged, unless moved-from. Just call *box.
137 /*implicit*/ operator bool() const = delete;
138
139 T* operator->() noexcept { return Get(); }
140 const T* operator->() const noexcept { return Get(); }
141
142 T& operator*() noexcept { return *Get(); }
143 const T& operator*() const noexcept { return *Get(); }
144
145 bool operator==(const Box& other) const { return **this == *other; }
146
147 bool operator!=(const Box& other) const { return **this != *other; }
148
149 bool operator<(const Box& other) const { return **this < *other; }
150
151 bool operator>(const Box& other) const { return **this > *other; }
152
153 bool operator<=(const Box& other) const { return **this <= *other; }
154
155 bool operator>=(const Box& other) const { return **this >= *other; }
156
157private:
158 struct EmplaceFactory final {};
159
160 template <typename Factory>
161 explicit Box(EmplaceFactory, Factory&& factory) : data_(new T(std::forward<Factory>(factory)())) {}
162
163 T* Get() noexcept {
164 UASSERT_MSG(data_, "Accessing a moved-from Box");
165 return data_.get();
166 }
167
168 const T* Get() const noexcept {
169 UASSERT_MSG(data_, "Accessing a moved-from Box");
170 return data_.get();
171 }
172
173 std::unique_ptr<T> data_;
174};
175
176template <typename Value, typename T>
177Box<T> Parse(const Value& value, formats::parse::To<Box<T>>) {
178 return Box<T>::MakeWithFactory([&value] { return value.template As<T>(); });
179}
180
181template <typename Value, typename T>
182Value Serialize(const Box<T>& value, formats::serialize::To<Value>) {
183 return Serialize(*value, formats::serialize::To<Value>{});
184}
185
186template <typename StringBuilder, typename T>
187void WriteToStream(const Box<T>& value, StringBuilder& sw) {
188 WriteToStream(*value, sw);
189}
190
191template <typename T>
192logging::LogHelper& operator<<(logging::LogHelper& lh, const Box<T>& box) {
193 lh << *box;
194 return lh;
195}
196
197} // namespace utils
198
199USERVER_NAMESPACE_END