blob: 8162db64d15cbaea26ec39e9740ed917efcbee49 [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_OPTIONAL_H_
#define LIB_FIT_OPTIONAL_H_
#if defined(__cplusplus) && __cplusplus >= 201703L && !defined(FORCE_FIT_OPTIONAL)
// In C++17 fit::optional should simply be an alias for std::optional.
#include <optional>
namespace fit {
using std::make_optional;
using std::nullopt;
using std::nullopt_t;
using std::optional;
} // namespace fit
#else
#include <exception>
#include <new>
#include <type_traits>
#include <utility>
#include "constructors_internal.h"
#include "in_place_internal.h"
#include "storage_internal.h"
#include "traits.h"
#include "utility_internal.h"
namespace fit {
// A sentinel value for indicating that it contains no value.
struct nullopt_t {
explicit constexpr nullopt_t(int) {}
};
static constexpr nullopt_t nullopt{0};
// Exception type to report bad accesses to optional.
class bad_optional_access : public std::exception {
public:
bad_optional_access() noexcept {}
const char* what() const noexcept override { return reason_; }
private:
template <typename T>
friend class optional;
bad_optional_access(const char* reason) noexcept : reason_{reason} {}
// String describing the reason for the bad access. Must point to a string
// with static storage duration.
const char* reason_;
};
// A reasonably complete implementation of std::optional compatible with C++14.
//
// See also fit::nullable<T>, which may be more efficient in certain
// circumstances when T can be initialized, assigned, and compared with nullptr.
//
template <typename T>
class optional : private ::fit::internal::modulate_copy_and_move<T> {
private:
// Helper types and values for SFINAE and noexcept rules.
static constexpr bool nothrow_move_constructible = std::is_nothrow_move_constructible<T>::value;
static constexpr bool nothrow_swappable = std::is_nothrow_move_constructible<T>::value &&
::fit::internal::is_nothrow_swappable<T>::value;
static constexpr auto trivial_init_v = ::fit::internal::trivial_init_v;
static constexpr auto maybe_init_v = ::fit::internal::maybe_init_v;
using type_tag = ::fit::internal::type_tag<T>;
template <typename U, typename V>
using converts_from_optional = disjunction<
std::is_constructible<U, const optional<V>&>, std::is_constructible<U, optional<V>&>,
std::is_constructible<U, const optional<V>&&>, std::is_constructible<U, optional<V>&&>,
std::is_convertible<const optional<V>&, U>, std::is_convertible<optional<V>&, U>,
std::is_convertible<const optional<V>&&, U>, std::is_convertible<optional<V>&&, U>>;
template <typename U, typename V>
using assigns_from_optional =
disjunction<std::is_assignable<U&, const optional<V>&>, std::is_assignable<U&, optional<V>&>,
std::is_assignable<U&, const optional<V>&&>,
std::is_assignable<U&, optional<V>&&>>;
template <typename U>
using not_self_type = ::fit::internal::not_same_type<optional, U>;
template <typename U>
using not_in_place = ::fit::internal::not_same_type<in_place_t, U>;
template <typename... Conditions>
using requires_conditions = ::fit::internal::requires_conditions<Conditions...>;
template <typename... Conditions>
using assignment_requires_conditions =
::fit::internal::assignment_requires_conditions<optional&, Conditions...>;
template <typename... Args>
using emplace_constructible = std::enable_if_t<std::is_constructible<T, Args...>::value, T&>;
[[noreturn]] static constexpr void throw_bad_optional_access(const char* reason) {
#if __cpp_exceptions
throw bad_optional_access(reason);
#else
(void)reason;
__builtin_abort();
#endif
}
public:
using value_type = T;
// Default constructors.
constexpr optional() = default;
constexpr optional(nullopt_t) noexcept {}
// Copy/move constructors and assignment operators.
constexpr optional(const optional&) = default;
constexpr optional& operator=(const optional&) = default;
constexpr optional(optional&&) = default;
constexpr optional& operator=(optional&&) = default;
// Converting constructors.
template <typename U = T,
requires_conditions<not_self_type<U>, not_in_place<U>, std::is_constructible<T, U&&>,
std::is_convertible<U&&, T>> = true>
constexpr optional(U&& value) : storage_(type_tag{}, std::forward<U>(value)) {}
template <typename U = T,
requires_conditions<not_self_type<U>, not_in_place<U>, std::is_constructible<T, U&&>,
negation<std::is_convertible<U&&, T>>> = false>
explicit constexpr optional(U&& value) : storage_{type_tag{}, std::forward<U>(value)} {}
template <typename U,
requires_conditions<negation<std::is_same<T, U>>, std::is_constructible<T, const U&>,
std::is_convertible<const U&, T>,
negation<converts_from_optional<T, U>>> = true>
constexpr optional(const optional<U>& other) : storage_{maybe_init_v, other.storage_} {}
template <typename U,
requires_conditions<negation<std::is_same<T, U>>, std::is_constructible<T, const U&>,
negation<std::is_convertible<const U&, T>>,
negation<converts_from_optional<T, U>>> = false>
explicit constexpr optional(const optional<U>& other) : storage_{maybe_init_v, other.storage_} {}
template <typename U,
requires_conditions<negation<std::is_same<T, U>>, std::is_constructible<T, U&&>,
std::is_convertible<U&&, T>,
negation<converts_from_optional<T, U>>> = true>
constexpr optional(optional<U>&& other) : storage_{maybe_init_v, std::move(other.storage_)} {}
template <typename U,
requires_conditions<negation<std::is_same<T, U>>, std::is_constructible<T, U&&>,
negation<std::is_convertible<U&&, T>>,
negation<converts_from_optional<T, U>>> = false>
explicit constexpr optional(optional<U>&& other)
: storage_{maybe_init_v, std::move(other.storage_)} {}
template <typename... Args, requires_conditions<std::is_constructible<T, Args&&...>> = false>
explicit constexpr optional(in_place_t, Args&&... args)
: storage_(type_tag{}, std::forward<Args>(args)...) {}
template <
typename U, typename... Args,
requires_conditions<std::is_constructible<T, std::initializer_list<U>&, Args&&...>> = false>
explicit constexpr optional(in_place_t, std::initializer_list<U> init_list, Args&&... args)
: storage_(type_tag{}, init_list, std::forward<Args>(args)...) {}
// Destructor.
~optional() = default;
// Checked accessors.
constexpr T& value() & {
if (has_value()) {
return storage_.get(type_tag{});
} else {
throw_bad_optional_access("Accessed value of empty optional!");
}
}
constexpr const T& value() const& {
if (has_value()) {
return storage_.get(type_tag{});
} else {
throw_bad_optional_access("Accessed value of empty optional!");
}
}
constexpr T&& value() && {
if (has_value()) {
return std::move(storage_.get(type_tag{}));
} else {
throw_bad_optional_access("Accessed value of empty optional!");
}
}
constexpr const T&& value() const&& {
if (has_value()) {
return std::move(storage_.get(type_tag{}));
} else {
throw_bad_optional_access("Accessed value of empty optional!");
}
}
template <typename U>
constexpr T value_or(U&& default_value) const& {
static_assert(std::is_copy_constructible<T>::value,
"value_or() requires copy-constructible value_type!");
static_assert(std::is_convertible<U&&, T>::value,
"Default value must be convertible to value_type!");
return has_value() ? storage_.get(type_tag{}) : static_cast<T>(std::forward<U>(default_value));
}
template <typename U>
constexpr T value_or(U&& default_value) && {
static_assert(std::is_move_constructible<T>::value,
"value_or() requires move-constructible value_type!");
static_assert(std::is_convertible<U&&, T>::value,
"Default value must be convertible to value_type!");
return has_value() ? std::move(storage_.get(type_tag{}))
: static_cast<T>(std::forward<U>(default_value));
}
// Unchecked accessors.
constexpr T* operator->() { return std::addressof(storage_.get(type_tag{})); }
constexpr const T* operator->() const { return std::addressof(storage_.get(type_tag{})); }
constexpr T& operator*() { return storage_.get(type_tag{}); }
constexpr const T& operator*() const { return storage_.get(type_tag{}); }
// Availability accessors/operators.
constexpr bool has_value() const { return storage_.has_value(); }
constexpr explicit operator bool() const { return has_value(); }
// Assignment operators.
template <typename U>
constexpr assignment_requires_conditions<
not_self_type<U>, negation<conjunction<std::is_scalar<T>, std::is_same<T, std::decay_t<U>>>>,
std::is_constructible<T, U>, std::is_assignable<T&, U>>
operator=(U&& value) {
if (has_value()) {
storage_.get(type_tag{}) = std::forward<U>(value);
} else {
storage_.construct(type_tag{}, std::forward<U>(value));
}
return *this;
}
template <typename U>
constexpr assignment_requires_conditions<
negation<std::is_same<T, U>>, std::is_constructible<T, const U&>, std::is_assignable<T&, U>,
negation<converts_from_optional<T, U>>, negation<assigns_from_optional<T, U>>>
operator=(const optional<U>& other) {
storage_.assign(other.storage_);
return *this;
}
template <typename U>
constexpr assignment_requires_conditions<
negation<std::is_same<T, U>>, std::is_constructible<T, U>, std::is_assignable<T&, U>,
negation<converts_from_optional<T, U>>, negation<assigns_from_optional<T, U>>>
operator=(optional<U>&& other) {
storage_.assign(std::move(other.storage_));
return *this;
}
constexpr optional& operator=(nullopt_t) {
storage_.reset();
return *this;
}
// Swap.
constexpr void swap(optional& other) noexcept(nothrow_swappable) {
storage_.swap(other.storage_);
}
// Emplacement.
template <typename... Args>
constexpr emplace_constructible<Args&&...> emplace(Args&&... args) {
storage_.reset();
storage_.construct(type_tag{}, std::forward<Args>(args)...);
return storage_.get(type_tag{});
}
template <typename U, typename... Args>
constexpr emplace_constructible<std::initializer_list<U>&, Args&&...> emplace(
std::initializer_list<U> init_list, Args&&... args) {
storage_.reset();
storage_.construct(type_tag{}, init_list, std::forward<Args>(args)...);
return storage_.get(type_tag{});
}
// Reset.
void reset() noexcept { storage_.reset(); }
private:
::fit::internal::storage_type<T> storage_;
};
// Swap.
template <typename T>
inline std::enable_if_t<(std::is_move_constructible<T>::value &&
::fit::internal::is_swappable<T>::value)>
swap(optional<T>& a, optional<T>& b) noexcept(noexcept(a.swap(b))) {
a.swap(b);
}
template <typename T>
inline std::enable_if_t<(!std::is_move_constructible<T>::value &&
::fit::internal::is_swappable<T>::value)>
swap(optional<T>& a, optional<T>& b) = delete;
// Make optional.
template <typename T>
constexpr optional<std::decay_t<T>> make_optional(T&& value) {
return optional<std::decay_t<T>>{std::forward<T>(value)};
}
template <typename T, typename... Args>
constexpr optional<T> make_optional(Args&&... args) {
return optional<T>{in_place, std::forward<Args>(args)...};
}
template <typename T, typename U, typename... Args>
constexpr optional<T> make_optional(std::initializer_list<U> init_list, Args&&... args) {
return optional<T>{in_place, init_list, std::forward<Args>(args)...};
}
// Empty.
template <typename T>
constexpr bool operator==(const optional<T>& lhs, nullopt_t) {
return !lhs.has_value();
}
template <typename T>
constexpr bool operator!=(const optional<T>& lhs, nullopt_t) {
return lhs.has_value();
}
template <typename T>
constexpr bool operator==(nullopt_t, const optional<T>& rhs) {
return !rhs.has_value();
}
template <typename T>
constexpr bool operator!=(nullopt_t, const optional<T>& rhs) {
return rhs.has_value();
}
// Equal/not equal.
template <typename T, typename U,
::fit::internal::enable_relop_t<decltype(std::declval<T>() == std::declval<U>())> = true>
constexpr bool operator==(const optional<T>& lhs, const optional<U>& rhs) {
return (lhs.has_value() == rhs.has_value()) && (!lhs.has_value() || *lhs == *rhs);
}
template <typename T, typename U,
::fit::internal::enable_relop_t<decltype(std::declval<T>() != std::declval<U>())> = true>
constexpr bool operator!=(const optional<T>& lhs, const optional<U>& rhs) {
return (lhs.has_value() != rhs.has_value()) || (lhs.has_value() && *lhs != *rhs);
}
template <typename T, typename U,
::fit::internal::enable_relop_t<decltype(std::declval<T>() == std::declval<U>()),
::fit::internal::not_same_type<nullopt_t, U>> = true>
constexpr bool operator==(const optional<T>& lhs, const U& rhs) {
return lhs.has_value() && *lhs == rhs;
}
template <typename T, typename U,
::fit::internal::enable_relop_t<decltype(std::declval<T>() != std::declval<U>()),
::fit::internal::not_same_type<nullopt_t, U>> = true>
constexpr bool operator!=(const optional<T>& lhs, const U& rhs) {
return !lhs.has_value() || *lhs != rhs;
}
template <typename T, typename U,
::fit::internal::enable_relop_t<decltype(std::declval<T>() == std::declval<U>()),
::fit::internal::not_same_type<nullopt_t, T>> = true>
constexpr bool operator==(const T& lhs, const optional<U>& rhs) {
return rhs.has_value() && lhs == *rhs;
}
template <typename T, typename U,
::fit::internal::enable_relop_t<decltype(std::declval<T>() != std::declval<U>()),
::fit::internal::not_same_type<nullopt_t, T>> = true>
constexpr bool operator!=(const T& lhs, const optional<U>& rhs) {
return !rhs.has_value() || lhs != *rhs;
}
// Less than/greater than.
template <typename T, typename U,
::fit::internal::enable_relop_t<decltype(std::declval<T>() < std::declval<U>())> = true>
constexpr bool operator<(const optional<T>& lhs, const optional<U>& rhs) {
return rhs.has_value() && (!lhs.has_value() || *lhs < *rhs);
}
template <typename T, typename U,
::fit::internal::enable_relop_t<decltype(std::declval<T>() > std::declval<U>())> = true>
constexpr bool operator>(const optional<T>& lhs, const optional<U>& rhs) {
return lhs.has_value() && (!rhs.has_value() || *lhs > *rhs);
}
template <typename T, typename U,
::fit::internal::enable_relop_t<decltype(std::declval<T>() < std::declval<U>()),
::fit::internal::not_same_type<nullopt_t, U>> = true>
constexpr bool operator<(const optional<T>& lhs, const U& rhs) {
return !lhs.has_value() || *lhs < rhs;
}
template <typename T, typename U,
::fit::internal::enable_relop_t<decltype(std::declval<T>() > std::declval<U>()),
::fit::internal::not_same_type<nullopt_t, U>> = true>
constexpr bool operator>(const optional<T>& lhs, const U& rhs) {
return lhs.has_value() && *lhs > rhs;
}
template <typename T, typename U,
::fit::internal::enable_relop_t<decltype(std::declval<T>() < std::declval<U>()),
::fit::internal::not_same_type<nullopt_t, T>> = true>
constexpr bool operator<(const T& lhs, const optional<U>& rhs) {
return rhs.has_value() && lhs < *rhs;
}
template <typename T, typename U,
::fit::internal::enable_relop_t<decltype(std::declval<T>() > std::declval<U>()),
::fit::internal::not_same_type<nullopt_t, T>> = true>
constexpr bool operator>(const T& lhs, const optional<U>& rhs) {
return !rhs.has_value() || lhs > *rhs;
}
// Less than or equal/greater than or equal.
template <typename T, typename U,
::fit::internal::enable_relop_t<decltype(std::declval<T>() <= std::declval<U>())> = true>
constexpr bool operator<=(const optional<T>& lhs, const optional<U>& rhs) {
return !lhs.has_value() || (rhs.has_value() && *lhs <= *rhs);
}
template <typename T, typename U,
::fit::internal::enable_relop_t<decltype(std::declval<T>() >= std::declval<U>())> = true>
constexpr bool operator>=(const optional<T>& lhs, const optional<U>& rhs) {
return !rhs.has_value() || (lhs.has_value() && *lhs >= *rhs);
}
template <typename T, typename U,
::fit::internal::enable_relop_t<decltype(std::declval<T>() <= std::declval<U>()),
::fit::internal::not_same_type<nullopt_t, U>> = true>
constexpr bool operator<=(const optional<T>& lhs, const U& rhs) {
return !lhs.has_value() || *lhs <= rhs;
}
template <typename T, typename U,
::fit::internal::enable_relop_t<decltype(std::declval<T>() >= std::declval<U>()),
::fit::internal::not_same_type<nullopt_t, U>> = true>
constexpr bool operator>=(const optional<T>& lhs, const U& rhs) {
return lhs.has_value() && *lhs >= rhs;
}
template <typename T, typename U,
::fit::internal::enable_relop_t<decltype(std::declval<T>() <= std::declval<U>()),
::fit::internal::not_same_type<nullopt_t, T>> = true>
constexpr bool operator<=(const T& lhs, const optional<U>& rhs) {
return rhs.has_value() && lhs <= *rhs;
}
template <typename T, typename U,
::fit::internal::enable_relop_t<decltype(std::declval<T>() >= std::declval<U>()),
::fit::internal::not_same_type<nullopt_t, T>> = true>
constexpr bool operator>=(const T& lhs, const optional<U>& rhs) {
return !rhs.has_value() || lhs >= *rhs;
}
} // namespace fit
#endif
#endif // LIB_FIT_OPTIONAL_H_