Replace the result<...> implementation by a lighter one

* result<...> only moveable now, the conditional copy capabilities were removed
* If you require the optional copy capability use a std::variant implementation of your choice
* Closes #13
This commit is contained in:
Denis Blank 2020-04-04 20:41:32 +02:00
parent 5f8b2aa317
commit 1e39bd85dd
8 changed files with 283 additions and 634 deletions

View File

@ -34,8 +34,8 @@
#include <type_traits>
#include <utility>
#include <continuable/continuable-primitives.hpp>
#include <continuable/detail/utility/flat-variant.hpp>
#include <continuable/detail/utility/result-trait.hpp>
#include <continuable/detail/utility/result-variant.hpp>
#include <continuable/detail/utility/traits.hpp>
#include <continuable/detail/utility/util.hpp>
@ -49,6 +49,11 @@ namespace cti {
/// or was cancelled.
/// \{
/// A tag which represents present void values in result.
///
/// \since 4.0.0
using void_arg_t = detail::void_arg_t;
/// A class which is convertible to any \ref result and that definitely holds no
/// value so the real result gets invalidated when this object is passed to it.
///
@ -137,43 +142,53 @@ public:
template <typename... T>
class result {
using trait_t = detail::result_trait<T...>;
using surrogate_t = typename trait_t::surrogate_t;
template <typename... Args>
explicit result(detail::init_arg_t, Args&&... values)
: variant_(trait_t::wrap(std::forward<Args>(values)...)) {}
explicit result(detail::init_arg_t, exception_t exception)
: variant_(std::move(exception)) {}
explicit result(detail::init_result_arg_t arg, Args&&... values)
: variant_(arg, trait_t::wrap(std::forward<Args>(values)...)) {}
explicit result(detail::init_exception_arg_t arg, exception_t exception)
: variant_(arg, std::move(exception)) {}
public:
using value_t = typename trait_t::value_t;
using value_placeholder_t = typename trait_t::surrogate_t;
template <typename FirstArg, typename... Args>
explicit result(FirstArg&& first, Args&&... values)
: variant_(trait_t::wrap(std::forward<FirstArg>(first),
: variant_(detail::init_result_arg_t{},
trait_t::wrap(std::forward<FirstArg>(first),
std::forward<Args>(values)...)) {}
result() = default;
result(result const&) = default;
result(result const&) = delete;
result(result&&) = default;
result& operator=(result const&) = default;
result& operator=(result const&) = delete;
result& operator=(result&&) = default;
~result() = default;
explicit result(exception_t exception)
: variant_(std::move(exception)) {}
: variant_(detail::init_exception_arg_t{}, std::move(exception)) {}
/* implicit */ result(empty_result) {}
/* implicit */ result(exceptional_result exceptional_result)
: variant_(std::move(exceptional_result.get_exception())) {}
: variant_(detail::init_exception_arg_t{},
std::move(exceptional_result.get_exception())) {}
/* implicit */ result(cancellation_result)
: variant_(exception_t{}) {}
: variant_(detail::init_exception_arg_t{}, exception_t{}) {}
result& operator=(empty_result) {
set_empty();
variant_.set_empty();
return *this;
}
result& operator=(exceptional_result exceptional_result) {
set_exception(std::move(exceptional_result.get_exception()));
result& operator=(value_placeholder_t value) {
variant_.set_value(std::move(value));
return *this;
}
result& operator=(exceptional_result exception) {
variant_.set_exception(std::move(exception.get_exception()));
return *this;
}
result& operator=(cancellation_result) {
variant_.set_exception({});
return *this;
}
@ -183,15 +198,15 @@ public:
}
/// Set the result to a the state which holds the corresponding value
void set_value(T... values) {
variant_ = trait_t::wrap(std::move(values)...);
variant_.set_value(trait_t::wrap(std::move(values)...));
}
/// Set the result into a state which holds the corresponding exception
void set_exception(exception_t exception) {
variant_ = std::move(exception);
variant_.set_exception(std::move(exception));
}
/// Set the result into a state which holds the cancellation token
void set_canceled() {
variant_ = exception_t{};
variant_.set_exception(exception_t{});
}
/// Returns true if the state of the result is empty
@ -200,11 +215,11 @@ public:
}
/// Returns true if the state of the result holds the result
bool is_value() const noexcept {
return variant_.template is<surrogate_t>();
return variant_.is_value();
}
/// Returns true if the state of the result holds a present exception
bool is_exception() const noexcept {
return variant_.template is<exception_t>();
return variant_.is_exception();
}
/// \copydoc is_value
@ -215,15 +230,15 @@ public:
/// Returns the values of the result, if the result doesn't hold the value
/// the behaviour is undefined but will assert in debug mode.
decltype(auto) get_value() & noexcept {
return trait_t::unwrap(variant_.template cast<surrogate_t>());
return trait_t::unwrap(variant_.get_value());
}
///\copydoc get_value
decltype(auto) get_value() const& noexcept {
return trait_t::unwrap(variant_.template cast<surrogate_t>());
return trait_t::unwrap(variant_.get_value());
}
///\copydoc get_value
decltype(auto) get_value() && noexcept {
return trait_t::unwrap(std::move(variant_).template cast<surrogate_t>());
return trait_t::unwrap(std::move(variant_.get_value()));
}
///\copydoc get_value
@ -236,30 +251,30 @@ public:
}
///\copydoc get_value
decltype(auto) operator*() && noexcept {
return std::move(*this).get_value();
return std::move(variant_.get_value());
}
/// Returns the exception of the result, if the result doesn't hold an
/// exception the behaviour is undefined but will assert in debug mode.
exception_t& get_exception() & noexcept {
return variant_.template cast<exception_t>();
return variant_.get_exception();
}
/// \copydoc get_exception
exception_t const& get_exception() const& noexcept {
return variant_.template cast<exception_t>();
return variant_.get_exception();
}
/// \copydoc get_exception
exception_t&& get_exception() && noexcept {
return std::move(variant_).template cast<exception_t>();
return std::move(variant_.get_exception());
}
/// Creates a present result from the given values
static result from(T... values) {
return result{detail::init_arg_t{}, std::move(values)...};
return result{detail::init_result_arg_t{}, std::move(values)...};
}
/// Creates a present result from the given exception
static result from(exception_arg_t, exception_t exception) {
return result{detail::init_arg_t{}, std::move(exception)};
return result{detail::init_exception_arg_t{}, std::move(exception)};
}
/// Creates an empty result
@ -268,7 +283,7 @@ public:
}
private:
detail::container::flat_variant<surrogate_t, exception_t> variant_;
detail::result_variant<value_placeholder_t> variant_;
};
/// Returns the value at position I of the given result

