blob: 91eeb3d1e17d6d290c34c48a2a33391f10445d1e [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 {};
// Returns true if a value equals nullptr.
template <typename T, typename Comparable = bool>
struct is_null_predicate {
constexpr bool operator()(const T& value) { return false; }
};
template <typename T>
struct is_null_predicate<T, decltype(std::declval<const T&>() == nullptr)> {
// This test is intended to work for all types that are comparable with
// nullptr. Sometimes, the compiler knows that the value can never equal
// nullptr and it may complain that the comparison is always false.
// For example, this is the case for a function type or a captureless
// lambda closure. It's possible to use template selection to match
// some of these cases but not all, so just suppress the warning.
// The compiler will optimize away the always-false comparison.
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Waddress"
constexpr bool operator()(const T& value) { return value == nullptr; }
#pragma GCC diagnostic pop
};
template <typename T>
constexpr inline bool is_null(const T& value) {
return is_null_predicate<T>()(value);
}
// 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; })
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;
constexpr nullable() = default;
explicit constexpr nullable(decltype(nullptr)) {}
explicit constexpr nullable(T value)
: opt_(std::move(value)) {}
nullable(const nullable& other) = default;
nullable(nullable&& other) = default;
~nullable() = 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(); }
nullable& operator=(const nullable& other) = default;
nullable& operator=(nullable&& other) = default;
nullable& operator=(decltype(nullptr)) {
reset();
return *this;
}
nullable& operator=(T value) {
opt_ = std::move(value);
return *this;
}
void reset() { opt_.reset(); }
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)) {}
nullable(const nullable& other) = default;
nullable(nullable&& other)
: value_(std::move(other.value_)) {
other.value_ = nullptr;
}
~nullable() = default;
constexpr T& value() & {
assert(has_value());
return value_;
}
constexpr const T& value() const& {
assert(has_value());
return value_;
}
constexpr T&& value() && {
assert(has_value());
return std::move(value_);
}
constexpr const T&& value() const&& {
assert(has_value());
return std::move(value_);
}
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(); }
nullable& operator=(const nullable& other) = default;
nullable& operator=(nullable&& other) {
if (&other == this)
return *this;
value_ = std::move(other.value_);
other.value_ = nullptr;
return *this;
}
nullable& operator=(decltype(nullptr)) {
reset();
return *this;
}
nullable& operator=(T value) {
value_ = std::move(value);
return *this;
}
void reset() { value_ = nullptr; }
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_