diff --git a/include/libipc/imp/expected.h b/include/libipc/imp/expected.h new file mode 100644 index 0000000..e5f7820 --- /dev/null +++ b/include/libipc/imp/expected.h @@ -0,0 +1,392 @@ +/** + * \file libimp/expected.h + * \author mutouyun (orz@orzz.org) + * \brief Provides a way to store either of two values. + */ +#pragma once + +#include +#include // std::exchange +#include +#include // std::addressof +#include // std::nullptr_t + +#include "libipc/imp/uninitialized.h" +#include "libipc/imp/generic.h" +#include "libipc/imp/byte.h" + +namespace ipc { + +/** + * \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); +} + +} // namespace ipc diff --git a/test/imp/test_imp_expected.cpp b/test/imp/test_imp_expected.cpp new file mode 100644 index 0000000..cb96e54 --- /dev/null +++ b/test/imp/test_imp_expected.cpp @@ -0,0 +1,142 @@ + +#include "test.h" + +#include "libipc/imp/expected.h" + +namespace { + +class test_val { +public: + int val = 0; + int dc_ = 0; + int cc_ = 0; + int mv_ = 0; + + test_val() { + dc_ += 1; + std::printf("test_val construct.\n"); + } + test_val(test_val const &o) + : val(o.val) { + cc_ = o.cc_ + 1; + std::printf("test_val copy construct.\n"); + } + test_val(test_val &&o) noexcept + : val(std::exchange(o.val, 0)) { + mv_ = o.mv_ + 1; + std::printf("test_val move construct.\n"); + } + ~test_val() { + std::printf("test_val destruct.\n"); + } + + test_val &operator=(test_val &&o) noexcept { + mv_ = o.mv_ + 1; + val = std::exchange(o.val, 0); + return *this; + } + + test_val(int v) + : val(v) { + std::printf("test_val value initialization.\n"); + } + + bool operator==(test_val const &rhs) const { + return val == rhs.val; + } +}; + +class test_err { +public: + int dc_ = 0; + int cc_ = 0; + int mv_ = 0; + std::int64_t val = 0; + + test_err() { + dc_ += 1; + std::printf("test_err construct.\n"); + } + test_err(test_err const &o) + : val(o.val) { + cc_ = o.cc_ + 1; + std::printf("test_err copy construct.\n"); + } + test_err(test_err &&o) noexcept + : val(std::exchange(o.val, 0)) { + mv_ = o.mv_ + 1; + std::printf("test_err move construct.\n"); + } + ~test_err() { + std::printf("test_err destruct.\n"); + } + + test_err &operator=(test_err &&o) noexcept { + mv_ = o.mv_ + 1; + val = std::exchange(o.val, 0); + return *this; + } + + test_err(int v) + : val(v) { + std::printf("test_err value initialization.\n"); + } + + bool operator==(test_err const &rhs) const { + return val == rhs.val; + } +}; + +} // namespace + +TEST(expected, in_place) { + ipc::expected e1; + EXPECT_TRUE(e1); + EXPECT_EQ(e1.value().dc_, 1); + EXPECT_EQ(e1.value().val, 0); + + ipc::expected e2 {ipc::in_place, 123}; + EXPECT_TRUE(e2); + EXPECT_EQ(e2.value().dc_, 0); + EXPECT_EQ(e2.value().val, 123); +} + +TEST(expected, unexpected) { + ipc::expected e1 {ipc::unexpected}; + EXPECT_FALSE(e1); + EXPECT_EQ(e1.error().dc_, 1); + EXPECT_EQ(e1.error().val, 0); + + ipc::expected e2 {ipc::unexpected, 321}; + EXPECT_FALSE(e2); + EXPECT_EQ(e2.error().dc_, 0); + EXPECT_EQ(e2.error().val, 321); +} + +TEST(expected, copy_and_move) { + ipc::expected e1 {ipc::in_place, 123}; + ipc::expected e2 {e1}; + EXPECT_TRUE(e1); + EXPECT_TRUE(e2); + EXPECT_EQ(e1, e2); + EXPECT_EQ(e2.value().cc_, 1); + EXPECT_EQ(e2.value().val, 123); + + ipc::expected e3 {ipc::unexpected, 333}; + ipc::expected e4 {e3}; + EXPECT_FALSE(e3); + EXPECT_FALSE(e4); + EXPECT_EQ(e3, e4); + EXPECT_EQ(e4.error().cc_, 1); + EXPECT_EQ(e4.error().val, 333); + + ipc::expected e5; + e5 = e1; + EXPECT_EQ(e1, e5); + EXPECT_EQ(e5.value().val, 123); + + ipc::expected e6; + e6 = std::move(e5); + EXPECT_EQ(e1, e6); + EXPECT_EQ(e5.value().val, 0); +}