• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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<T, decltype(nullptr)>::value &&
45                                               std::is_assignable<T&, decltype(nullptr)>::value &&
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(fxbug.dev/4681): 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<T, T&&>::value &&
66                               std::is_assignable<T&, T&&>::value)>
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     } else {
135       PW_ASSERT(false);
136     }
137   }
138   constexpr const T& value() const& {
139     if (has_value()) {
140       return value_;
141     } else {
142       PW_ASSERT(false);
143     }
144   }
145   constexpr T&& value() && {
146     if (has_value()) {
147       return std::move(value_);
148     } else {
149       PW_ASSERT(false);
150     }
151   }
152   constexpr const T&& value() const&& {
153     if (has_value()) {
154       return std::move(value_);
155     } else {
156       PW_ASSERT(false);
157     }
158   }
159 
160   template <typename U = T>
161   constexpr T value_or(U&& default_value) const {
162     return has_value() ? value_ : static_cast<T>(std::forward<U>(default_value));
163   }
164 
165   constexpr T* operator->() { return &value_; }
166   constexpr const T* operator->() const { return &value_; }
167   constexpr T& operator*() { return value_; }
168   constexpr const T& operator*() const { return value_; }
169 
170   constexpr bool has_value() const { return !(value_ == nullptr); }
171   explicit constexpr operator bool() const { return has_value(); }
172 
173   constexpr nullable& operator=(const nullable& other) = default;
174   constexpr nullable& operator=(nullable&& other) {
175     value_ = std::move(other.value_);
176     return *this;
177   }
178 
179   constexpr nullable& operator=(decltype(nullptr)) {
180     reset();
181     return *this;
182   }
183 
184   constexpr nullable& operator=(T value) {
185     value_ = std::move(value);
186     return *this;
187   }
188 
189   constexpr void reset() { value_ = nullptr; }
190 
191   constexpr void swap(nullable& other) {
192     using std::swap;
193     swap(value_, other.value_);
194   }
195 
196  private:
197   T value_;
198 };
199 
200 template <typename T>
201 void swap(nullable<T>& a, nullable<T>& b) {
202   a.swap(b);
203 }
204 
205 template <typename T>
206 constexpr bool operator==(const nullable<T>& lhs, decltype(nullptr)) {
207   return !lhs.has_value();
208 }
209 template <typename T>
210 constexpr bool operator!=(const nullable<T>& lhs, decltype(nullptr)) {
211   return lhs.has_value();
212 }
213 
214 template <typename T>
215 constexpr bool operator==(decltype(nullptr), const nullable<T>& rhs) {
216   return !rhs.has_value();
217 }
218 template <typename T>
219 constexpr bool operator!=(decltype(nullptr), const nullable<T>& rhs) {
220   return rhs.has_value();
221 }
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 template <typename T, typename U>
228 constexpr bool operator!=(const nullable<T>& lhs, const nullable<U>& rhs) {
229   return (lhs.has_value() != rhs.has_value()) || (lhs.has_value() && *lhs != *rhs);
230 }
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 template <typename T, typename U>
237 constexpr bool operator!=(const nullable<T>& lhs, const U& rhs) {
238   return (lhs.has_value() == is_null(rhs)) || (lhs.has_value() && *lhs != rhs);
239 }
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 template <typename T, typename U>
246 constexpr bool operator!=(const T& lhs, const nullable<U>& rhs) {
247   return (is_null(lhs) == rhs.has_value()) || (rhs.has_value() && lhs != *rhs);
248 }
249 
250 }  // namespace fit
251 
252 #endif  // LIB_FIT_NULLABLE_H_
253