| // Copyright 2018 The Fuchsia Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #ifndef LIB_FIT_NULLABLE_H_ |
| #define LIB_FIT_NULLABLE_H_ |
| |
| #include <assert.h> |
| |
| #include <type_traits> |
| #include <utility> |
| |
| #include "optional.h" |
| |
| namespace fit { |
| |
| // Determines whether a type can be compared with nullptr. |
| template <typename T, typename Comparable = bool> |
| struct is_comparable_with_null : public std::false_type {}; |
| template <typename T> |
| struct is_comparable_with_null<T, decltype(std::declval<const T&>() == nullptr)> |
| : public std::true_type {}; |
| |
| // Suppress the warning when the compiler can see that a nullable value is |
| // never equal to nullptr. |
| #pragma GCC diagnostic push |
| #pragma GCC diagnostic ignored "-Waddress" |
| template <typename T, std::enable_if_t<is_comparable_with_null<T>::value, bool> = true> |
| constexpr inline bool is_null(T&& value) { |
| return std::forward<T>(value) == nullptr; |
| } |
| #pragma GCC diagnostic pop |
| |
| template <typename T, std::enable_if_t<!is_comparable_with_null<T>::value, bool> = false> |
| constexpr inline bool is_null(T&&) { |
| return false; |
| } |
| |
| // Determines whether a type can be initialized, assigned, and compared |
| // with nullptr. |
| template <typename T> |
| struct is_nullable |
| : public std::integral_constant<bool, std::is_constructible<T, decltype(nullptr)>::value && |
| std::is_assignable<T&, decltype(nullptr)>::value && |
| is_comparable_with_null<T>::value> {}; |
| template <> |
| struct is_nullable<void> : public std::false_type {}; |
| |
| // Holds a value or nullptr. |
| // |
| // This class is similar to |std::optional<T>| except that it uses less |
| // storage when the value type can be initialized, assigned, and compared |
| // with nullptr. |
| // |
| // For example: |
| // - sizeof(fit::nullable<void*>) == sizeof(void*) |
| // - sizeof(std::optional<void*>) == sizeof(struct { bool; void*; }) |
| // - sizeof(fit::nullable<int>) == sizeof(struct { bool; int; }) |
| // - sizeof(std::optional<int>) == sizeof(struct { bool; int; }) |
| // |
| // TODO(fxbug.dev/4681): fit::nullable does not precisely mirror fit::optional now that |
| // fit::optional is closer to standards compliant. This should be corrected to |
| // avoid surprises when switching between the types. |
| template <typename T, bool = (is_nullable<T>::value && std::is_constructible<T, T&&>::value && |
| std::is_assignable<T&, T&&>::value)> |
| class nullable final { |
| public: |
| using value_type = T; |
| |
| ~nullable() = default; |
| constexpr nullable() = default; |
| |
| explicit constexpr nullable(decltype(nullptr)) {} |
| explicit constexpr nullable(T value) : opt_(std::move(value)) {} |
| |
| constexpr nullable(const nullable& other) = default; |
| constexpr nullable& operator=(const nullable& other) = default; |
| |
| constexpr nullable(nullable&& other) = default; |
| constexpr nullable& operator=(nullable&& other) = default; |
| |
| constexpr T& value() & { return opt_.value(); } |
| constexpr const T& value() const& { return opt_.value(); } |
| constexpr T&& value() && { return std::move(opt_.value()); } |
| constexpr const T&& value() const&& { return std::move(opt_.value()); } |
| |
| template <typename U = T> |
| constexpr T value_or(U&& default_value) const { |
| return opt_.value_or(std::forward<U>(default_value)); |
| } |
| |
| constexpr T* operator->() { return &*opt_; } |
| constexpr const T* operator->() const { return &*opt_; } |
| constexpr T& operator*() { return *opt_; } |
| constexpr const T& operator*() const { return *opt_; } |
| |
| constexpr bool has_value() const { return opt_.has_value(); } |
| explicit constexpr operator bool() const { return has_value(); } |
| |
| constexpr nullable& operator=(decltype(nullptr)) { |
| reset(); |
| return *this; |
| } |
| |
| constexpr nullable& operator=(T value) { |
| opt_ = std::move(value); |
| return *this; |
| } |
| |
| constexpr void reset() { opt_.reset(); } |
| |
| constexpr void swap(nullable& other) { opt_.swap(other.opt_); } |
| |
| private: |
| optional<T> opt_; |
| }; |
| |
| template <typename T> |
| class nullable<T, true> final { |
| public: |
| using value_type = T; |
| |
| constexpr nullable() : value_(nullptr) {} |
| explicit constexpr nullable(decltype(nullptr)) : value_(nullptr) {} |
| explicit constexpr nullable(T value) : value_(std::move(value)) {} |
| constexpr nullable(const nullable& other) = default; |
| constexpr nullable(nullable&& other) : value_(std::move(other.value_)) {} |
| ~nullable() = default; |
| |
| constexpr T& value() & { |
| if (has_value()) { |
| return value_; |
| } else { |
| __builtin_abort(); |
| } |
| } |
| constexpr const T& value() const& { |
| if (has_value()) { |
| return value_; |
| } else { |
| __builtin_abort(); |
| } |
| } |
| constexpr T&& value() && { |
| if (has_value()) { |
| return std::move(value_); |
| } else { |
| __builtin_abort(); |
| } |
| } |
| constexpr const T&& value() const&& { |
| if (has_value()) { |
| return std::move(value_); |
| } else { |
| __builtin_abort(); |
| } |
| } |
| |
| template <typename U = T> |
| constexpr T value_or(U&& default_value) const { |
| return has_value() ? value_ : static_cast<T>(std::forward<U>(default_value)); |
| } |
| |
| constexpr T* operator->() { return &value_; } |
| constexpr const T* operator->() const { return &value_; } |
| constexpr T& operator*() { return value_; } |
| constexpr const T& operator*() const { return value_; } |
| |
| constexpr bool has_value() const { return !(value_ == nullptr); } |
| explicit constexpr operator bool() const { return has_value(); } |
| |
| constexpr nullable& operator=(const nullable& other) = default; |
| constexpr nullable& operator=(nullable&& other) { |
| value_ = std::move(other.value_); |
| return *this; |
| } |
| |
| constexpr nullable& operator=(decltype(nullptr)) { |
| reset(); |
| return *this; |
| } |
| |
| constexpr nullable& operator=(T value) { |
| value_ = std::move(value); |
| return *this; |
| } |
| |
| constexpr void reset() { value_ = nullptr; } |
| |
| constexpr void swap(nullable& other) { |
| using std::swap; |
| swap(value_, other.value_); |
| } |
| |
| private: |
| T value_; |
| }; |
| |
| template <typename T> |
| void swap(nullable<T>& a, nullable<T>& b) { |
| a.swap(b); |
| } |
| |
| template <typename T> |
| constexpr bool operator==(const nullable<T>& lhs, decltype(nullptr)) { |
| return !lhs.has_value(); |
| } |
| template <typename T> |
| constexpr bool operator!=(const nullable<T>& lhs, decltype(nullptr)) { |
| return lhs.has_value(); |
| } |
| |
| template <typename T> |
| constexpr bool operator==(decltype(nullptr), const nullable<T>& rhs) { |
| return !rhs.has_value(); |
| } |
| template <typename T> |
| constexpr bool operator!=(decltype(nullptr), const nullable<T>& rhs) { |
| return rhs.has_value(); |
| } |
| |
| template <typename T, typename U> |
| constexpr bool operator==(const nullable<T>& lhs, const nullable<U>& rhs) { |
| return (lhs.has_value() == rhs.has_value()) && (!lhs.has_value() || *lhs == *rhs); |
| } |
| template <typename T, typename U> |
| constexpr bool operator!=(const nullable<T>& lhs, const nullable<U>& rhs) { |
| return (lhs.has_value() != rhs.has_value()) || (lhs.has_value() && *lhs != *rhs); |
| } |
| |
| template <typename T, typename U> |
| constexpr bool operator==(const nullable<T>& lhs, const U& rhs) { |
| return (lhs.has_value() != is_null(rhs)) && (!lhs.has_value() || *lhs == rhs); |
| } |
| template <typename T, typename U> |
| constexpr bool operator!=(const nullable<T>& lhs, const U& rhs) { |
| return (lhs.has_value() == is_null(rhs)) || (lhs.has_value() && *lhs != rhs); |
| } |
| |
| template <typename T, typename U> |
| constexpr bool operator==(const T& lhs, const nullable<U>& rhs) { |
| return (is_null(lhs) != rhs.has_value()) && (!rhs.has_value() || lhs == *rhs); |
| } |
| template <typename T, typename U> |
| constexpr bool operator!=(const T& lhs, const nullable<U>& rhs) { |
| return (is_null(lhs) == rhs.has_value()) || (rhs.has_value() && lhs != *rhs); |
| } |
| |
| } // namespace fit |
| |
| #endif // LIB_FIT_NULLABLE_H_ |