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