blob: 6d4c060455654a5c7ee9f3623d4b9de45974d437 [file] [log] [blame]
// Copyright 2021 The Android Open Source Project
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// 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.
#pragma once
#include <assert.h>
#include <optional>
#include <type_traits>
#include <utility>
namespace android::base {
namespace fit {
// Determines whether a type can be compared with nullptr.
template <typename T, typename Comparable = bool>
struct IsComparableWithNull : public std::false_type {};
template <typename T>
struct IsComparableWithNull<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<IsComparableWithNull<T>::value, bool> = true>
constexpr inline bool isNull(T&& value) {
return std::forward<T>(value) == nullptr;
}
#pragma GCC diagnostic pop
template <typename T, std::enable_if_t<!IsComparableWithNull<T>::value, bool> = false>
constexpr inline bool isNull(T&&) {
return false;
}
// Determines whether a type can be initialized, assigned, and compared
// with nullptr.
template <typename T>
struct IsNullable
: public std::integral_constant<bool,
std::is_constructible<T, decltype(nullptr)>::value &&
std::is_assignable<T&, decltype(nullptr)>::value &&
IsComparableWithNull<T>::value> {};
template <>
struct IsNullable<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/42123486): fit::nullable does not precisely mirror
// std::optional. This should be corrected to avoid surprises when switching
// between the types.
template <typename T,
bool = (IsNullable<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) : mOpt(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 mOpt.value(); }
constexpr const T& value() const& { return mOpt.value(); }
constexpr T&& value() && { return std::move(mOpt.value()); }
constexpr const T&& value() const&& { return std::move(mOpt.value()); }
template <typename U = T>
constexpr T valueOr(U&& default_value) const {
return mOpt.value_or(std::forward<U>(default_value));
}
constexpr T* operator->() { return &*mOpt; }
constexpr const T* operator->() const { return &*mOpt; }
constexpr T& operator*() { return *mOpt; }
constexpr const T& operator*() const { return *mOpt; }
constexpr bool hasValue() const { return mOpt.has_value(); }
explicit constexpr operator bool() const { return hasValue(); }
constexpr Nullable& operator=(decltype(nullptr)) {
reset();
return *this;
}
constexpr Nullable& operator=(T value) {
mOpt = std::move(value);
return *this;
}
constexpr void reset() { mOpt.reset(); }
constexpr void swap(Nullable& other) { mOpt.swap(other.mOpt); }
private:
std::optional<T> mOpt;
};
template <typename T>
class Nullable<T, true> final {
public:
using value_type = T;
constexpr Nullable() : mValue(nullptr) {}
explicit constexpr Nullable(decltype(nullptr)) : mValue(nullptr) {}
explicit constexpr Nullable(T value) : mValue(std::move(value)) {}
constexpr Nullable(const Nullable& other) = default;
constexpr Nullable(Nullable&& other) : mValue(std::move(other.value_)) {}
~Nullable() = default;
constexpr T& value() & {
if (hasValue()) {
return mValue;
} else {
__builtin_abort();
}
}
constexpr const T& value() const& {
if (hasValue()) {
return mValue;
} else {
__builtin_abort();
}
}
constexpr T&& value() && {
if (hasValue()) {
return std::move(mValue);
} else {
__builtin_abort();
}
}
constexpr const T&& value() const&& {
if (hasValue()) {
return std::move(mValue);
} else {
__builtin_abort();
}
}
template <typename U = T>
constexpr T valueOr(U&& default_value) const {
return hasValue() ? mValue : static_cast<T>(std::forward<U>(default_value));
}
constexpr T* operator->() { return &mValue; }
constexpr const T* operator->() const { return &mValue; }
constexpr T& operator*() { return mValue; }
constexpr const T& operator*() const { return mValue; }
constexpr bool hasValue() const { return !(mValue == nullptr); }
explicit constexpr operator bool() const { return hasValue(); }
constexpr Nullable& operator=(const Nullable& other) = default;
constexpr Nullable& operator=(Nullable&& other) {
mValue = std::move(other.value_);
return *this;
}
constexpr Nullable& operator=(decltype(nullptr)) {
reset();
return *this;
}
constexpr Nullable& operator=(T value) {
mValue = std::move(value);
return *this;
}
constexpr void reset() { mValue = nullptr; }
constexpr void swap(Nullable& other) {
using std::swap;
swap(mValue, other.value_);
}
private:
T mValue;
};
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.hasValue();
}
template <typename T>
constexpr bool operator!=(const Nullable<T>& lhs, decltype(nullptr)) {
return lhs.hasValue();
}
template <typename T>
constexpr bool operator==(decltype(nullptr), const Nullable<T>& rhs) {
return !rhs.hasValue();
}
template <typename T>
constexpr bool operator!=(decltype(nullptr), const Nullable<T>& rhs) {
return rhs.hasValue();
}
template <typename T, typename U>
constexpr bool operator==(const Nullable<T>& lhs, const Nullable<U>& rhs) {
return (lhs.hasValue() == rhs.hasValue()) && (!lhs.hasValue() || *lhs == *rhs);
}
template <typename T, typename U>
constexpr bool operator!=(const Nullable<T>& lhs, const Nullable<U>& rhs) {
return (lhs.hasValue() != rhs.hasValue()) || (lhs.hasValue() && *lhs != *rhs);
}
template <typename T, typename U>
constexpr bool operator==(const Nullable<T>& lhs, const U& rhs) {
return (lhs.hasValue() != isNull(rhs)) && (!lhs.hasValue() || *lhs == rhs);
}
template <typename T, typename U>
constexpr bool operator!=(const Nullable<T>& lhs, const U& rhs) {
return (lhs.hasValue() == isNull(rhs)) || (lhs.hasValue() && *lhs != rhs);
}
template <typename T, typename U>
constexpr bool operator==(const T& lhs, const Nullable<U>& rhs) {
return (isNull(lhs) != rhs.hasValue()) && (!rhs.hasValue() || lhs == *rhs);
}
template <typename T, typename U>
constexpr bool operator!=(const T& lhs, const Nullable<U>& rhs) {
return (isNull(lhs) == rhs.hasValue()) || (rhs.hasValue() && lhs != *rhs);
}
} // namespace fit
} // namespace android::base