View File

@ -38,7 +38,6 @@
#include <continuable/continuable-result.hpp>
#include <continuable/continuable-traverse.hpp>
#include <continuable/detail/core/base.hpp>
#include <continuable/detail/utility/flat-variant.hpp>
#include <continuable/detail/utility/traits.hpp>
namespace cti {
@ -55,22 +54,25 @@ namespace connection {
/// - single async value -> single value
/// - multiple async value -> tuple of async values.
namespace aggregated {
/// Guards a type to be default constructible,
/// and wraps it into an optional type if it isn't default constructible.
template <typename T>
using lazy_value_t = std::conditional_t<std::is_default_constructible<T>::value,
T, container::flat_variant<T>>;
T, result<T>>;
template <typename T>
decltype(auto) unpack_lazy(T&& value) {
decltype(auto) unpack_lazy(std::true_type /*is_default_constructible*/,
T&& value) {
return std::forward<T>(value);
}
template <typename T>
T&& unpack_lazy(container::flat_variant<T>&& value) {
assert(value.template is<T>() &&
T&& unpack_lazy(std::false_type /*is_default_constructible*/,
result<T>&& value) {
assert(value.is_value() &&
"The connection was finalized before all values were present!");
return std::move(value.template cast<T>());
return std::move(value).get_value();
}
template <typename Continuable>
@ -82,8 +84,7 @@ class continuable_box<continuable_base<Data, identity<>>> {
public:
explicit continuable_box(continuable_base<Data, identity<>>&& continuable)
: continuable_(std::move(continuable)) {
}
: continuable_(std::move(continuable)) {}
auto const& peek() const {
return continuable_;
@ -93,13 +94,13 @@ public:
return std::move(continuable_);
}
void assign() {
}
void assign() {}
auto unbox() && {
return spread_this();
}
};
template <typename Data, typename First>
class continuable_box<continuable_base<Data, identity<First>>> {
@ -109,8 +110,7 @@ class continuable_box<continuable_base<Data, identity<First>>> {
public:
explicit continuable_box(
continuable_base<Data, identity<First>>&& continuable)
: continuable_(std::move(continuable)) {
}
: continuable_(std::move(continuable)) {}
auto const& peek() const {
return continuable_;
@ -125,7 +125,8 @@ public:
}
auto unbox() && {
return unpack_lazy(std::move(first_));
return unpack_lazy(std::is_default_constructible<First>{},
std::move(first_));
}
};
template <typename Data, typename First, typename Second, typename... Rest>
@ -138,8 +139,7 @@ class continuable_box<
public:
explicit continuable_box(
continuable_base<Data, identity<First, Second, Rest...>>&& continuable)
: continuable_(std::move(continuable)) {
}
: continuable_(std::move(continuable)) {}
auto const& peek() const {
return continuable_;
@ -159,7 +159,9 @@ public:
[](auto&&... args) {
return spread_this(std::forward<decltype(args)>(args)...);
},
unpack_lazy(std::move(args_)));
unpack_lazy(
std::is_default_constructible<std::tuple<First, Second, Rest...>>{},
std::move(args_)));
}
};

View File

@ -1,379 +0,0 @@
/*
/~` _ _ _|_. _ _ |_ | _
\_,(_)| | | || ||_|(_||_)|(/_
https://github.com/Naios/continuable
v4.0.0
Copyright(c) 2015 - 2019 Denis Blank <denis.blank at outlook dot com>
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files(the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and / or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions :
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
**/
#ifndef CONTINUABLE_DETAIL_FLAT_VARIANT_HPP_INCLUDED
#define CONTINUABLE_DETAIL_FLAT_VARIANT_HPP_INCLUDED
#include <cassert>
#include <cstddef>
#include <cstdint>
#include <limits>
#include <memory>
#include <type_traits>
#include <utility>
#include <continuable/detail/utility/traits.hpp>
namespace cti {
namespace detail {
namespace container {
namespace detail {
// We don't want to pull the algorithm header in
constexpr std::size_t max_element_of(std::initializer_list<std::size_t> list) {
std::size_t m = 0;
for (auto current : list) {
if (current > m) {
m = current;
}
}
return m;
}
/// Workarround for a regression introduced in ~ MSVC 15.8.1
template <typename T>
using size_of_helper = std::integral_constant<std::size_t, sizeof(T)>;
template <typename T>
using align_of_helper = std::integral_constant<std::size_t, alignof(T)>;
template <typename... T>
constexpr auto storage_of_impl(identity<T...>) {
constexpr auto size = max_element_of({(size_of_helper<T>::value)...});
constexpr auto align = max_element_of({(align_of_helper<T>::value)...});
return std::aligned_storage_t<size, align>{};
}
/// Declares the aligned storage union for the given types
template <typename... T>
using storage_of_t = decltype(storage_of_impl(identity<T...>{}));
/// The value fpr the empty slot
using slot_t = std::uint8_t;
/// The value which is used to mark the empty slot
using empty_slot =
std::integral_constant<slot_t, std::numeric_limits<slot_t>::max()>;
template <typename... T>
struct flat_variant_base {
storage_of_t<T...> storage_;
slot_t slot_;
constexpr flat_variant_base() : slot_(empty_slot::value) {
}
flat_variant_base(flat_variant_base const&) noexcept {
}
flat_variant_base(flat_variant_base&&) noexcept {
}
flat_variant_base& operator=(flat_variant_base const&) {
return *this;
}
flat_variant_base& operator=(flat_variant_base&&) {
return *this;
}
};
template <typename Base>
struct flat_variant_move_base {
constexpr flat_variant_move_base() = default;
flat_variant_move_base(flat_variant_move_base const&) = default;
explicit flat_variant_move_base(flat_variant_move_base&& right) {
Base& me = *static_cast<Base*>(this);
Base& other = *static_cast<Base*>(&right);
if (other.is_empty()) {
me.set_slot(empty_slot::value);
} else {
other.visit([&](auto&& value) {
#ifndef NDEBUG
me.set_slot(empty_slot::value);
#endif
// NOLINTNEXTLINE(misc-move-forwarding-reference)
me.init(std::move(value), other.get_slot());
});
}
other.destroy();
}
flat_variant_move_base& operator=(flat_variant_move_base const&) = default;
flat_variant_move_base& operator=(flat_variant_move_base&& right) {
Base& me = *static_cast<Base*>(this);
Base& other = *static_cast<Base*>(&right);
me.weak_destroy();
if (other.is_empty()) {
me.set_slot(empty_slot::value);
} else {
other.visit([&](auto&& value) {
// ...
me.init(std::move(value), other.get_slot());
});
}
other.destroy();
return *this;
}
};
template <typename Base, bool IsCopyable /*= true*/>
struct flat_variant_copy_base : flat_variant_move_base<Base> {
constexpr flat_variant_copy_base() = default;
flat_variant_copy_base(flat_variant_copy_base&&) = default;
explicit flat_variant_copy_base(flat_variant_copy_base const& right)
: flat_variant_move_base<Base>()
// TODO noexcept(Base::is_nothrow_move_constructible)
{
Base& me = *static_cast<Base*>(this);
Base const& other = *static_cast<Base const*>(&right);
if (other.is_empty()) {
me.set_slot(empty_slot::value);
} else {
other.visit([&](auto&& value) {
#ifndef NDEBUG
me.set_slot(empty_slot::value);
#endif
me.init(std::move(value), other.get_slot());
});
}
}
flat_variant_copy_base& operator=(flat_variant_copy_base&&) = default;
flat_variant_copy_base& operator=(flat_variant_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);
me.weak_destroy();
if (other.is_empty()) {
me.set_slot(empty_slot::value);
} else {
other.visit([&](auto&& value) {
// ...
me.init(std::move(value), other.get_slot());
});
}
return *this;
}
};
template <typename Base /*, bool IsCopyable = false*/>
struct flat_variant_copy_base<Base, false> : flat_variant_move_base<Base> {
constexpr flat_variant_copy_base() = default;
flat_variant_copy_base(flat_variant_copy_base const&) = delete;
explicit flat_variant_copy_base(flat_variant_copy_base&& right) = default;
flat_variant_copy_base& operator=(flat_variant_copy_base const&) = delete;
flat_variant_copy_base& operator=(flat_variant_copy_base&&) = default;
};
/// Deduces to a true_type if all parameters T satisfy the predicate.
template <template <typename> class Predicate, typename... T>
using every = traits::conjunction<Predicate<T>...>;
} // namespace detail
/// 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<
flat_variant<T...>,
detail::every<std::is_copy_constructible, T...>::value>,
detail::flat_variant_base<T...> {
static_assert(sizeof...(T) > 0, "At least one paremeter T is required!");
template <typename...>
friend class flat_variant;
template <typename>
friend struct detail::flat_variant_move_base;
template <typename, bool>
friend struct detail::flat_variant_copy_base;
template <typename V>
flat_variant(V&& value, detail::slot_t const slot) {
#ifndef NDEBUG
set_slot(detail::empty_slot::value);
#endif
init(std::forward<V>(value), slot);
}
public:
constexpr flat_variant() = default;
flat_variant(flat_variant const&) = default;
flat_variant(flat_variant&&) = default;
flat_variant& operator=(flat_variant const&) = default;
flat_variant& operator=(flat_variant&&) = default;
~flat_variant() noexcept(
detail::every<std::is_nothrow_destructible, T...>::value) {
weak_destroy();
}
template <
typename V,
std::enable_if_t<!is_flat_variant<std::decay_t<V>>::value>* = nullptr>
// 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),
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>
flat_variant& operator=(V&& value) {
weak_destroy();
init(std::forward<V>(value),
traits::index_of_t<std::decay_t<V>, T...>::value);
return *this;
}
void set_empty() {
weak_destroy();
set_slot(detail::empty_slot::value);
}
template <typename V, std::size_t Index =
traits::index_of_t<std::decay_t<V>, T...>::value>
bool is() const noexcept {
return is_slot(Index);
}
bool is_empty() const noexcept {
return is_slot(detail::empty_slot::value);
}
explicit constexpr operator bool() const noexcept {
return !is_empty();
}
template <typename V>
V& cast() & noexcept {
assert(is_slot(traits::index_of_t<std::decay_t<V>, T...>::value));
return *reinterpret_cast<std::decay_t<V>*>(&this->storage_);
}
template <typename V>
V const& cast() const& noexcept {
assert(is_slot(traits::index_of_t<std::decay_t<V>, T...>::value));
return *reinterpret_cast<std::decay_t<V> const*>(&this->storage_);
}
template <typename V>
V&& cast() && noexcept {
assert(is_slot(traits::index_of_t<std::decay_t<V>, T...>::value));
auto& value = *reinterpret_cast<std::decay_t<V>*>(&this->storage_);
return std::move(value);
}
private:
template <typename C, typename V>
static void visit_dispatch(flat_variant* me, V&& visitor) {
std::forward<V>(visitor)(me->cast<C>());
}
template <typename C, typename V>
static void visit_dispatch_const(flat_variant const* me, V&& visitor) {
std::forward<V>(visitor)(me->cast<C>());
}
template <typename V>
void visit(V&& visitor) {
if (!is_empty()) {
using callback_t = void (*)(flat_variant*, V &&);
constexpr callback_t const callbacks[] = {&visit_dispatch<T, V>...};
callbacks[get_slot()](this, std::forward<V>(visitor));
}
}
template <typename V>
void visit(V&& visitor) const {
if (!is_empty()) {
using callback_t = void (*)(flat_variant const*, V&&);
constexpr callback_t const callbacks[] = {&visit_dispatch_const<T, V>...};
callbacks[get_slot()](this, std::forward<V>(visitor));
}
}
template <typename V>
void init(V&& value, detail::slot_t const slot) {
assert(is_empty());
assert(sizeof(this->storage_) >= sizeof(std::decay_t<V>));
using type = std::decay_t<V>;
new (&this->storage_) type(std::forward<V>(value));
set_slot(slot);
}
void destroy() {
weak_destroy();
#ifdef NDEBUG
set_slot(detail::empty_slot::value);
#endif
}
void weak_destroy() {
visit([&](auto&& value) {
using type = std::decay_t<decltype(value)>;
value.~type();
});
#ifndef NDEBUG
set_slot(detail::empty_slot::value);
#endif
}
detail::slot_t get_slot() const noexcept {
// Check for invalid values especially for memory corruption,
// the empty element is included.
assert((this->slot_ < sizeof...(T)) ||
(this->slot_ == detail::empty_slot::value));
return this->slot_;
}
bool is_slot(detail::slot_t const slot) const noexcept {
return get_slot() == slot;
}
void set_slot(detail::slot_t const slot) {
this->slot_ = slot;
}
};
} // namespace container
} // namespace detail
} // namespace cti
#endif // CONTINUABLE_DETAIL_FLAT_VARIANT_HPP_INCLUDED

View File

@ -40,12 +40,14 @@
namespace cti {
namespace detail {
struct void_arg_t { };
template <typename... T>
struct result_trait;
template <>
struct result_trait<> {
using value_t = void;
struct surrogate_t {};
using surrogate_t = void_arg_t;
static constexpr surrogate_t wrap() noexcept {
return {};
@ -93,8 +95,6 @@ struct result_trait<First, Second, Rest...> {
return std::get<I>(std::forward<Result>(result).get_value());
}
};
struct init_arg_t {};
} // namespace detail
} // namespace cti

View File

@ -0,0 +1,217 @@
/*
/~` _ _ _|_. _ _ |_ | _
\_,(_)| | | || ||_|(_||_)|(/_
https://github.com/Naios/continuable
v4.0.0
Copyright(c) 2015 - 2019 Denis Blank <denis.blank at outlook dot com>
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files(the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and / or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions :
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
**/
#ifndef CONTINUABLE_DETAIL_RESULT_VARIANT_HPP_INCLUDED
#define CONTINUABLE_DETAIL_RESULT_VARIANT_HPP_INCLUDED
#include <cassert>
#include <cstddef>
#include <cstdint>
#include <initializer_list>
#include <memory>
#include <type_traits>
#include <utility>
#include <continuable/detail/utility/traits.hpp>
namespace cti {
namespace detail {
namespace container {
enum class result_slot_t : std::uint8_t {
slot_empty,
slot_value,
slot_exception,
};
} // namespace container
struct init_empty_arg_t {};
struct init_result_arg_t {};
struct init_exception_arg_t {};
template <typename T>
class result_variant {
static constexpr bool is_nothrow_destructible = //
std::is_nothrow_destructible<T>::value &&
std::is_nothrow_destructible<exception_t>::value;
static constexpr bool is_nothrow_move_constructible = //
std::is_nothrow_move_constructible<T>::value &&
std::is_nothrow_move_constructible<exception_t>::value;
public:
result_variant() = default;
~result_variant() noexcept(is_nothrow_destructible) {
destroy();
}
explicit result_variant(init_empty_arg_t) noexcept
: slot_(container::result_slot_t::slot_empty) {}
explicit result_variant(init_result_arg_t, T value) noexcept(
std::is_nothrow_destructible<T>::value&&
std::is_nothrow_move_constructible<T>::value)
: slot_(container::result_slot_t::slot_value) {
new (value_ptr()) T(std::move(value));
}
explicit result_variant(init_exception_arg_t, exception_t exception) noexcept(
std::is_nothrow_destructible<exception_t>::value&&
std::is_nothrow_move_constructible<exception_t>::value)
: slot_(container::result_slot_t::slot_exception) {
new (exception_ptr()) exception_t(std::move(exception));
}
result_variant(result_variant const&) = delete;
result_variant& operator=(result_variant const&) = delete;
result_variant(result_variant&& other) noexcept(
is_nothrow_destructible&& is_nothrow_move_constructible)
: slot_(other.slot_) {
switch (other.slot_) {
case container::result_slot_t::slot_value: {
new (value_ptr()) T(std::move(*other.value_ptr()));
break;
}
case container::result_slot_t::slot_exception: {
new (exception_ptr()) exception_t(std::move(*other.exception_ptr()));
break;
}
default: {
break;
}
}
other.destroy();
other.slot_ = container::result_slot_t::slot_empty;
}
result_variant& operator=(result_variant&& other) noexcept(
is_nothrow_destructible&& is_nothrow_move_constructible) {
destroy();
slot_ = other.slot_;
switch (other.slot_) {
case container::result_slot_t::slot_value: {
new (value_ptr()) T(std::move(*other.value_ptr()));
break;
}
case container::result_slot_t::slot_exception: {
new (exception_ptr()) exception_t(std::move(*other.exception_ptr()));
break;
}
default: {
break;
}
}
other.destroy();
other.slot_ = container::result_slot_t::slot_empty;
return *this;
}
void set_empty() {
destroy();
slot_ = container::result_slot_t::slot_empty;
}
void set_value(T value) {
destroy();
new (value_ptr()) T(std::move(value));
slot_ = container::result_slot_t::slot_value;
}
void set_exception(exception_t exception) {
destroy();
new (exception_ptr()) exception_t(std::move(exception));
slot_ = container::result_slot_t::slot_exception;
}
container::result_slot_t slot() const noexcept {
return slot_;
}
bool is_empty() const noexcept {
return slot_ == container::result_slot_t::slot_empty;
}
bool is_value() const noexcept {
return slot_ == container::result_slot_t::slot_value;
}
bool is_exception() const noexcept {
return slot_ == container::result_slot_t::slot_exception;
}
T& get_value() noexcept {
assert(is_value());
return *reinterpret_cast<T*>(&storage_);
}
T const& get_value() const noexcept {
assert(is_value());
return *reinterpret_cast<T const*>(&storage_);
}
exception_t& get_exception() noexcept {
assert(is_exception());
return *reinterpret_cast<exception_t*>(&storage_);
}
exception_t const& get_exception() const noexcept {
assert(is_exception());
return *reinterpret_cast<exception_t const*>(&storage_);
}
private:
constexpr T* value_ptr() noexcept {
return reinterpret_cast<T*>(&storage_);
}
constexpr exception_t* exception_ptr() noexcept {
return reinterpret_cast<exception_t*>(&storage_);
}
void destroy() noexcept(is_nothrow_destructible) {
switch (slot_) {
case container::result_slot_t::slot_value: {
value_ptr()->~T();
break;
}
case container::result_slot_t::slot_exception: {
exception_ptr()->~exception_t();
break;
}
default: {
break;
}
}
}
container::result_slot_t slot_{container::result_slot_t::slot_empty};
std::aligned_storage_t<
(sizeof(T) > sizeof(exception_t) ? sizeof(T) : sizeof(exception_t)),
(alignof(T) > alignof(exception_t) ? alignof(T) : alignof(exception_t))>
storage_;
};
} // namespace detail
} // namespace cti
#endif // CONTINUABLE_DETAIL_RESULT_VARIANT_HPP_INCLUDED

View File

@ -18,7 +18,6 @@ add_executable(test-continuable-single
${CMAKE_CURRENT_LIST_DIR}/single/test-continuable-promise.cpp
${CMAKE_CURRENT_LIST_DIR}/single/test-continuable-connection-noinst
${CMAKE_CURRENT_LIST_DIR}/single/test-continuable-forward-decl.cpp
${CMAKE_CURRENT_LIST_DIR}/single/test-continuable-flat-variant.cpp
${CMAKE_CURRENT_LIST_DIR}/single/test-continuable-result.cpp
${CMAKE_CURRENT_LIST_DIR}/single/test-continuable-ready.cpp
${CMAKE_CURRENT_LIST_DIR}/single/test-continuable-promisify.cpp

View File

@ -1,161 +0,0 @@
/*
Copyright(c) 2015 - 2019 Denis Blank <denis.blank at outlook dot com>
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files(the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and / or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions :
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
**/
#include <memory>
#include <utility>
#include <continuable/detail/utility/flat-variant.hpp>
#include <test-continuable.hpp>
using cti::detail::container::flat_variant;
static int const CANARY = 373671;
using int_variant = flat_variant<int, tag1, tag2>;
TEST(flat_variant_single_test, is_move_constructible) {
{
int_variant e_old(CANARY);
int_variant e(std::move(e_old));
EXPECT_TRUE(bool(e));
EXPECT_TRUE(e.is<int>());
EXPECT_EQ(e.cast<int>(), CANARY);
}
{
int_variant e(int_variant(tag1{}));
EXPECT_TRUE(bool(e));
EXPECT_TRUE(e.is<tag1>());
}
}
TEST(flat_variant_single_test, is_value_move_assignable) {
int_variant old(CANARY);
int_variant e;
e = std::move(old);
EXPECT_TRUE(bool(e));
EXPECT_TRUE(e.is<int>());
EXPECT_EQ(e.cast<int>(), CANARY);
}
TEST(flat_variant_single_test, is_copy_constructible) {
{
int_variant const e_old(CANARY);
int_variant e(e_old);
EXPECT_TRUE(bool(e));
EXPECT_TRUE(e.is<int>());
EXPECT_EQ(e.cast<int>(), CANARY);
}
{
int_variant const e_old(tag1{});
int_variant e(e_old);
EXPECT_TRUE(bool(e));
EXPECT_TRUE(e.is<tag1>());
}
}
TEST(flat_variant_single_test, is_copy_assignable) {
{
int_variant const e_old(CANARY);
int_variant e;
e = e_old;
EXPECT_TRUE(bool(e));
EXPECT_TRUE(e.is<int>());
EXPECT_EQ(e.cast<int>(), CANARY);
}
{
int_variant const e_old(tag1{});
int_variant e;
e = e_old;
EXPECT_TRUE(bool(e));
EXPECT_TRUE(e.is<tag1>());
}
}
// This regression test shows a memory leak which happens when using the
// expected class move constructed from another expected object.
TEST(flat_variant_single_test, test_leak_regression) {
// expected_all_tests<cti::detail::util::expected<std::__1::unique_ptr<int,
// std::__1::default_delete<int> > > >::supply<int const&>(int const&)
// const
// continuable/build/../test/unit-test/test-continuable-expected.cpp:52
// 3: #3 0x11cf07a in
// expected_all_tests_is_value_assignable_Test<cti::detail::util::expected<std::__1::unique_ptr<int,
// std::__1::default_delete<int> > > >::TestBody()
// continuable/build/../test/unit-test/test-continuable-expected.cpp:133:15
// 3: #4 0x1339e4e in void
// testing::internal::HandleSehExceptionsInMethodIfSupported<testing::Test,
// void>(testing::Test*, void (testing::Test::*)(), char const*)
// continuable/build/../dep/googletest/googletest/googletest/src/gtest.cc:2395:10
using type = std::shared_ptr<int>;
auto const validate = [](auto&& variant, long use) {
ASSERT_TRUE(variant.template is<type>());
ASSERT_EQ((*variant.template cast<type>()), CANARY);
ASSERT_EQ(variant.template cast<type>().use_count(), use);
};
bool destroyed = false;
{
type ptr(new int(CANARY), [&](int* val) {
destroyed = true;
delete val;
});
auto e1(flat_variant<type>(std::move(ptr)));
validate(e1, 1);
auto e2 = std::move(e1);
validate(e2, 1);
flat_variant<type> e3;
e3 = std::move(e2);
validate(e3, 1);
{
flat_variant<type> ec(e3);
validate(ec, 2);
}
{
flat_variant<type> ec = e3;
validate(ec, 2);
}
{
flat_variant<type> ec;
ec = e3;
validate(ec, 2);
}
}
ASSERT_TRUE(destroyed);
}

View File

@ -144,50 +144,6 @@ TYPED_TEST(result_all_tests, is_error_move_assignable) {
EXPECT_TRUE(e.is_exception());
}
TEST(result_copyable_tests, is_copy_constructible) {
{
copyable_type const e_old(CANARY);
copyable_type e(e_old);
EXPECT_TRUE(bool(e));
EXPECT_EQ(*e, CANARY);
EXPECT_TRUE(e.is_value());
EXPECT_FALSE(e.is_exception());
}
{
copyable_type const e_old(supply_test_exception());
copyable_type e(e_old);
EXPECT_FALSE(bool(e));
EXPECT_FALSE(e.is_value());
EXPECT_TRUE(e.is_exception());
}
}
TEST(result_copyable_tests, is_copy_assignable) {
{
copyable_type const e_old(CANARY);
copyable_type e;
e = e_old;
EXPECT_TRUE(bool(e));
EXPECT_EQ(*e, CANARY);
EXPECT_TRUE(e.is_value());
EXPECT_FALSE(e.is_exception());
}
{
copyable_type const e_old(supply_test_exception());
copyable_type e;
e = e_old;
EXPECT_FALSE(bool(e));
EXPECT_FALSE(e.is_value());
EXPECT_TRUE(e.is_exception());
}
}
TYPED_TEST(result_all_tests, is_constructible_from_error_helper) {
cti::exceptional_result e1(supply_test_exception());
{