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