/** * \file libimp/expected.h * \author mutouyun (orz@orzz.org) * \brief Provides a way to store either of two values. * \date 2023-02-05 */ #pragma once #include #include // std::exchange #include #include // std::addressof #include // std::nullptr_t #include "libimp/def.h" #include "libimp/uninitialized.h" #include "libimp/generic.h" #include "libimp/byte.h" LIBIMP_NAMESPACE_BEG_ /** * \brief In-place construction tag for unexpected value in expected. * \see https://en.cppreference.com/w/cpp/utility/expected/unexpect_t */ struct unexpected_t { explicit unexpected_t() = default; }; constexpr unexpected_t unexpected {}; /** * \class template expected * \brief Provides a way to store either of two values. * \see https://en.cppreference.com/w/cpp/utility/expected * \tparam T - the type of the expected value. * \tparam E - the type of the unexpected value. */ template class expected; namespace detail_expected { template struct data_union { using const_value_t = typename std::add_const::type; using const_error_t = typename std::add_const::type; union { T value_; ///< the expected value E error_; ///< the unexpected value }; data_union(data_union const &) = delete; data_union &operator=(data_union const &) = delete; data_union(std::nullptr_t) noexcept {} ~data_union() {} template data_union(in_place_t, A &&...args) : value_{std::forward(args)...} {} template data_union(unexpected_t, A &&...args) : error_{std::forward(args)...} {} void destruct_value() noexcept { destroy(&value_); } void destruct_error() noexcept { destroy(&error_); } const_value_t & value() const & noexcept { return value_; } T & value() & noexcept { return value_; } const_value_t &&value() const && noexcept { return std::move(value_); } T && value() && noexcept { return std::move(value_); } const_error_t & error() const & noexcept { return error_; } E & error() & noexcept { return error_; } const_error_t &&error() const && noexcept { return std::move(error_); } E && error() && noexcept { return std::move(error_); } }; template struct data_union { using const_error_t = typename std::add_const::type; alignas(E) std::array error_; ///< the unexpected value data_union(data_union const &) = delete; data_union &operator=(data_union const &) = delete; data_union(std::nullptr_t) noexcept {} template data_union(in_place_t, A &&...) noexcept {} template data_union(unexpected_t, A &&...args) { construct(&error_, std::forward(args)...); } void destruct_value() noexcept {} void destruct_error() noexcept { destroy(reinterpret_cast(&error_)); } const_error_t & error() const & noexcept { return *reinterpret_cast(error_.data()); } E & error() & noexcept { return *reinterpret_cast(error_.data()); } const_error_t &&error() const && noexcept { return std::move(*reinterpret_cast(error_.data())); } E && error() && noexcept { return std::move(*reinterpret_cast(error_.data())); } }; template auto destruct(bool /*has_value*/, data_union &/*data*/) noexcept -> typename std::enable_if::value && std::is_trivially_destructible::value>::type { // Do nothing. } template auto destruct(bool has_value, data_union &data) noexcept -> typename std::enable_if::value && std::is_trivially_destructible::value>::type { if (has_value) data.destruct_value(); } template auto destruct(bool has_value, data_union &data) noexcept -> typename std::enable_if< std::is_trivially_destructible::value && !std::is_trivially_destructible::value>::type { if (!has_value) data.destruct_error(); } template auto destruct(bool has_value, data_union &data) noexcept -> typename std::enable_if::value && !std::is_trivially_destructible::value>::type { if (has_value) { data.destruct_value(); } else { data.destruct_error(); } } template struct value_getter : data_union { using data_union::data_union; template value_getter(U &&other) : data_union(nullptr) { if (other) { construct>(this, in_place, std::forward(other).value()); } else { construct>(this, unexpected, std::forward(other).error()); } } T const *operator->() const noexcept { return std::addressof(this->value()); } T * operator->() noexcept { return std::addressof(this->value()); } T const & operator*() const & noexcept { return this->value(); } T & operator*() & noexcept { return this->value(); } T const &&operator*() const && noexcept { return std::move(this->value()); } T && operator*() && noexcept { return std::move(this->value()); } template T value_or(U &&def) const & { return bool(*static_cast(this)) ? **this : static_cast(std::forward(def)); } template T value_or(U &&def) && { return bool(*static_cast(this)) ? std::move(**this) : static_cast(std::forward(def)); } template T &emplace(A &&...args) { static_cast(this)->reconstruct(in_place, std::forward(args)...); return this->value(); } void swap(S &other) { if (bool(*static_cast(this)) && bool(other)) { std::swap(this->value(), other.value()); } else if (!*static_cast(this) && !other) { std::swap(this->error(), other.error()); } else if (!*static_cast(this) && bool(other)) { E err(std::move(this->error())); this->emplace(std::move(other.value())); other.reconstruct(unexpected, std::move(err)); } else /*if (bool(*this) && !other)*/ { E err(std::move(other.error())); other.emplace(std::move(this->value())); static_cast(this)->reconstruct(unexpected, std::move(err)); } } }; template struct value_getter : data_union { using data_union::data_union; template value_getter(U &&other) : data_union(nullptr) { if (other) { construct>(this, in_place); } else { construct>(this, unexpected, std::forward(other).error()); } } void emplace() noexcept { static_cast(this)->reconstruct(in_place); } void swap(S &other) { if (bool(*static_cast(this)) && bool(other)) { return; } else if (!*static_cast(this) && !other) { std::swap(this->error(), other.error()); } else if (!*static_cast(this) && bool(other)) { E err(std::move(this->error())); this->emplace(); other.reconstruct(unexpected, std::move(err)); } else /*if (bool(*this) && !other)*/ { E err(std::move(other.error())); other.emplace(); static_cast(this)->reconstruct(unexpected, std::move(err)); } } }; /** * \brief Define the expected storage. */ template struct storage : value_getter, T, E> { using getter_t = value_getter, T, E>; bool has_value_; template storage(in_place_t, A &&...args) : getter_t(in_place, std::forward(args)...) , has_value_(true) {} template storage(unexpected_t, A &&...args) : getter_t(unexpected, std::forward(args)...) , has_value_(false) {} storage(storage const &other) : getter_t(other) , has_value_(other.has_value_) {} storage(storage &&other) : getter_t(std::move(other)) /// After construction, has_value() is equal to other.has_value(). , has_value_(other.has_value_) {} template storage(storage const &other) : getter_t(other) , has_value_(other.has_value_) {} template storage(storage &&other) : getter_t(std::move(other)) /// After construction, has_value() is equal to other.has_value(). , has_value_(other.has_value_) {} bool has_value() const noexcept { return has_value_; } explicit operator bool() const noexcept { return this->has_value(); } protected: friend getter_t; template void reconstruct(A &&...args) { destroy(this); construct(this, std::forward(args)...); } }; /// \brief The invoke forwarding helper. template auto invoke(F &&f, A &&...args) noexcept( noexcept(std::forward(f)(std::forward(args)...))) -> decltype(std::forward(f)(std::forward(args)...)) { return std::forward(f)(std::forward(args)...); } template auto invoke(F &&f, A &&...args) noexcept( noexcept(std::forward(f)())) -> decltype(std::forward(f)()) { return std::forward(f)(); } /// \brief and_then helper. template (), *std::declval()))> R and_then(E &&exp, F &&f) { static_assert(is_specialized::value, "F must return an `expected`."); return bool(exp) ? invoke(std::forward(f), *std::forward(exp)) : R(unexpected, std::forward(exp).error()); } /// \brief or_else helper. template (), std::declval().error()))> R or_else(E &&exp, F &&f) { static_assert(is_specialized::value, "F must return an `expected`."); return bool(exp) ? std::forward(exp) : invoke(std::forward(f), std::forward(exp).error()); } } // namespace detail_expected /** * \class template expected * \brief Provides a way to store either of two values. */ template class expected : public detail_expected::storage::type, E> { public: using value_type = typename std::remove_cv::type; using error_type = E; using detail_expected::storage::storage; expected(expected const &) = default; expected(expected &&) = default; expected() : detail_expected::storage(in_place) {} expected &operator=(expected other) { this->swap(other); return *this; } // Monadic operations template auto and_then(F &&f) & { return detail_expected::and_then(*this, std::forward(f)); } template auto and_then(F &&f) const & { return detail_expected::and_then(*this, std::forward(f)); } template auto and_then(F &&f) && { return detail_expected::and_then(std::move(*this), std::forward(f)); } template auto or_else(F &&f) & { return detail_expected::or_else(*this, std::forward(f)); } template auto or_else(F &&f) const & { return detail_expected::or_else(*this, std::forward(f)); } template auto or_else(F &&f) && { return detail_expected::or_else(std::move(*this), std::forward(f)); } }; // Compares template bool operator==(expected const &lhs, expected const &rhs) { return (lhs.has_value() == rhs.has_value()) && (lhs.has_value() ? *lhs == *rhs : lhs.error() == rhs.error()); } template bool operator==(expected const &lhs, expected const &rhs) { return (lhs.has_value() == rhs.has_value()) && (lhs.has_value() || lhs.error() == rhs.error()); } template bool operator!=(expected const &lhs, expected const &rhs) { return !(lhs == rhs); } LIBIMP_NAMESPACE_END_