blob: c50da1a00c114416064464efca4d5705aa10a14b [file] [log] [blame]
// Copyright 2022 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef BASE_TYPES_EXPECTED_INTERNAL_H_
#define BASE_TYPES_EXPECTED_INTERNAL_H_
// IWYU pragma: private, include "base/types/expected.h"
#include <concepts>
#include <functional>
#include <type_traits>
#include <utility>
#include "base/check.h"
#include "third_party/abseil-cpp/absl/types/variant.h"
#include "third_party/abseil-cpp/absl/utility/utility.h"
// This header defines type traits and aliases used for the implementation of
// base::expected.
namespace base {
template <typename T>
class ok;
template <typename E>
class unexpected;
struct unexpect_t {
explicit unexpect_t() = default;
};
// in-place construction of unexpected values
inline constexpr unexpect_t unexpect{};
template <typename T, typename E>
class expected;
namespace internal {
template <typename T>
inline constexpr bool UnderlyingIsOk = false;
template <typename T>
inline constexpr bool UnderlyingIsOk<ok<T>> = true;
template <typename T>
inline constexpr bool IsOk = UnderlyingIsOk<std::remove_cvref_t<T>>;
template <typename T>
inline constexpr bool UnderlyingIsUnexpected = false;
template <typename E>
inline constexpr bool UnderlyingIsUnexpected<unexpected<E>> = true;
template <typename T>
inline constexpr bool IsUnexpected =
UnderlyingIsUnexpected<std::remove_cvref_t<T>>;
template <typename T>
inline constexpr bool UnderlyingIsExpected = false;
template <typename T, typename E>
inline constexpr bool UnderlyingIsExpected<expected<T, E>> = true;
template <typename T>
inline constexpr bool IsExpected = UnderlyingIsExpected<std::remove_cvref_t<T>>;
template <typename T, typename U>
inline constexpr bool IsConstructibleOrConvertible =
std::is_constructible_v<T, U> || std::is_convertible_v<U, T>;
template <typename T, typename U>
inline constexpr bool IsAnyConstructibleOrConvertible =
IsConstructibleOrConvertible<T, U&> ||
IsConstructibleOrConvertible<T, U&&> ||
IsConstructibleOrConvertible<T, const U&> ||
IsConstructibleOrConvertible<T, const U&&>;
// Checks whether a given expected<U, G> can be converted into another
// expected<T, E>. Used inside expected's conversion constructors. UF and GF are
// the forwarded versions of U and G, e.g. UF is const U& for the converting
// copy constructor and U for the converting move constructor. Similarly for GF.
// ExUG is used for convenience, and not expected to be passed explicitly.
// See https://eel.is/c++draft/expected#lib:expected,constructor___
template <typename T,
typename E,
typename UF,
typename GF,
typename ExUG =
expected<std::remove_cvref_t<UF>, std::remove_cvref_t<GF>>>
inline constexpr bool IsValidConversion =
std::is_constructible_v<T, UF> && std::is_constructible_v<E, GF> &&
!IsAnyConstructibleOrConvertible<T, ExUG> &&
!IsAnyConstructibleOrConvertible<unexpected<E>, ExUG>;
// Checks whether a given expected<U, G> can be converted into another
// expected<T, E> when T is a void type. Used inside expected<void>'s conversion
// constructors. GF is the forwarded versions of G, e.g. GF is const G& for the
// converting copy constructor and G for the converting move constructor. ExUG
// is used for convenience, and not expected to be passed explicitly. See
// https://eel.is/c++draft/expected#lib:expected%3cvoid%3e,constructor___
template <typename E,
typename U,
typename GF,
typename ExUG = expected<U, std::remove_cvref_t<GF>>>
inline constexpr bool IsValidVoidConversion =
std::is_void_v<U> && std::is_constructible_v<E, GF> &&
!IsAnyConstructibleOrConvertible<unexpected<E>, ExUG>;
// Checks whether expected<T, E> can be constructed from a value of type U.
template <typename T, typename E, typename U>
inline constexpr bool IsValidValueConstruction =
std::is_constructible_v<T, U> &&
!std::is_same_v<std::remove_cvref_t<U>, absl::in_place_t> &&
!std::is_same_v<std::remove_cvref_t<U>, expected<T, E>> && !IsOk<U> &&
!IsUnexpected<U>;
template <typename T, typename U>
inline constexpr bool IsOkValueConstruction =
!std::is_same_v<std::remove_cvref_t<U>, ok<T>> &&
!std::is_same_v<std::remove_cvref_t<U>, absl::in_place_t> &&
std::is_constructible_v<T, U>;
template <typename T, typename U>
inline constexpr bool IsUnexpectedValueConstruction =
!std::is_same_v<std::remove_cvref_t<U>, unexpected<T>> &&
!std::is_same_v<std::remove_cvref_t<U>, absl::in_place_t> &&
std::is_constructible_v<T, U>;
template <typename T, typename E, typename U>
inline constexpr bool IsValueAssignment =
!std::is_same_v<expected<T, E>, std::remove_cvref_t<U>> && !IsOk<U> &&
!IsUnexpected<U> && std::is_constructible_v<T, U> &&
std::is_assignable_v<T&, U>;
template <typename T, typename E>
class ExpectedImpl {
public:
static constexpr size_t kValIdx = 1;
static constexpr size_t kErrIdx = 2;
static constexpr absl::in_place_index_t<1> kValTag{};
static constexpr absl::in_place_index_t<2> kErrTag{};
template <typename U, typename G>
friend class ExpectedImpl;
constexpr ExpectedImpl() noexcept
requires(std::default_initializable<T>)
: data_(kValTag) {}
constexpr ExpectedImpl(const ExpectedImpl& rhs) noexcept : data_(rhs.data_) {
CHECK(!rhs.is_moved_from());
}
constexpr ExpectedImpl(ExpectedImpl&& rhs) noexcept
: data_(std::move(rhs.data_)) {
CHECK(!rhs.is_moved_from());
rhs.set_is_moved_from();
}
template <typename U, typename G>
constexpr explicit ExpectedImpl(const ExpectedImpl<U, G>& rhs) noexcept {
if (rhs.has_value()) {
emplace_value(rhs.value());
} else {
emplace_error(rhs.error());
}
}
template <typename U, typename G>
constexpr explicit ExpectedImpl(ExpectedImpl<U, G>&& rhs) noexcept {
if (rhs.has_value()) {
emplace_value(std::move(rhs.value()));
} else {
emplace_error(std::move(rhs.error()));
}
rhs.set_is_moved_from();
}
template <typename... Args>
constexpr explicit ExpectedImpl(decltype(kValTag), Args&&... args) noexcept
: data_(kValTag, std::forward<Args>(args)...) {}
template <typename U, typename... Args>
constexpr explicit ExpectedImpl(decltype(kValTag),
std::initializer_list<U> il,
Args&&... args) noexcept
: data_(kValTag, il, std::forward<Args>(args)...) {}
template <typename... Args>
constexpr explicit ExpectedImpl(decltype(kErrTag), Args&&... args) noexcept
: data_(kErrTag, std::forward<Args>(args)...) {}
template <typename U, typename... Args>
constexpr explicit ExpectedImpl(decltype(kErrTag),
std::initializer_list<U> il,
Args&&... args) noexcept
: data_(kErrTag, il, std::forward<Args>(args)...) {}
constexpr ExpectedImpl& operator=(const ExpectedImpl& rhs) noexcept {
CHECK(!rhs.is_moved_from());
data_ = rhs.data_;
return *this;
}
constexpr ExpectedImpl& operator=(ExpectedImpl&& rhs) noexcept {
CHECK(!rhs.is_moved_from());
data_ = std::move(rhs.data_);
rhs.set_is_moved_from();
return *this;
}
template <typename... Args>
constexpr T& emplace_value(Args&&... args) noexcept {
return data_.template emplace<kValIdx>(std::forward<Args>(args)...);
}
template <typename U, typename... Args>
constexpr T& emplace_value(std::initializer_list<U> il,
Args&&... args) noexcept {
return data_.template emplace<kValIdx>(il, std::forward<Args>(args)...);
}
template <typename... Args>
constexpr E& emplace_error(Args&&... args) noexcept {
return data_.template emplace<kErrIdx>(std::forward<Args>(args)...);
}
template <typename U, typename... Args>
constexpr E& emplace_error(std::initializer_list<U> il,
Args&&... args) noexcept {
return data_.template emplace<kErrIdx>(il, std::forward<Args>(args)...);
}
void swap(ExpectedImpl& rhs) noexcept {
CHECK(!is_moved_from());
CHECK(!rhs.is_moved_from());
data_.swap(rhs.data_);
}
constexpr bool has_value() const noexcept {
CHECK(!is_moved_from());
return data_.index() == kValIdx;
}
// Note: No `CHECK()` here and below, since absl::get already checks that
// the passed in index is active.
constexpr T& value() noexcept { return absl::get<kValIdx>(data_); }
constexpr const T& value() const noexcept {
return absl::get<kValIdx>(data_);
}
constexpr E& error() noexcept { return absl::get<kErrIdx>(data_); }
constexpr const E& error() const noexcept {
return absl::get<kErrIdx>(data_);
}
private:
static constexpr size_t kNulIdx = 0;
static_assert(kNulIdx != kValIdx);
static_assert(kNulIdx != kErrIdx);
constexpr bool is_moved_from() const noexcept {
return data_.index() == kNulIdx;
}
constexpr void set_is_moved_from() noexcept {
data_.template emplace<kNulIdx>();
}
absl::variant<absl::monostate, T, E> data_;
};
template <typename Exp, typename F>
constexpr auto AndThen(Exp&& exp, F&& f) noexcept {
using T = std::remove_cvref_t<decltype(exp.value())>;
using E = std::remove_cvref_t<decltype(exp.error())>;
auto invoke_f = [&]() -> decltype(auto) {
if constexpr (!std::is_void_v<T>) {
return std::invoke(std::forward<F>(f), std::forward<Exp>(exp).value());
} else {
return std::invoke(std::forward<F>(f));
}
};
using U = decltype(invoke_f());
static_assert(internal::IsExpected<U>,
"expected<T, E>::and_then: Result of f() must be a "
"specialization of expected");
static_assert(
std::is_same_v<typename U::error_type, E>,
"expected<T, E>::and_then: Result of f() must have E as error_type");
return exp.has_value() ? invoke_f()
: U(unexpect, std::forward<Exp>(exp).error());
}
template <typename Exp, typename F>
constexpr auto OrElse(Exp&& exp, F&& f) noexcept {
using T = std::remove_cvref_t<decltype(exp.value())>;
using G = std::invoke_result_t<F, decltype(std::forward<Exp>(exp).error())>;
static_assert(internal::IsExpected<G>,
"expected<T, E>::or_else: Result of f() must be a "
"specialization of expected");
static_assert(
std::is_same_v<typename G::value_type, T>,
"expected<T, E>::or_else: Result of f() must have T as value_type");
if (!exp.has_value()) {
return std::invoke(std::forward<F>(f), std::forward<Exp>(exp).error());
}
if constexpr (!std::is_void_v<T>) {
return G(std::in_place, std::forward<Exp>(exp).value());
} else {
return G();
}
}
template <typename Exp, typename F>
constexpr auto Transform(Exp&& exp, F&& f) noexcept {
using T = std::remove_cvref_t<decltype(exp.value())>;
using E = std::remove_cvref_t<decltype(exp.error())>;
auto invoke_f = [&]() -> decltype(auto) {
if constexpr (!std::is_void_v<T>) {
return std::invoke(std::forward<F>(f), std::forward<Exp>(exp).value());
} else {
return std::invoke(std::forward<F>(f));
}
};
using U = std::remove_cv_t<decltype(invoke_f())>;
if constexpr (!std::is_void_v<U>) {
static_assert(!std::is_array_v<U>,
"expected<T, E>::transform: Result of f() should "
"not be an Array");
static_assert(!std::is_same_v<U, absl::in_place_t>,
"expected<T, E>::transform: Result of f() should "
"not be absl::in_place_t");
static_assert(!std::is_same_v<U, unexpect_t>,
"expected<T, E>::transform: Result of f() should "
"not be unexpect_t");
static_assert(!internal::IsOk<U>,
"expected<T, E>::transform: Result of f() should "
"not be a specialization of ok");
static_assert(!internal::IsUnexpected<U>,
"expected<T, E>::transform: Result of f() should "
"not be a specialization of unexpected");
static_assert(std::is_object_v<U>,
"expected<T, E>::transform: Result of f() should be "
"an object type");
}
if (!exp.has_value()) {
return expected<U, E>(unexpect, std::forward<Exp>(exp).error());
}
if constexpr (!std::is_void_v<U>) {
return expected<U, E>(std::in_place, invoke_f());
} else {
invoke_f();
return expected<U, E>();
}
}
template <typename Exp, typename F>
constexpr auto TransformError(Exp&& exp, F&& f) noexcept {
using T = std::remove_cvref_t<decltype(exp.value())>;
using G = std::remove_cv_t<
std::invoke_result_t<F, decltype(std::forward<Exp>(exp).error())>>;
static_assert(
!std::is_array_v<G>,
"expected<T, E>::transform_error: Result of f() should not be an Array");
static_assert(!std::is_same_v<G, absl::in_place_t>,
"expected<T, E>::transform_error: Result of f() should not be "
"absl::in_place_t");
static_assert(!std::is_same_v<G, unexpect_t>,
"expected<T, E>::transform_error: Result of f() should not be "
"unexpect_t");
static_assert(!internal::IsOk<G>,
"expected<T, E>::transform_error: Result of f() should not be "
"a specialization of ok");
static_assert(!internal::IsUnexpected<G>,
"expected<T, E>::transform_error: Result of f() should not be "
"a specialization of unexpected");
static_assert(std::is_object_v<G>,
"expected<T, E>::transform_error: Result of f() should be an "
"object type");
if (!exp.has_value()) {
return expected<T, G>(
unexpect,
std::invoke(std::forward<F>(f), std::forward<Exp>(exp).error()));
}
if constexpr (std::is_void_v<T>) {
return expected<T, G>();
} else {
return expected<T, G>(std::in_place, std::forward<Exp>(exp).value());
}
}
} // namespace internal
} // namespace base
#endif // BASE_TYPES_EXPECTED_INTERNAL_H_