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