Commit b5d446af authored by Dominik Charousset's avatar Dominik Charousset

Re-implement result<...> as sum type

The `result` class predates the sum type API in CAF and used a flag for
selecting one of several members. Re-implementing the class with a
`variant` instead is both cleaner and more idiomatic.

Furthermore, the implementation for `result` now uses a base type that
wraps state as well common constructors. The class `result` is merely a
thin wrapper around the base with additional constructors. For example,
results with a single template parameter allow conversion from
`expected`. This fixes #989 by avoiding catch-all constructors and
unreliable `enable_if` magic.
parent 4406ed26
......@@ -23,6 +23,7 @@
#include "caf/detail/apply_args.hpp"
#include "caf/detail/core_export.hpp"
#include "caf/detail/int_list.hpp"
#include "caf/detail/overload.hpp"
#include "caf/detail/type_traits.hpp"
#include "caf/expected.hpp"
#include "caf/fwd.hpp"
......@@ -198,20 +199,18 @@ public:
/// Dispatches on the runtime-type of `x`.
template <class... Ts>
bool visit(result<Ts...>& x) {
switch (x.flag) {
case rt_value:
(*this)(x.value);
bool visit(result<Ts...>& res) {
auto f = detail::make_overload(
[this](auto& x) {
(*this)(x);
return true;
case rt_error:
(*this)(x.err);
return true;
case rt_delegated:
},
[this](delegated<Ts...>&) {
(*this)();
return true;
default:
return false;
}
},
[this](skip_t&) { return false; });
return caf::visit(f, res);
}
};
......
......@@ -33,14 +33,6 @@
namespace caf {
/// Helper class to construct an `expected<T>` that represents no error.
/// @relates expected
struct no_error_t {};
/// The only instance of ::no_error_t.
/// @relates expected
constexpr no_error_t no_error = no_error_t{};
/// Represents the result of a computation which can either complete
/// successfully with an instance of type `T` or fail with an `error`.
/// @tparam T The type of the result.
......@@ -85,10 +77,6 @@ public:
new (std::addressof(error_)) caf::error{std::move(e)};
}
expected(no_error_t) noexcept : engaged_(false) {
new (std::addressof(error_)) caf::error{};
}
expected(const expected& other) noexcept(nothrow_copy) {
construct(other);
}
......@@ -375,10 +363,6 @@ public:
// nop
}
expected(no_error_t) noexcept {
// nop
}
expected(caf::error e) noexcept : error_(std::move(e)) {
// nop
}
......
......@@ -20,6 +20,7 @@
#include <type_traits>
#include "caf/default_sum_type_access.hpp"
#include "caf/delegated.hpp"
#include "caf/detail/type_list.hpp"
#include "caf/detail/type_traits.hpp"
......@@ -29,196 +30,217 @@
#include "caf/message.hpp"
#include "caf/none.hpp"
#include "caf/skip.hpp"
#include "caf/sum_type.hpp"
#include "caf/variant.hpp"
namespace caf::detail {
// Tag type for selecting a protected constructor in `result_base`.
struct result_base_message_init {};
} // namespace caf::detail
namespace caf {
enum result_runtime_type { rt_value, rt_error, rt_delegated, rt_skip };
// -- base type for result<Ts...> ----------------------------------------------
/// Base type for all specializations of `result`.
template <class... Ts>
class result {
class result_base {
public:
// clang-format off
template <class... Us,
class = detail::enable_if_tt<
detail::all_constructible<
detail::type_list<Ts...>,
detail::type_list<std::decay_t<Us>...>>>>
// clang-format on
result(Us&&... xs) : flag(rt_value) {
value = make_message(Ts{std::forward<Us>(xs)}...);
}
static_assert(sizeof...(Ts) > 0);
using types = detail::type_list<delegated<Ts...>, message, error, skip_t>;
result_base() = default;
result_base(result_base&&) = default;
result_base(const result_base&) = default;
result_base& operator=(result_base&&) = default;
result_base& operator=(const result_base&) = default;
template <class Enum, uint8_t = error_category<Enum>::value>
result(Enum x) : flag(rt_error), err(make_error(x)) {
result_base(Enum x) : content_(make_error(x)) {
// nop
}
result(error x) : flag(rt_error), err(std::move(x)) {
result_base(error x) : content_(std::move(x)) {
// nop
}
template <class T,
class = typename std::enable_if<
sizeof...(Ts) == 1
&& std::is_convertible<
T, detail::tl_head_t<detail::type_list<Ts...>>>::value>::type>
result(expected<T> x) {
if (x) {
flag = rt_value;
init(std::move(*x));
} else {
flag = rt_error;
err = std::move(x.error());
}
}
result(skip_t) : flag(rt_skip) {
result_base(skip_t) : content_(skip) {
// nop
}
result(delegated<Ts...>) : flag(rt_delegated) {
result_base(delegated<Ts...> x) : content_(x) {
// nop
}
result(const typed_response_promise<Ts...>&) : flag(rt_delegated) {
result_base(const typed_response_promise<Ts...>&)
: content_(delegated<Ts...>{}) {
// nop
}
result(const response_promise&) : flag(rt_delegated) {
result_base(const response_promise&) : content_(delegated<Ts...>{}) {
// nop
}
result_runtime_type flag;
message value;
error err;
private:
void init(Ts... xs) {
value = make_message(std::move(xs)...);
/// @private
auto& get_data() {
return content_;
}
};
template <>
class result<message> {
public:
result(message msg) : flag(rt_value), value(std::move(msg)) {
// nop
/// @private
const auto& get_data() const {
return content_;
}
template <class Enum, uint8_t = error_category<Enum>::value>
result(Enum x) : flag(rt_error), err(make_error(x)) {
protected:
explicit result_base(detail::result_base_message_init) : content_(message{}) {
// nop
}
result(error x) : flag(rt_error), err(std::move(x)) {
template <class... Us>
explicit result_base(detail::result_base_message_init, Us&&... xs)
: content_(make_message(std::forward<Us>(xs)...)) {
// nop
}
template <class T>
result(expected<message> x) {
if (x) {
flag = rt_value;
value = std::move(*x);
} else {
flag = rt_error;
err = std::move(x.error());
}
}
variant<delegated<Ts...>, message, error, skip_t> content_;
};
result(skip_t) : flag(rt_skip) {
// -- result<Ts...> and its specializations ------------------------------------
/// Wraps the result of a message handler to represent either a value (wrapped
/// into a `message`), a `delegated<Ts...>` (indicates that another actor is
/// going to respond), or an `error`.
template <class... Ts>
class result;
template <>
class result<void> : public result_base<void> {
public:
using super = result_base<void>;
using super::super;
result() : super(detail::result_base_message_init{}) {
// nop
}
result(delegated<message>) : flag(rt_delegated) {
result(unit_t) : super(detail::result_base_message_init{}) {
// nop
}
result(const typed_response_promise<message>&) : flag(rt_delegated) {
result(delegated<unit_t>) : super(delegated<void>{}) {
// nop
}
result(const response_promise&) : flag(rt_delegated) {
result(const typed_response_promise<unit_t>&) : super(delegated<void>{}) {
// nop
}
result_runtime_type flag;
message value;
error err;
};
template <>
struct result<void> {
class result<unit_t> : public result_base<void> {
public:
result() : flag(rt_value) {
using super = result_base<void>;
using super::super;
result() : super(detail::result_base_message_init{}) {
// nop
}
result(const unit_t&) : flag(rt_value) {
result(unit_t) : super(detail::result_base_message_init{}) {
// nop
}
template <class Enum, uint8_t = error_category<Enum>::value>
result(Enum x) : flag(rt_error), err(make_error(x)) {
result(delegated<unit_t>) : super(delegated<void>{}) {
// nop
}
result(error x) : flag(rt_error), err(std::move(x)) {
result(const typed_response_promise<unit_t>&) : super(delegated<void>{}) {
// nop
}
};
result(expected<void> x) {
init(x);
}
template <>
class result<message> : public result_base<message> {
public:
using super = result_base<message>;
result(expected<unit_t> x) {
init(x);
}
using super::super;
result(skip_t) : flag(rt_skip) {
// nop
result(message x) {
this->content_ = std::move(x);
}
result(delegated<void>) : flag(rt_delegated) {
// nop
result(expected<message> x) {
if (x)
this->content_ = std::move(*x);
else
this->content_ = std::move(x.error());
}
result(delegated<unit_t>) : flag(rt_delegated) {
// nop
result& operator=(expected<message> x) {
if (x)
this->content_ = std::move(*x);
else
this->content_ = std::move(x.error());
return *this;
}
};
result(const typed_response_promise<void>&) : flag(rt_delegated) {
template <class T>
class result<T> : public result_base<T> {
public:
using super = result_base<T>;
using super::super;
result(T x) : super(detail::result_base_message_init{}, std::move(x)) {
// nop
}
result(const typed_response_promise<unit_t>&) : flag(rt_delegated) {
// nop
result(expected<T> x) {
if (x)
this->content_ = make_message(std::move(*x));
else
this->content_ = std::move(x.error());
}
result(const response_promise&) : flag(rt_delegated) {
// nop
result& operator=(expected<T> x) {
if (x)
this->content_ = make_message(std::move(*x));
else
this->content_ = std::move(x.error());
return *this;
}
};
result_runtime_type flag;
message value;
error err;
template <class T0, class T1, class... Ts>
class result<T0, T1, Ts...> : public result_base<T0, T1, Ts...> {
public:
using super = result_base<T0, T1, Ts...>;
using super::super;
private:
template <class T>
void init(T& x) {
if (x) {
flag = rt_value;
} else {
flag = rt_error;
err = std::move(x.error());
}
result(T0 x0, T1 x1, Ts... xs)
: super(detail::result_base_message_init{}, std::move(x0), std::move(x1),
std::move(xs)...) {
// nop
}
};
template <>
struct result<unit_t> : result<void> {
using super = result<void>;
// -- sum type access to result<Ts...> -----------------------------------------
using super::super;
template <class... Ts>
struct sum_type_access<result<Ts...>> : default_sum_type_access<result<Ts...>> {
// nop
};
template <class T>
......
......@@ -196,7 +196,7 @@ blocking_actor::mailbox_visitor::operator()(mailbox_element& x) {
case match_result::no_match: { // Blocking actors can have fallback
// handlers for catch-all rules.
auto sres = bhvr.fallback(self->current_element_->payload);
if (sres.flag != rt_skip) {
if (!holds_alternative<skip_t>(sres)) {
visitor.visit(sres);
return check_if_done();
}
......
......@@ -93,16 +93,9 @@ invoke_message_result raw_event_based_actor::consume(mailbox_element& x) {
});
auto call_default_handler = [&] {
auto sres = call_handler(default_handler_, this, x.payload);
switch (sres.flag) {
default:
break;
case rt_error:
case rt_value:
visitor.visit(sres);
break;
case rt_skip:
skipped = true;
}
auto f = detail::make_overload([&](auto& x) { visitor.visit(x); },
[&](skip_t& x) { skipped = true; });
visit(f, sres);
};
if (bhvr_stack_.empty()) {
call_default_handler();
......
......@@ -701,17 +701,10 @@ invoke_message_result scheduled_actor::consume(mailbox_element& x) {
setf(has_timeout_flag);
});
auto call_default_handler = [&] {
auto f = detail::make_overload([&](auto& val) { visitor.visit(val); },
[&](skip_t&) { skipped = true; });
auto sres = call_handler(default_handler_, this, x.payload);
switch (sres.flag) {
default:
break;
case rt_error:
case rt_value:
visitor.visit(sres);
break;
case rt_skip:
skipped = true;
}
visit(f, sres);
};
if (bhvr_stack_.empty()) {
call_default_handler();
......@@ -1138,15 +1131,14 @@ scheduled_actor::handle_open_stream_msg(mailbox_element& x) {
// Utility for invoking the default handler.
auto fallback = [&] {
auto sres = call_handler(default_handler_, this, x.payload);
switch (sres.flag) {
default:
CAF_LOG_DEBUG(
"default handler was called for open_stream_msg:" << osm.msg);
fail(sec::stream_init_failed, "dropped open_stream_msg (no match)");
return invoke_message_result::dropped;
case rt_skip:
CAF_LOG_DEBUG("default handler skipped open_stream_msg:" << osm.msg);
return invoke_message_result::skipped;
if (holds_alternative<skip_t>(sres)) {
CAF_LOG_DEBUG("default handler skipped open_stream_msg:" << osm.msg);
return invoke_message_result::skipped;
} else {
CAF_LOG_DEBUG(
"default handler was called for open_stream_msg:" << osm.msg);
fail(sec::stream_init_failed, "dropped open_stream_msg (no match)");
return invoke_message_result::dropped;
}
};
// Invoke behavior and dispatch on the result.
......
......@@ -116,13 +116,3 @@ CAF_TEST(construction_with_none) {
CHECK(!x);
CHECK(!x.error());
}
CAF_TEST(construction_with_no_error) {
e_int x{no_error};
CHECK(!x);
CHECK(!x.error());
auto f = []() -> e_int {
return no_error;
};
CHECK_EQ(f(), x);
}
......@@ -31,38 +31,34 @@ namespace {
template<class T>
void test_unit_void() {
auto x = result<T>{};
CAF_CHECK_EQUAL(x.flag, rt_value);
x = skip();
CAF_CHECK_EQUAL(x.flag, rt_skip);
x = expected<T>{};
CAF_CHECK_EQUAL(x.flag, rt_value);
x = expected<T>{sec::unexpected_message};
CAF_CHECK_EQUAL(x.flag, rt_error);
CAF_CHECK_EQUAL(x.err, make_error(sec::unexpected_message));
CAF_CHECK(holds_alternative<message>(x));
x = skip;
CAF_CHECK(holds_alternative<skip_t>(x));
}
} // namespace anonymous
CAF_TEST(skip) {
auto x = result<>{skip()};
CAF_CHECK_EQUAL(x.flag, rt_skip);
CAF_CHECK(x.value.empty());
auto x = result<int>{skip};
CAF_CHECK(holds_alternative<skip_t>(x));
}
CAF_TEST(value) {
auto x = result<int>{42};
CAF_CHECK_EQUAL(x.flag, rt_value);
CAF_CHECK_EQUAL(x.value.get_as<int>(0), 42);
CAF_REQUIRE(holds_alternative<message>(x));
if (auto view = make_typed_message_view<int>(get<message>(x)))
CAF_CHECK_EQUAL(get<0>(view), 42);
else
CAF_FAIL("unexpected types in result message");
}
CAF_TEST(expected) {
auto x = result<int>{expected<int>{42}};
CAF_CHECK_EQUAL(x.flag, rt_value);
CAF_CHECK_EQUAL(x.value.get_as<int>(0), 42);
x = expected<int>{sec::unexpected_message};
CAF_CHECK_EQUAL(x.flag, rt_error);
CAF_CHECK_EQUAL(x.err, make_error(sec::unexpected_message));
CAF_CHECK(x.value.empty());
CAF_REQUIRE(holds_alternative<message>(x));
if (auto view = make_typed_message_view<int>(get<message>(x)))
CAF_CHECK_EQUAL(get<0>(view), 42);
else
CAF_FAIL("unexpected types in result message");
}
CAF_TEST(void_specialization) {
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment