blob: ae18f260ca19e8961d6e7cec54fc0098b298481c [file] [log] [blame]
// 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(CF-806): 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_