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