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