xref: /aosp_15_r20/external/pigweed/third_party/fuchsia/repo/sdk/lib/fit/include/lib/fit/nullable.h (revision 61c4878ac05f98d0ceed94b57d316916de578985)
1 // Copyright 2018 The Fuchsia Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4 
5 #ifndef LIB_FIT_NULLABLE_H_
6 #define LIB_FIT_NULLABLE_H_
7 
8 #include <assert.h>
9 #include <lib/stdcompat/optional.h>
10 
11 #include <type_traits>
12 #include <utility>
13 
14 #include "pw_assert/assert.h"
15 
16 namespace fit {
17 
18 // Determines whether a type can be compared with nullptr.
19 template <typename T, typename Comparable = bool>
20 struct is_comparable_with_null : public std::false_type {};
21 template <typename T>
22 struct is_comparable_with_null<T, decltype(std::declval<const T&>() == nullptr)>
23     : public std::true_type {};
24 
25 // Suppress the warning when the compiler can see that a nullable value is
26 // never equal to nullptr.
27 #pragma GCC diagnostic push
28 #pragma GCC diagnostic ignored "-Waddress"
29 template <typename T, std::enable_if_t<is_comparable_with_null<T>::value, bool> = true>
30 constexpr inline bool is_null(T&& value) {
31   return std::forward<T>(value) == nullptr;
32 }
33 #pragma GCC diagnostic pop
34 
35 template <typename T, std::enable_if_t<!is_comparable_with_null<T>::value, bool> = false>
36 constexpr inline bool is_null(T&&) {
37   return false;
38 }
39 
40 // Determines whether a type can be initialized, assigned, and compared
41 // with nullptr.
42 template <typename T>
43 struct is_nullable
44     : public std::integral_constant<bool, std::is_constructible_v<T, decltype(nullptr)> &&
45                                               std::is_assignable_v<T&, decltype(nullptr)> &&
46                                               is_comparable_with_null<T>::value> {};
47 template <>
48 struct is_nullable<void> : public std::false_type {};
49 
50 // Holds a value or nullptr.
51 //
52 // This class is similar to |std::optional<T>| except that it uses less
53 // storage when the value type can be initialized, assigned, and compared
54 // with nullptr.
55 //
56 // For example:
57 // - sizeof(fit::nullable<void*>) == sizeof(void*)
58 // - sizeof(std::optional<void*>) == sizeof(struct { bool; void*; })
59 // - sizeof(fit::nullable<int>) == sizeof(struct { bool; int; })
60 // - sizeof(std::optional<int>) == sizeof(struct { bool; int; })
61 //
62 // TODO(https://fxbug.dev/42123486): fit::nullable does not precisely mirror
63 // cpp17::optional. This should be corrected to avoid surprises when switching
64 // between the types.
65 template <typename T, bool = (is_nullable<T>::value && std::is_constructible_v<T, T&&> &&
66                               std::is_assignable_v<T&, T&&>)>
67 class nullable final {
68  public:
69   using value_type = T;
70 
71   ~nullable() = default;
72   constexpr nullable() = default;
73 
74   explicit constexpr nullable(decltype(nullptr)) {}
75   explicit constexpr nullable(T value) : opt_(std::move(value)) {}
76 
77   constexpr nullable(const nullable& other) = default;
78   constexpr nullable& operator=(const nullable& other) = default;
79 
80   constexpr nullable(nullable&& other) = default;
81   constexpr nullable& operator=(nullable&& other) = default;
82 
83   constexpr T& value() & { return opt_.value(); }
84   constexpr const T& value() const& { return opt_.value(); }
85   constexpr T&& value() && { return std::move(opt_.value()); }
86   constexpr const T&& value() const&& { return std::move(opt_.value()); }
87 
88   template <typename U = T>
89   constexpr T value_or(U&& default_value) const {
90     return opt_.value_or(std::forward<U>(default_value));
91   }
92 
93   constexpr T* operator->() { return &*opt_; }
94   constexpr const T* operator->() const { return &*opt_; }
95   constexpr T& operator*() { return *opt_; }
96   constexpr const T& operator*() const { return *opt_; }
97 
98   constexpr bool has_value() const { return opt_.has_value(); }
99   explicit constexpr operator bool() const { return has_value(); }
100 
101   constexpr nullable& operator=(decltype(nullptr)) {
102     reset();
103     return *this;
104   }
105 
106   constexpr nullable& operator=(T value) {
107     opt_ = std::move(value);
108     return *this;
109   }
110 
111   constexpr void reset() { opt_.reset(); }
112 
113   constexpr void swap(nullable& other) { opt_.swap(other.opt_); }
114 
115  private:
116   cpp17::optional<T> opt_;
117 };
118 
119 template <typename T>
120 class nullable<T, true> final {
121  public:
122   using value_type = T;
123 
124   constexpr nullable() : value_(nullptr) {}
125   explicit constexpr nullable(decltype(nullptr)) : value_(nullptr) {}
126   explicit constexpr nullable(T value) : value_(std::move(value)) {}
127   constexpr nullable(const nullable& other) = default;
128   constexpr nullable(nullable&& other) : value_(std::move(other.value_)) {}
129   ~nullable() = default;
130 
131   constexpr T& value() & {
132     if (has_value()) {
133       return value_;
134     }
135     PW_ASSERT(false);
136   }
137   constexpr const T& value() const& {
138     if (has_value()) {
139       return value_;
140     }
141     PW_ASSERT(false);
142   }
143   constexpr T&& value() && {
144     if (has_value()) {
145       return std::move(value_);
146     }
147     PW_ASSERT(false);
148   }
149   constexpr const T&& value() const&& {
150     if (has_value()) {
151       return std::move(value_);
152     }
153     PW_ASSERT(false);
154   }
155 
156   template <typename U = T>
157   constexpr T value_or(U&& default_value) const {
158     return has_value() ? value_ : static_cast<T>(std::forward<U>(default_value));
159   }
160 
161   constexpr T* operator->() { return &value_; }
162   constexpr const T* operator->() const { return &value_; }
163   constexpr T& operator*() { return value_; }
164   constexpr const T& operator*() const { return value_; }
165 
166   constexpr bool has_value() const { return !(value_ == nullptr); }
167   explicit constexpr operator bool() const { return has_value(); }
168 
169   constexpr nullable& operator=(const nullable& other) = default;
170   constexpr nullable& operator=(nullable&& other) {
171     value_ = std::move(other.value_);
172     return *this;
173   }
174 
175   constexpr nullable& operator=(decltype(nullptr)) {
176     reset();
177     return *this;
178   }
179 
180   constexpr nullable& operator=(T value) {
181     value_ = std::move(value);
182     return *this;
183   }
184 
185   constexpr void reset() { value_ = nullptr; }
186 
187   constexpr void swap(nullable& other) {
188     using std::swap;
189     swap(value_, other.value_);
190   }
191 
192  private:
193   T value_;
194 };
195 
196 template <typename T>
197 void swap(nullable<T>& a, nullable<T>& b) {
198   a.swap(b);
199 }
200 
201 template <typename T>
202 constexpr bool operator==(const nullable<T>& lhs, decltype(nullptr)) {
203   return !lhs.has_value();
204 }
205 template <typename T>
206 constexpr bool operator!=(const nullable<T>& lhs, decltype(nullptr)) {
207   return lhs.has_value();
208 }
209 
210 template <typename T>
211 constexpr bool operator==(decltype(nullptr), const nullable<T>& rhs) {
212   return !rhs.has_value();
213 }
214 template <typename T>
215 constexpr bool operator!=(decltype(nullptr), const nullable<T>& rhs) {
216   return rhs.has_value();
217 }
218 
219 template <typename T, typename U>
220 constexpr bool operator==(const nullable<T>& lhs, const nullable<U>& rhs) {
221   return (lhs.has_value() == rhs.has_value()) && (!lhs.has_value() || *lhs == *rhs);
222 }
223 template <typename T, typename U>
224 constexpr bool operator!=(const nullable<T>& lhs, const nullable<U>& rhs) {
225   return (lhs.has_value() != rhs.has_value()) || (lhs.has_value() && *lhs != *rhs);
226 }
227 
228 template <typename T, typename U>
229 constexpr bool operator==(const nullable<T>& lhs, const U& rhs) {
230   return (lhs.has_value() != is_null(rhs)) && (!lhs.has_value() || *lhs == rhs);
231 }
232 template <typename T, typename U>
233 constexpr bool operator!=(const nullable<T>& lhs, const U& rhs) {
234   return (lhs.has_value() == is_null(rhs)) || (lhs.has_value() && *lhs != rhs);
235 }
236 
237 template <typename T, typename U>
238 constexpr bool operator==(const T& lhs, const nullable<U>& rhs) {
239   return (is_null(lhs) != rhs.has_value()) && (!rhs.has_value() || lhs == *rhs);
240 }
241 template <typename T, typename U>
242 constexpr bool operator!=(const T& lhs, const nullable<U>& rhs) {
243   return (is_null(lhs) == rhs.has_value()) || (rhs.has_value() && lhs != *rhs);
244 }
245 
246 }  // namespace fit
247 
248 #endif  // LIB_FIT_NULLABLE_H_
249