Use the flat_variant for the expected implementation

This commit is contained in:
Denis Blank 2018-03-08 19:29:31 +01:00
parent c66e9a8ee1
commit 916ea3c04d
4 changed files with 49 additions and 267 deletions

View File

@ -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_;

View File

@ -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

View File

@ -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));

View File

@ -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;