userver: userver/utils/box.hpp Source File
Loading...
Searching...
No Matches
box.hpp
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 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 template <typename U = T>
63 // Protect against hiding special constructors.
64 requires(!std::same_as<std::remove_cvref_t<U>, Box>) &&
65 // Prevent infinite recursion from constructible_from in the next check.
66 (!impl::IsBox<std::remove_cvref_t<U>>::value) &&
67 // Prevent infinite recursion.
68 (std::same_as<std::remove_cvref_t<U>, T> || !std::is_constructible_v<T, Box>) &&
69 // Normal requirement.
70 std::is_constructible_v<T, U>
71 explicit(!std::convertible_to<U, T>) Box(U&& arg)
72 : data_(std::make_unique<T>(std::forward<U>(arg)))
73 {}
74
75 /// Allocate the value, emplacing it with the given @a args.
76 template <typename... Args>
77 requires(sizeof...(Args) >= 2) && std::is_constructible_v<T, Args...>
78 explicit Box(Args&&... args)
79 : data_(std::make_unique<T>(std::forward<Args>(args)...))
80 {}
81
82 /// Allocate the value as constructed by the given @a factory.
83 /// Allows to save an extra move of the contained value.
84 template <typename Factory>
85 static Box MakeWithFactory(Factory&& factory) {
86 return Box(EmplaceFactory{}, std::forward<Factory>(factory));
87 }
88
89 Box(Box&& other) noexcept = default;
90 Box& operator=(Box&& other) noexcept = default;
91
92 Box(const Box& other)
93 : data_(std::make_unique<T>(*other))
94 {}
95
96 Box& operator=(const Box& other) {
97 *this = Box{other};
98 return *this;
99 }
100
101 /// Assigns-through to the contained value.
102 template <typename U = T>
103 requires(!std::same_as<std::remove_cvref_t<U>, Box>) && std::is_assignable_v<T&, U>
104 Box& operator=(U&& other) {
105 if (data_) {
106 *data_ = std::forward<U>(other);
107 } else {
108 data_ = std::make_unique<T>(std::forward<U>(other));
109 }
110 return *this;
111 }
112
113 // Box is always engaged, unless moved-from. Just call *box.
114 /*implicit*/ operator bool() const = delete;
115
116 T* operator->() noexcept { return Get(); }
117 const T* operator->() const noexcept { return Get(); }
118
119 T& operator*() noexcept { return *Get(); }
120 const T& operator*() const noexcept { return *Get(); }
121
122 bool operator==(const Box& other) const { return **this == *other; }
123
124 bool operator!=(const Box& other) const { return **this != *other; }
125
126 bool operator<(const Box& other) const { return **this < *other; }
127
128 bool operator>(const Box& other) const { return **this > *other; }
129
130 bool operator<=(const Box& other) const { return **this <= *other; }
131
132 bool operator>=(const Box& other) const { return **this >= *other; }
133
134private:
135 struct EmplaceFactory final {};
136
137 template <typename Factory>
138 explicit Box(EmplaceFactory, Factory&& factory)
139 : data_(new T(std::forward<Factory>(factory)()))
140 {}
141
142 T* Get() noexcept {
143 UASSERT_MSG(data_, "Accessing a moved-from Box");
144 return data_.get();
145 }
146
147 const T* Get() const noexcept {
148 UASSERT_MSG(data_, "Accessing a moved-from Box");
149 return data_.get();
150 }
151
152 std::unique_ptr<T> data_;
153};
154
155template <typename Value, typename T>
156Box<T> Parse(const Value& value, formats::parse::To<Box<T>>) {
157 return Box<T>::MakeWithFactory([&value] { return value.template As<T>(); });
158}
159
160template <typename Value, typename T>
161Value Serialize(const Box<T>& value, formats::serialize::To<Value>) {
162 return Serialize(*value, formats::serialize::To<Value>{});
163}
164
165template <typename StringBuilder, typename T>
166void WriteToStream(const Box<T>& value, StringBuilder& sw) {
167 WriteToStream(*value, sw);
168}
169
170template <typename T>
171logging::LogHelper& operator<<(logging::LogHelper& lh, const Box<T>& box) {
172 lh << *box;
173 return lh;
174}
175
176} // namespace utils
177
178USERVER_NAMESPACE_END