mirror of
https://github.com/Naios/continuable.git
synced 2026-01-01 03:12:12 +08:00
Use the flat_variant for the expected implementation
This commit is contained in:
parent
c66e9a8ee1
commit
916ea3c04d
@ -58,7 +58,7 @@ using std::experimental::coroutine_handle;
|
||||
/// for waiting on a continuable in a stackless coroutine.
|
||||
template <typename Continuable>
|
||||
class awaitable {
|
||||
using trait_t = util::expected_result_trait_t<Continuable>;
|
||||
using trait_t = container::expected_result_trait_t<Continuable>;
|
||||
|
||||
/// The continuable which is invoked upon suspension
|
||||
Continuable continuable_;
|
||||
|
||||
@ -31,231 +31,75 @@
|
||||
#ifndef CONTINUABLE_DETAIL_EXPECTED_HPP_INCLUDED
|
||||
#define CONTINUABLE_DETAIL_EXPECTED_HPP_INCLUDED
|
||||
|
||||
#include <cassert>
|
||||
#include <memory>
|
||||
#include <type_traits>
|
||||
#include <utility>
|
||||
|
||||
#include <continuable/detail/flat-variant.hpp>
|
||||
#include <continuable/detail/hints.hpp>
|
||||
#include <continuable/detail/traits.hpp>
|
||||
#include <continuable/detail/types.hpp>
|
||||
|
||||
namespace cti {
|
||||
namespace detail {
|
||||
namespace util {
|
||||
namespace detail {
|
||||
enum class slot_t { empty, value, error };
|
||||
|
||||
template <typename T>
|
||||
using storage_of_t = //
|
||||
std::aligned_storage_t<
|
||||
(sizeof(types::error_type) > sizeof(T) ? sizeof(types::error_type)
|
||||
: sizeof(T)),
|
||||
(alignof(types::error_type) > alignof(T) ? alignof(types::error_type)
|
||||
: alignof(T))>;
|
||||
|
||||
template <typename T>
|
||||
struct expected_base {
|
||||
storage_of_t<T> storage_;
|
||||
slot_t slot_;
|
||||
|
||||
constexpr expected_base() : slot_(slot_t::empty) {
|
||||
}
|
||||
|
||||
expected_base(expected_base const&) noexcept {
|
||||
}
|
||||
expected_base(expected_base&&) noexcept {
|
||||
}
|
||||
expected_base& operator=(expected_base const&) {
|
||||
return *this;
|
||||
}
|
||||
expected_base& operator=(expected_base&&) {
|
||||
return *this;
|
||||
}
|
||||
};
|
||||
|
||||
template <typename Base>
|
||||
struct expected_move_base {
|
||||
constexpr expected_move_base() = default;
|
||||
|
||||
expected_move_base(expected_move_base const&) = default;
|
||||
explicit expected_move_base(expected_move_base&& right) {
|
||||
Base& me = *static_cast<Base*>(this);
|
||||
Base& other = *static_cast<Base*>(&right);
|
||||
assert(!other.is_empty());
|
||||
|
||||
#ifndef _NDEBUG
|
||||
me.set(slot_t::empty);
|
||||
#endif
|
||||
|
||||
other.visit([&](auto&& value) {
|
||||
// ...
|
||||
me.init(std::move(value));
|
||||
});
|
||||
me.set(other.get());
|
||||
other.destroy();
|
||||
}
|
||||
expected_move_base& operator=(expected_move_base const&) = default;
|
||||
expected_move_base& operator=(expected_move_base&& right) {
|
||||
Base& me = *static_cast<Base*>(this);
|
||||
Base& other = *static_cast<Base*>(&right);
|
||||
assert(!other.is_empty());
|
||||
|
||||
me.weak_destroy();
|
||||
|
||||
other.visit([&](auto&& value) {
|
||||
// ...
|
||||
me.init(std::move(value));
|
||||
});
|
||||
me.set(other.get());
|
||||
other.destroy();
|
||||
return *this;
|
||||
}
|
||||
};
|
||||
template <typename Base, bool IsCopyable /*= true*/>
|
||||
struct expected_copy_base : expected_move_base<Base> {
|
||||
constexpr expected_copy_base() = default;
|
||||
|
||||
expected_copy_base(expected_copy_base&&) = default;
|
||||
explicit expected_copy_base(expected_copy_base const& right)
|
||||
: expected_move_base<Base>()
|
||||
// TODO noexcept(Base::is_nothrow_move_constructible)
|
||||
{
|
||||
Base& me = *static_cast<Base*>(this);
|
||||
Base const& other = *static_cast<Base const*>(&right);
|
||||
assert(!other.is_empty());
|
||||
|
||||
#ifndef _NDEBUG
|
||||
me.set(slot_t::empty);
|
||||
#endif
|
||||
|
||||
other.visit([&](auto&& value) {
|
||||
// ...
|
||||
me.init(std::move(value));
|
||||
});
|
||||
me.set(other.get());
|
||||
}
|
||||
expected_copy_base& operator=(expected_copy_base&&) = default;
|
||||
expected_copy_base& operator=(expected_copy_base const& right)
|
||||
// TODO noexcept(Base::is_nothrow_move_constructible)
|
||||
{
|
||||
Base& me = *static_cast<Base*>(this);
|
||||
Base const& other = *static_cast<Base const*>(&right);
|
||||
assert(!other.is_empty());
|
||||
|
||||
me.weak_destroy();
|
||||
|
||||
other.visit([&](auto&& value) {
|
||||
// ...
|
||||
me.init(std::move(value));
|
||||
});
|
||||
me.set(other.get());
|
||||
return *this;
|
||||
}
|
||||
};
|
||||
template <typename Base /*, bool IsCopyable = false*/>
|
||||
struct expected_copy_base<Base, false> : expected_move_base<Base> {
|
||||
constexpr expected_copy_base() = default;
|
||||
|
||||
expected_copy_base(expected_copy_base const&) = delete;
|
||||
explicit expected_copy_base(expected_copy_base&& right) = default;
|
||||
expected_copy_base& operator=(expected_copy_base const&) = delete;
|
||||
expected_copy_base& operator=(expected_copy_base&& right) = default;
|
||||
};
|
||||
} // namespace detail
|
||||
|
||||
namespace container {
|
||||
/// A class similar to the one in the expected proposal,
|
||||
/// however it is capable of carrying an exception_ptr if
|
||||
/// exceptions are used.
|
||||
template <typename T>
|
||||
class expected
|
||||
: detail::expected_copy_base<
|
||||
expected<T>, std::is_copy_constructible<types::error_type>::value &&
|
||||
std::is_copy_constructible<T>::value>,
|
||||
detail::expected_base<T> {
|
||||
|
||||
template <typename>
|
||||
friend class expected;
|
||||
template <typename>
|
||||
friend struct detail::expected_move_base;
|
||||
template <typename, bool>
|
||||
friend struct detail::expected_copy_base;
|
||||
|
||||
template <typename V>
|
||||
expected(V&& value, detail::slot_t const slot) {
|
||||
using type = std::decay_t<decltype(value)>;
|
||||
new (&this->storage_) type(std::forward<V>(value));
|
||||
set(slot);
|
||||
}
|
||||
class expected {
|
||||
flat_variant<T, types::error_type> variant_;
|
||||
|
||||
public:
|
||||
constexpr expected() = default;
|
||||
expected(expected const&) = default;
|
||||
expected(expected&&) = default;
|
||||
explicit expected() = default;
|
||||
explicit expected(expected const&) = default;
|
||||
explicit expected(expected&&) = default;
|
||||
expected& operator=(expected const&) = default;
|
||||
expected& operator=(expected&&) = default;
|
||||
~expected() = default;
|
||||
|
||||
~expected() noexcept(
|
||||
std::is_nothrow_destructible<T>::value&&
|
||||
std::is_nothrow_destructible<types::error_type>::value) {
|
||||
weak_destroy();
|
||||
explicit expected(T value) : variant_(std::move(value)) {
|
||||
}
|
||||
|
||||
explicit expected(T value) //
|
||||
: expected(std::move(value), detail::slot_t::value) {
|
||||
}
|
||||
explicit expected(types::error_type error) //
|
||||
: expected(std::move(error), detail::slot_t::error) {
|
||||
explicit expected(types::error_type exception)
|
||||
: variant_(std::move(exception)) {
|
||||
}
|
||||
|
||||
expected& operator=(T value) {
|
||||
set_value(std::move(value));
|
||||
variant_ = std::move(value);
|
||||
return *this;
|
||||
}
|
||||
expected& operator=(types::error_type error) {
|
||||
set_exception(std::move(error));
|
||||
expected& operator=(types::error_type exception) {
|
||||
variant_ = std::move(exception);
|
||||
return *this;
|
||||
}
|
||||
|
||||
void set_value(T value) {
|
||||
variant_ = std::move(value);
|
||||
}
|
||||
void set_exception(types::error_type exception) {
|
||||
variant_ = std::move(exception);
|
||||
}
|
||||
|
||||
bool is_value() const noexcept {
|
||||
assert(!is_empty());
|
||||
return is(detail::slot_t::value);
|
||||
return variant_.template is<T>();
|
||||
}
|
||||
bool is_exception() const noexcept {
|
||||
assert(!is_empty());
|
||||
return is(detail::slot_t::error);
|
||||
return variant_.template is<types::error_type>();
|
||||
}
|
||||
|
||||
explicit constexpr operator bool() const noexcept {
|
||||
return is_value();
|
||||
}
|
||||
|
||||
void set_value(T value) {
|
||||
weak_destroy();
|
||||
init(std::move(value));
|
||||
set(detail::slot_t::value);
|
||||
}
|
||||
void set_exception(types::error_type error) {
|
||||
weak_destroy();
|
||||
init(std::move(error));
|
||||
set(detail::slot_t::error);
|
||||
}
|
||||
|
||||
T& get_value() noexcept {
|
||||
assert(is_value());
|
||||
return cast<T>();
|
||||
return variant_.template cast<T>();
|
||||
}
|
||||
T const& get_value() const noexcept {
|
||||
assert(is_value());
|
||||
return cast<T>();
|
||||
return variant_.template cast<T>();
|
||||
}
|
||||
types::error_type& get_exception() noexcept {
|
||||
assert(is_exception());
|
||||
return cast<types::error_type>();
|
||||
return variant_.template cast<types::error_type>();
|
||||
}
|
||||
types::error_type const& get_exception() const noexcept {
|
||||
assert(is_exception());
|
||||
return cast<types::error_type>();
|
||||
return variant_.template cast<types::error_type>();
|
||||
}
|
||||
|
||||
T& operator*() noexcept {
|
||||
@ -264,80 +108,6 @@ public:
|
||||
T const& operator*() const noexcept {
|
||||
return get_value();
|
||||
}
|
||||
|
||||
private:
|
||||
template <typename V>
|
||||
void visit(V&& visitor) {
|
||||
switch (this->slot_) {
|
||||
case detail::slot_t::value:
|
||||
return std::forward<V>(visitor)(cast<T>());
|
||||
case detail::slot_t::error:
|
||||
return std::forward<V>(visitor)(cast<types::error_type>());
|
||||
default:
|
||||
// We don't visit when there is no value
|
||||
break;
|
||||
}
|
||||
}
|
||||
template <typename V>
|
||||
void visit(V&& visitor) const {
|
||||
switch (this->slot_) {
|
||||
case detail::slot_t::value:
|
||||
return std::forward<V>(visitor)(cast<T>());
|
||||
case detail::slot_t::error:
|
||||
return std::forward<V>(visitor)(cast<types::error_type>());
|
||||
default:
|
||||
// We don't visit when there is no value
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
bool is_empty() const noexcept {
|
||||
return is(detail::slot_t::empty);
|
||||
}
|
||||
|
||||
template <typename V>
|
||||
V& cast() noexcept {
|
||||
assert(!is_empty());
|
||||
return *reinterpret_cast<V*>(&this->storage_);
|
||||
}
|
||||
template <typename V>
|
||||
V const& cast() const noexcept {
|
||||
assert(!is_empty());
|
||||
return *reinterpret_cast<V const*>(&this->storage_);
|
||||
}
|
||||
|
||||
template <typename V>
|
||||
void init(V&& value) {
|
||||
assert(is_empty());
|
||||
using type = std::decay_t<decltype(value)>;
|
||||
new (&this->storage_) type(std::forward<V>(value));
|
||||
}
|
||||
void destroy() {
|
||||
weak_destroy();
|
||||
|
||||
#ifdef NDEBUG
|
||||
set(detail::slot_t::empty);
|
||||
#endif
|
||||
}
|
||||
void weak_destroy() {
|
||||
visit([&](auto&& value) {
|
||||
using type = std::decay_t<decltype(value)>;
|
||||
value.~type();
|
||||
});
|
||||
|
||||
#ifndef NDEBUG
|
||||
set(detail::slot_t::empty);
|
||||
#endif
|
||||
}
|
||||
detail::slot_t get() const noexcept {
|
||||
return this->slot_;
|
||||
}
|
||||
bool is(detail::slot_t const slot) const noexcept {
|
||||
return get() == slot;
|
||||
}
|
||||
void set(detail::slot_t const slot) {
|
||||
this->slot_ = slot;
|
||||
}
|
||||
};
|
||||
|
||||
namespace detail {
|
||||
@ -387,7 +157,7 @@ struct expected_result_trait<traits::identity<First, Second, Rest...>> {
|
||||
template <typename Continuable>
|
||||
using expected_result_trait_t = detail::expected_result_trait<decltype(
|
||||
hints::hint_of(traits::identify<Continuable>{}))>;
|
||||
} // namespace util
|
||||
} // namespace container
|
||||
} // namespace detail
|
||||
} // namespace cti
|
||||
|
||||
|
||||
@ -137,7 +137,7 @@ struct flat_variant_copy_base : flat_variant_move_base<Base> {
|
||||
|
||||
flat_variant_copy_base(flat_variant_copy_base&&) = default;
|
||||
explicit flat_variant_copy_base(flat_variant_copy_base const& right)
|
||||
: flat_variant_copy_base<Base>()
|
||||
: flat_variant_move_base<Base>()
|
||||
// TODO noexcept(Base::is_nothrow_move_constructible)
|
||||
{
|
||||
Base& me = *static_cast<Base*>(this);
|
||||
@ -189,6 +189,14 @@ using every = traits::conjunction<Predicate<T>...>;
|
||||
|
||||
/// A class similar to the one in the variant proposal,
|
||||
/// however it is capable of carrying an empty state by default.
|
||||
template <typename... T>
|
||||
class flat_variant;
|
||||
|
||||
template <typename T>
|
||||
struct is_flat_variant : std::false_type {};
|
||||
template <typename... T>
|
||||
struct is_flat_variant<flat_variant<T...>> : std::true_type {};
|
||||
|
||||
template <typename... T>
|
||||
class flat_variant
|
||||
: detail::flat_variant_copy_base<
|
||||
@ -222,17 +230,21 @@ public:
|
||||
weak_destroy();
|
||||
}
|
||||
|
||||
template <typename V, std::size_t Index =
|
||||
traits::index_of_t<std::decay_t<V>, T...>::value>
|
||||
// Since the flat_variant is never a part of the contained
|
||||
// values itself this overload is safed against the linted issue.
|
||||
template <
|
||||
typename V,
|
||||
std::enable_if_t<!is_flat_variant<std::decay_t<V>>::value>* = nullptr,
|
||||
std::size_t Index = traits::index_of_t<std::decay_t<V>, T...>::value>
|
||||
// Since the flat_variant isn't allowed through SFINAE
|
||||
// this overload is safed against the linted issue.
|
||||
// NOLINTNEXTLINE(misc-forwarding-reference-overload)
|
||||
explicit flat_variant(V&& value)
|
||||
: flat_variant(std::forward<V>(value), Index) {
|
||||
}
|
||||
|
||||
template <typename V, std::size_t Index =
|
||||
traits::index_of_t<std::decay_t<V>, T...>::value>
|
||||
template <
|
||||
typename V,
|
||||
std::enable_if_t<!is_flat_variant<std::decay_t<V>>::value>* = nullptr,
|
||||
std::size_t Index = traits::index_of_t<std::decay_t<V>, T...>::value>
|
||||
flat_variant& operator=(V&& value) {
|
||||
weak_destroy();
|
||||
init(std::forward<V>(value));
|
||||
|
||||
@ -30,7 +30,7 @@
|
||||
#include <test-continuable.hpp>
|
||||
|
||||
using cti::detail::types::error_type;
|
||||
using cti::detail::util::expected;
|
||||
using cti::detail::container::expected;
|
||||
|
||||
static int const CANARY = 373671;
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user