266 lines
8.4 KiB
C++
266 lines
8.4 KiB
C++
// 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/4681): 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
|