Commit 4d53aa0d authored by Dominik Charousset's avatar Dominik Charousset

Define actor interfaces using function signatures

The implementation of `replies_to<...>::with<...>` introduced
unnecessary complexity and metaprogramming utilities. Using plain old
function signatures instead makes working with actor interfaces a bit
easier internally and confronts users with less `detail` types should
they face compiler errors related to `typed_actor`.
parent d5c0956f
......@@ -97,7 +97,13 @@ is based on [Keep a Changelog](https://keepachangelog.com).
typed behavior always must provide all message handlers (typed behavior assume
a complete implementation of the interface). This use case received direct
support: constructing a typed behavior with `partial_behavior_init` as first
argument suppresses the check for completeness .
argument suppresses the check for completeness.
- In order to reduce complexity of typed actors, CAF defines interfaces as a set
of function signatures rather than using custom metaprogramming facilities.
Function signatures *must* always wrap the return type in a `result<T>`. For
example: `typed_actor<result<double>(double)>`. We have reimplemented the
metaprogramming facilities `racts_to<...>` and `replies_to<...>::with<...>`
as an alternative way of writing the function signature.
### Removed
......
......@@ -34,8 +34,8 @@ namespace {
// atoms for chopstick and philosopher interfaces
// a chopstick
using chopstick = typed_actor<replies_to<take_atom>::with<taken_atom, bool>,
reacts_to<put_atom>>;
using chopstick
= typed_actor<result<taken_atom, bool>(take_atom), result<void>(put_atom)>;
chopstick::behavior_type taken_chopstick(chopstick::pointer,
const strong_actor_ptr&);
......
......@@ -14,8 +14,9 @@ using namespace caf;
namespace {
using calculator_actor = typed_actor<replies_to<add_atom, int, int>::with<int>,
replies_to<sub_atom, int, int>::with<int>>;
using calculator_actor
= typed_actor<result<int32_t>(add_atom, int32_t, int32_t),
result<int32_t>(sub_atom, int32_t, int32_t)>;
// prototypes and forward declarations
behavior calculator_fun(event_based_actor* self);
......@@ -28,8 +29,8 @@ class typed_calculator;
// function-based, dynamically typed, event-based API
behavior calculator_fun(event_based_actor*) {
return {
[](add_atom, int a, int b) { return a + b; },
[](sub_atom, int a, int b) { return a - b; },
[](add_atom, int32_t a, int32_t b) { return a + b; },
[](sub_atom, int32_t a, int32_t b) { return a - b; },
};
}
......@@ -37,8 +38,8 @@ behavior calculator_fun(event_based_actor*) {
void blocking_calculator_fun(blocking_actor* self) {
bool running = true;
self->receive_while(running)( //
[](add_atom, int a, int b) { return a + b; },
[](sub_atom, int a, int b) { return a - b; },
[](add_atom, int32_t a, int32_t b) { return a + b; },
[](sub_atom, int32_t a, int32_t b) { return a - b; },
[&](exit_msg& em) {
if (em.reason) {
self->fail_state(std::move(em.reason));
......@@ -50,8 +51,8 @@ void blocking_calculator_fun(blocking_actor* self) {
// function-based, statically typed, event-based API
calculator_actor::behavior_type typed_calculator_fun() {
return {
[](add_atom, int a, int b) { return a + b; },
[](sub_atom, int a, int b) { return a - b; },
[](add_atom, int32_t a, int32_t b) { return a + b; },
[](sub_atom, int32_t a, int32_t b) { return a - b; },
};
}
......@@ -97,7 +98,8 @@ void tester(scoped_actor&) {
// tests a calculator instance
template <class Handle, class... Ts>
void tester(scoped_actor& self, const Handle& hdl, int x, int y, Ts&&... xs) {
void tester(scoped_actor& self, const Handle& hdl, int32_t x, int32_t y,
Ts&&... xs) {
auto handle_err = [&](const error& err) {
aout(self) << "AUT (actor under test) failed: "
<< self->system().render(err) << endl;
......@@ -105,12 +107,12 @@ void tester(scoped_actor& self, const Handle& hdl, int x, int y, Ts&&... xs) {
// first test: x + y = z
self->request(hdl, infinite, add_atom_v, x, y)
.receive(
[&](int res1) {
[&](int32_t res1) {
aout(self) << x << " + " << y << " = " << res1 << endl;
// second test: x - y = z
self->request(hdl, infinite, sub_atom_v, x, y)
.receive(
[&](int res2) {
[&](int32_t res2) {
aout(self) << x << " - " << y << " = " << res2 << endl;
},
handle_err);
......
......@@ -7,6 +7,7 @@
// without updating the references in the *.tex files!
// Manual references: lines 18-44, and 49-50 (Actor.tex)
#include <cstdint>
#include <iostream>
#include "caf/all.hpp"
......@@ -15,33 +16,21 @@ using std::cout;
using std::endl;
using namespace caf;
using cell = typed_actor<reacts_to<put_atom, int>,
replies_to<get_atom>::with<int>>;
using cell
= typed_actor<result<void>(put_atom, int32_t), result<int32_t>(get_atom)>;
struct cell_state {
int value = 0;
int32_t value = 0;
};
cell::behavior_type type_checked_cell(cell::stateful_pointer<cell_state> self) {
return {
[=](put_atom, int val) {
self->state.value = val;
},
[=](get_atom) {
return self->state.value;
}
};
return {[=](put_atom, int32_t val) { self->state.value = val; },
[=](get_atom) { return self->state.value; }};
}
behavior unchecked_cell(stateful_actor<cell_state>* self) {
return {
[=](put_atom, int val) {
self->state.value = val;
},
[=](get_atom) {
return self->state.value;
}
};
return {[=](put_atom, int32_t val) { self->state.value = val; },
[=](get_atom) { return self->state.value; }};
}
void caf_main(actor_system& system) {
......
......@@ -10,16 +10,18 @@ using namespace caf;
// using add_atom = atom_constant<atom("add")>; (defined in atom.hpp)
using calc = typed_actor<replies_to<add_atom, int, int>::with<int>>;
using calc = typed_actor<result<int32_t>(add_atom, int32_t, int32_t)>;
void actor_a(event_based_actor* self, const calc& worker) {
self->request(worker, std::chrono::seconds(10), add_atom_v, 1, 2)
.then([=](int result) { aout(self) << "1 + 2 = " << result << std::endl; });
.then([=](int32_t result) { //
aout(self) << "1 + 2 = " << result << std::endl;
});
}
calc::behavior_type actor_b(calc::pointer self, const calc& worker) {
return {
[=](add_atom add, int x, int y) {
[=](add_atom add, int32_t x, int32_t y) {
return self->delegate(worker, add, x, y);
},
};
......@@ -27,7 +29,7 @@ calc::behavior_type actor_b(calc::pointer self, const calc& worker) {
calc::behavior_type actor_c() {
return {
[](add_atom, int x, int y) { return x + y; },
[](add_atom, int32_t x, int32_t y) { return x + y; },
};
}
......
......@@ -46,7 +46,7 @@ public:
}
};
using divider = typed_actor<replies_to<div_atom, double, double>::with<double>>;
using divider = typed_actor<result<double>(div_atom, double, double)>;
divider::behavior_type divider_impl() {
return {
......
......@@ -36,20 +36,20 @@ using namespace caf;
/// A simple actor for storing an integer value.
using cell = typed_actor<
// Writes a new value.
reacts_to<put_atom, int>,
result<void>(put_atom, int),
// Reads the value.
replies_to<get_atom>::with<int>>;
result<int>(get_atom)>;
/// An for storing a 2-dimensional matrix of integers.
using matrix = typed_actor<
// Writes a new value to given cell (x-coordinate, y-coordinate, new-value).
reacts_to<put_atom, int, int, int>,
result<void>(put_atom, int, int, int),
// Reads from given cell.
replies_to<get_atom, int, int>::with<int>,
result<int>(get_atom, int, int),
// Computes the average for given row.
replies_to<get_atom, average_atom, row_atom, int>::with<double>,
result<double>(get_atom, average_atom, row_atom, int),
// Computes the average for given column.
replies_to<get_atom, average_atom, column_atom, int>::with<double>>;
result<double>(get_atom, average_atom, column_atom, int)>;
struct cell_state {
int value = 0;
......
......@@ -15,16 +15,12 @@ using std::endl;
using namespace caf;
// using add_atom = atom_constant<atom("add")>; (defined in atom.hpp)
using adder = typed_actor<replies_to<add_atom, int, int>::with<int>>;
using adder = typed_actor<result<int32_t>(add_atom, int32_t, int32_t)>;
// function-based, statically typed, event-based API
adder::behavior_type worker() {
return {
[](add_atom, int a, int b) {
return a + b;
}
[](add_atom, int32_t a, int32_t b) { return a + b; },
};
}
......@@ -32,13 +28,13 @@ adder::behavior_type worker() {
adder::behavior_type calculator_master(adder::pointer self) {
auto w = self->spawn(worker);
return {
[=](add_atom x, int y, int z) -> result<int> {
auto rp = self->make_response_promise<int>();
self->request(w, infinite, x, y, z).then([=](int result) mutable {
[=](add_atom x, int32_t y, int32_t z) -> result<int32_t> {
auto rp = self->make_response_promise<int32_t>();
self->request(w, infinite, x, y, z).then([=](int32_t result) mutable {
rp.deliver(result);
});
return rp;
}
},
};
}
......
......@@ -6,9 +6,10 @@
// without updating the references in the *.tex files!
// Manual references: lines 20-37, 39-51, 53-64, 67-69 (MessagePassing.tex)
#include <vector>
#include <chrono>
#include <cstdint>
#include <iostream>
#include <vector>
#include "caf/all.hpp"
......@@ -17,35 +18,32 @@ using std::vector;
using std::chrono::seconds;
using namespace caf;
using cell = typed_actor<reacts_to<put_atom, int>,
replies_to<get_atom>::with<int>>;
using cell
= typed_actor<result<void>(put_atom, int32_t), result<int32_t>(get_atom)>;
struct cell_state {
int value = 0;
int32_t value = 0;
};
cell::behavior_type cell_impl(cell::stateful_pointer<cell_state> self, int x0) {
cell::behavior_type cell_impl(cell::stateful_pointer<cell_state> self,
int32_t x0) {
self->state.value = x0;
return {
[=](put_atom, int val) {
self->state.value = val;
},
[=](get_atom) {
return self->state.value;
}
[=](put_atom, int32_t val) { self->state.value = val; },
[=](get_atom) { return self->state.value; },
};
}
void waiting_testee(event_based_actor* self, vector<cell> cells) {
for (auto& x : cells)
self->request(x, seconds(1), get_atom_v).await([=](int y) {
self->request(x, seconds(1), get_atom_v).await([=](int32_t y) {
aout(self) << "cell #" << x.id() << " -> " << y << endl;
});
}
void multiplexed_testee(event_based_actor* self, vector<cell> cells) {
for (auto& x : cells)
self->request(x, seconds(1), get_atom_v).then([=](int y) {
self->request(x, seconds(1), get_atom_v).then([=](int32_t y) {
aout(self) << "cell #" << x.id() << " -> " << y << endl;
});
}
......@@ -54,7 +52,9 @@ void blocking_testee(blocking_actor* self, vector<cell> cells) {
for (auto& x : cells)
self->request(x, seconds(1), get_atom_v)
.receive(
[&](int y) { aout(self) << "cell #" << x.id() << " -> " << y << endl; },
[&](int32_t y) {
aout(self) << "cell #" << x.id() << " -> " << y << endl;
},
[&](error& err) {
aout(self) << "cell #" << x.id() << " -> "
<< self->system().render(err) << endl;
......
......@@ -12,13 +12,14 @@ using namespace caf;
namespace {
using calculator_type = typed_actor<replies_to<add_atom, int, int>::with<int>,
replies_to<sub_atom, int, int>::with<int>>;
using calculator_type
= typed_actor<result<int32_t>(add_atom, int32_t, int32_t),
result<int32_t>(sub_atom, int32_t, int32_t)>;
calculator_type::behavior_type typed_calculator_fun(calculator_type::pointer) {
return {
[](add_atom, int x, int y) { return x + y; },
[](sub_atom, int x, int y) { return x - y; },
[](add_atom, int32_t x, int32_t y) -> int32_t { return x + y; },
[](sub_atom, int32_t x, int32_t y) -> int32_t { return x - y; },
};
}
......@@ -31,8 +32,8 @@ public:
protected:
behavior_type make_behavior() override {
return {
[](add_atom, int x, int y) { return x + y; },
[](sub_atom, int x, int y) { return x - y; },
[](add_atom, int32_t x, int32_t y) -> int32_t { return x + y; },
[](sub_atom, int32_t x, int32_t y) -> int32_t { return x - y; },
};
}
};
......@@ -42,9 +43,9 @@ void tester(event_based_actor* self, const calculator_type& testee) {
// first test: 2 + 1 = 3
self->request(testee, infinite, add_atom_v, 2, 1)
.then(
[=](int r1) {
[=](int32_t r1) {
// second test: 2 - 1 = 1
self->request(testee, infinite, sub_atom_v, 2, 1).then([=](int r2) {
self->request(testee, infinite, sub_atom_v, 2, 1).then([=](int32_t r2) {
// both tests succeeded
if (r1 == 3 && r2 == 1) {
aout(self) << "AUT (actor under test) seems to be ok" << endl;
......
......@@ -20,8 +20,8 @@
// --(rst-calculator-begin)--
using calculator
= caf::typed_actor<caf::replies_to<caf::add_atom, int, int>::with<int>,
caf::replies_to<caf::sub_atom, int, int>::with<int>>;
= caf::typed_actor<caf::result<int32_t>(caf::add_atom, int32_t, int32_t),
caf::result<int32_t>(caf::sub_atom, int32_t, int32_t)>;
// --(rst-calculator-end)--
CAF_BEGIN_TYPE_ID_BLOCK(remote_spawn, first_custom_type_id)
......@@ -39,11 +39,11 @@ using namespace caf;
calculator::behavior_type calculator_fun(calculator::pointer self) {
return {
[=](add_atom, int a, int b) -> int {
[=](add_atom, int32_t a, int32_t b) {
aout(self) << "received task from a remote node" << endl;
return a + b;
},
[=](sub_atom, int a, int b) -> int {
[=](sub_atom, int32_t a, int32_t b) {
aout(self) << "received task from a remote node" << endl;
return a - b;
},
......@@ -65,8 +65,8 @@ void client_repl(function_view<calculator> f) {
auto usage = [] {
cout << "Usage:" << endl
<< " quit : terminate program" << endl
<< " <x> + <y> : adds two integers" << endl
<< " <x> - <y> : subtracts two integers" << endl
<< " <x> + <y> : adds two int32_tegers" << endl
<< " <x> - <y> : subtracts two int32_tegers" << endl
<< endl;
};
usage();
......@@ -81,15 +81,15 @@ void client_repl(function_view<calculator> f) {
usage();
continue;
}
auto to_int = [](const string& str) -> optional<int> {
auto to_int32_t = [](const string& str) -> optional<int32_t> {
char* end = nullptr;
auto res = strtol(str.c_str(), &end, 10);
if (end == str.c_str() + str.size())
return static_cast<int>(res);
return static_cast<int32_t>(res);
return none;
};
auto x = to_int(words[0]);
auto y = to_int(words[2]);
auto x = to_int32_t(words[0]);
auto y = to_int32_t(words[2]);
if (!x || !y || (words[1] != "+" && words[1] != "-"))
usage();
else
......
......@@ -56,18 +56,31 @@ namespace caf::detail {
template <class>
struct typed_mpi_access;
template <class... In, class... Out>
struct typed_mpi_access<typed_mpi<type_list<In...>, output_tuple<Out...>>> {
template <class Out, class... In>
struct typed_mpi_access<Out(In...)> {
std::string operator()() const {
static_assert(sizeof...(In) > 0, "typed MPI without inputs");
std::vector<std::string> inputs{type_name_v<In>...};
std::string result = "(";
result += join(inputs, ",");
result += ") -> ";
result += type_name_v<Out>;
return result;
}
};
template <class... Out, class... In>
struct typed_mpi_access<result<Out...>(In...)> {
std::string operator()() const {
static_assert(sizeof...(In) > 0, "typed MPI without inputs");
static_assert(sizeof...(Out) > 0, "typed MPI without outputs");
std::vector<std::string> inputs{type_name_v<In>...};
std::vector<std::string> outputs1{type_name_v<Out>...};
std::string result = "caf::replies_to<";
std::string result = "(";
result += join(inputs, ",");
result += ">::with<";
result += ") -> (";
result += join(outputs1, ",");
result += ">";
result += ")";
return result;
}
};
......
......@@ -25,16 +25,6 @@
namespace caf {
template <class T>
struct output_types_of {
// nop
};
template <class In, class Out>
struct output_types_of<typed_mpi<In, Out>> {
using type = Out;
};
template <class T>
struct signatures_of {
using type = typename std::remove_pointer<T>::type::signatures;
......@@ -52,6 +42,9 @@ constexpr bool statically_typed() {
template <class T>
struct is_void_response : std::false_type {};
template <>
struct is_void_response<void> : std::true_type {};
template <>
struct is_void_response<detail::type_list<void>> : std::true_type {};
......
......@@ -56,33 +56,35 @@ struct composed_type<detail::type_list<X, Xs...>, Ys, detail::type_list<>, Rs>
: composed_type<detail::type_list<Xs...>, Ys, Ys, Rs> {};
// Output type matches the input type of the next actor.
template <class... In, class... Out, class... Xs, class Ys, class... MapsTo,
template <class Out, class... In, class... Xs, class Ys, class MapsTo,
class... Zs, class... Rs>
struct composed_type<
detail::type_list<typed_mpi<detail::type_list<In...>, output_tuple<Out...>>,
Xs...>,
Ys,
detail::type_list<
typed_mpi<detail::type_list<Out...>, output_tuple<MapsTo...>>, Zs...>,
struct composed_type<detail::type_list<Out(In...), Xs...>, Ys,
detail::type_list<MapsTo(Out), Zs...>,
detail::type_list<Rs...>>
: composed_type<
detail::type_list<Xs...>, Ys, Ys,
detail::type_list<
Rs..., typed_mpi<detail::type_list<In...>, output_tuple<MapsTo...>>>> {
};
: composed_type<detail::type_list<Xs...>, Ys, Ys,
detail::type_list<Rs..., MapsTo(In...)>> {};
// Output type matches the input type of the next actor.
template <class... Out, class... In, class... Xs, class Ys, class MapsTo,
class... Zs, class... Rs>
struct composed_type<detail::type_list<result<Out...>(In...), Xs...>, Ys,
detail::type_list<MapsTo(Out...), Zs...>,
detail::type_list<Rs...>>
: composed_type<detail::type_list<Xs...>, Ys, Ys,
detail::type_list<Rs..., MapsTo(In...)>> {};
// No match, recurse over Zs.
template <class In, class Out, class... Xs, class Ys, class Unrelated,
class MapsTo, class... Zs, class Rs>
struct composed_type<detail::type_list<typed_mpi<In, Out>, Xs...>, Ys,
detail::type_list<typed_mpi<Unrelated, MapsTo>, Zs...>, Rs>
: composed_type<detail::type_list<typed_mpi<In, Out>, Xs...>, Ys,
template <class Out, class... In, class... Xs, class Ys, class MapsTo,
class... Unrelated, class... Zs, class Rs>
struct composed_type<detail::type_list<Out(In...), Xs...>, Ys,
detail::type_list<MapsTo(Unrelated...), Zs...>, Rs>
: composed_type<detail::type_list<Out(In...), Xs...>, Ys,
detail::type_list<Zs...>, Rs> {};
/// Convenience type alias.
/// @relates composed_type
template <class F, class G>
using composed_type_t = typename composed_type<G, F, F,
detail::type_list<>>::type;
using composed_type_t =
typename composed_type<G, F, F, detail::type_list<>>::type;
} // namespace caf
......@@ -26,73 +26,64 @@
#include "caf/optional.hpp"
#include "caf/replies_to.hpp"
namespace caf {
namespace detail {
namespace caf::detail {
// dmi = deduce_mpi_implementation
template <class T>
struct dmi;
// case #1: function returning a single value
template <class Y, class... Xs>
struct dmi<Y(Xs...)> {
using type = typed_mpi<type_list<std::decay_t<Xs>...>,
output_tuple<implicit_conversions_t<Y>>>;
};
// case #2a: function returning a result<...>
template <class... Ys, class... Xs>
struct dmi<result<Ys...>(Xs...)> {
using type = typed_mpi<type_list<std::decay_t<Xs>...>,
output_tuple<implicit_conversions_t<Ys>...>>;
template <class Out, class... In>
struct dmi<Out(In...)> {
using type = result<implicit_conversions_t<Out>>(std::decay_t<In>...);
};
// case #2b: function returning a std::tuple<...>
template <class... Ys, class... Xs>
struct dmi<std::tuple<Ys...>(Xs...)> {
using type = typed_mpi<type_list<std::decay_t<Xs>...>,
output_tuple<implicit_conversions_t<Ys>...>>;
// case #2: function returning a result<...>
template <class... Out, class... In>
struct dmi<result<Out...>(In...)> {
using type = result<Out...>(std::decay_t<In>...);
};
// case #2c: function returning a std::tuple<...>
template <class... Ys, class... Xs>
struct dmi<delegated<Ys...>(Xs...)> {
using type = typed_mpi<type_list<std::decay_t<Xs>...>,
output_tuple<implicit_conversions_t<Ys>...>>;
// case #3: function returning a delegated<...>
template <class... Out, class... In>
struct dmi<delegated<Out...>(In...)> {
using type = result<Out...>(std::decay_t<In>...);
};
// case #2d: function returning a typed_response_promise<...>
template <class... Ys, class... Xs>
struct dmi<typed_response_promise<Ys...>(Xs...)> {
using type = typed_mpi<type_list<std::decay_t<Xs>...>,
output_tuple<implicit_conversions_t<Ys>...>>;
// case #4: function returning a typed_response_promise<...>
template <class... Out, class... In>
struct dmi<typed_response_promise<Out...>(In...)> {
using type = result<Out...>(std::decay_t<In>...);
};
// case #3: function returning an optional<>
template <class Y, class... Xs>
struct dmi<optional<Y>(Xs...)> : dmi<Y(Xs...)> {};
// case #4: function returning an expected<>
template <class Y, class... Xs>
struct dmi<expected<Y>(Xs...)> : dmi<Y(Xs...)> {};
// -- dmfou = deduce_mpi_function_object_unboxing
template <class T, bool isClass = std::is_class<T>::value>
struct dmfou;
// case #1: const member function pointer
template <class C, class Result, class... Ts>
struct dmfou<Result (C::*)(Ts...) const, false> : dmi<Result(Ts...)> {};
// case #1a: const member function pointer
template <class C, class Out, class... In>
struct dmfou<Out (C::*)(In...) const, false> : dmi<Out(In...)> {};
// case #2: member function pointer
template <class C, class Result, class... Ts>
struct dmfou<Result (C::*)(Ts...), false> : dmi<Result(Ts...)> {};
// case #1b: const member function pointer with noexcept
template <class C, class Out, class... In>
struct dmfou<Out (C::*)(In...) const noexcept, false> : dmi<Out(In...)> {};
// case #3: good ol' function
template <class Result, class... Ts>
struct dmfou<Result(Ts...), false> : dmi<Result(Ts...)> {};
// case #2a: member function pointer
template <class C, class Out, class... In>
struct dmfou<Out (C::*)(In...), false> : dmi<Out(In...)> {};
// case #2b: member function pointer with noexcept
template <class C, class Out, class... In>
struct dmfou<Out (C::*)(In...) noexcept, false> : dmi<Out(In...)> {};
// case #3a: good ol' function
template <class Out, class... In>
struct dmfou<Out(In...), false> : dmi<Out(In...)> {};
// case #3a: good ol' function with noexcept
template <class Out, class... In>
struct dmfou<Out(In...) noexcept, false> : dmi<Out(In...)> {};
template <class T>
struct dmfou<T, true> : dmfou<decltype(&T::operator()), false> {};
......@@ -104,11 +95,12 @@ struct dmfou<timeout_definition<T>, true> {
using type = timeout_definition<T>;
};
} // namespace detail
} // namespace caf::detail
namespace caf {
/// Deduces the message passing interface from a function object.
template <class T>
using deduce_mpi_t = typename detail::dmfou<std::decay_t<T>>::type;
} // namespace caf
/******************************************************************************
* ____ _ _____ *
* / ___| / \ | ___| C++ *
* | | / _ \ | |_ Actor *
* | |___ / ___ \| _| Framework *
* \____/_/ \_|_| *
* *
* Copyright 2011-2018 Dominik Charousset *
* *
* Distributed under the terms and conditions of the BSD 3-Clause License or *
* (at your option) under the terms and conditions of the Boost Software *
* License 1.0. See accompanying files LICENSE and LICENSE_ALTERNATIVE. *
* *
* If you did not receive a copy of the license files, see *
* http://opensource.org/licenses/BSD-3-Clause and *
* http://www.boost.org/LICENSE_1_0.txt. *
******************************************************************************/
#pragma once
#include <type_traits>
#include "caf/replies_to.hpp"
#include "caf/detail/type_list.hpp"
#include "caf/detail/typed_actor_util.hpp"
namespace caf::detail {
template <class T, class... Lists>
struct mpi_splice_by_input;
template <class T>
struct mpi_splice_by_input<T> {
using type = T;
};
template <class T, class... Lists>
struct mpi_splice_by_input<T, type_list<>, Lists...> {
// consumed an entire list without match -> fail
using type = none_t;
};
// splice two MPIs if they have the same input
template <class Input, class... Xs, class... Ys, class... Ts, class... Lists>
struct mpi_splice_by_input<typed_mpi<Input, type_list<Xs...>>,
type_list<typed_mpi<Input, type_list<Ys...>>, Ts...>,
Lists...>
: mpi_splice_by_input<typed_mpi<Input, type_list<Xs..., Ys...>>, Lists...> {};
// skip element in list until empty
template <class MPI, class MPI2, class... Ts, class... Lists>
struct mpi_splice_by_input<MPI, type_list<MPI2, Ts...>, Lists...>
: mpi_splice_by_input<MPI, type_list<Ts...>, Lists...> {};
template <class Result, class CurrentNeedle, class... Lists>
struct input_mapped;
template <class... Rs, class... Lists>
struct input_mapped<type_list<Rs...>, none_t, type_list<>, Lists...> {
using type = type_list<Rs...>;
};
template <class... Rs, class T, class... Ts, class... Lists>
struct input_mapped<type_list<Rs...>, none_t, type_list<T, Ts...>, Lists...>
: input_mapped<type_list<Rs...>, T, type_list<Ts...>, Lists...> {};
template <class... Rs, class T, class FirstList, class... Lists>
struct input_mapped<type_list<Rs...>, T, FirstList, Lists...>
: input_mapped<
type_list<Rs..., typename mpi_splice_by_input<T, Lists...>::type>, none_t,
FirstList, Lists...> {};
template <template <class...> class Target, class ListA, class ListB>
struct mpi_splice;
template <template <class...> class Target, class... Ts, class List>
struct mpi_splice<Target, type_list<Ts...>, List> {
using spliced_list =
typename input_mapped<type_list<>, none_t, type_list<Ts...>, List>::type;
using filtered_list = typename tl_filter_not_type<spliced_list, none_t>::type;
static_assert(tl_size<filtered_list>::value > 0,
"cannot splice incompatible actor handles");
using type = typename tl_apply<filtered_list, Target>::type;
};
} // namespace caf::detail
......@@ -21,6 +21,7 @@
#include <tuple>
#include "caf/delegated.hpp"
#include "caf/fwd.hpp"
#include "caf/replies_to.hpp"
#include "caf/response_promise.hpp"
#include "caf/system_messages.hpp"
......@@ -28,28 +29,8 @@
#include "caf/detail/type_list.hpp"
namespace caf { // forward declarations
template <class... Ts>
class typed_actor;
} // namespace caf
namespace caf::detail {
template <class Arguments, class Signature>
struct input_is_eval_impl : std::false_type {};
template <class Arguments, class Out>
struct input_is_eval_impl<Arguments, typed_mpi<Arguments, Out>>
: std::true_type {};
template <class Arguments>
struct input_is {
template <class Signature>
struct eval : input_is_eval_impl<Arguments, Signature> {};
};
template <class... Ts>
struct make_response_promise_helper {
using type = typed_response_promise<Ts...>;
......@@ -111,7 +92,7 @@ struct static_error_printer<N, -1, Xs, Ys> {
#define CAF_STATICERR(x) \
template <int N, class Xs, class Ys> \
struct static_error_printer<N, x, Xs, Ys> { \
static_assert(N == x, "unexpected handler at index " #x); \
static_assert(N == x, "unexpected handler at index " #x " (0-based)"); \
}
CAF_STATICERR(0);
......@@ -150,4 +131,23 @@ struct extend_with_helper<typed_actor<Xs...>, typed_actor<Ys...>, Ts...>
// nop
};
template <class F>
struct is_normalized_signature {
static constexpr bool value = false;
};
template <class T>
constexpr bool is_decayed = !std::is_reference<T>::value
&& !std::is_const<T>::value
&& !std::is_volatile<T>::value;
template <class... Out, class... In>
struct is_normalized_signature<result<Out...>(In...)> {
static constexpr bool value = (is_decayed<Out> && ...)
&& (is_decayed<In> && ...);
};
template <class F>
constexpr bool is_normalized_signature_v = is_normalized_signature<F>::value;
} // namespace caf::detail
......@@ -23,9 +23,7 @@
#include "caf/detail/type_list.hpp"
namespace caf {
namespace detail {
namespace caf::detail {
// imi = interface_mismatch_implementation
// Precondition: Pos == 0 && len(Xs) == len(Ys) && len(Zs) == 0
......@@ -56,19 +54,17 @@ struct imi<Pos, type_list<timeout_definition<X>>, type_list<>, type_list<>> {
// end of recursion: failure (consumed all Xs but not all Ys)
template <int Pos, class Yin, class Yout, class... Ys>
struct imi<Pos, type_list<>, type_list<typed_mpi<Yin, Yout>, Ys...>,
type_list<>> {
struct imi<Pos, type_list<>, type_list<Yout(Yin), Ys...>, type_list<>> {
static constexpr int value = -1;
using xs = type_list<>;
using ys = type_list<typed_mpi<Yin, Yout>, Ys...>;
using ys = type_list<Yout(Yin), Ys...>;
};
// end of recursion: failure (consumed all Ys but not all Xs)
template <int Pos, class Xin, class Xout, class... Xs>
struct imi<Pos, type_list<typed_mpi<Xin, Xout>, Xs...>, type_list<>,
type_list<>> {
struct imi<Pos, type_list<Xout(Xin), Xs...>, type_list<>, type_list<>> {
static constexpr int value = -2;
using xs = type_list<typed_mpi<Xin, Xout>, Xs...>;
using xs = type_list<Xout(Xin), Xs...>;
using ys = type_list<>;
};
......@@ -81,27 +77,22 @@ struct imi<Pos, type_list<timeout_definition<X>>, type_list<Y, Ys...>,
using ys = type_list<Y, Ys...>;
};
// case #1: exact match
template <int Pos, class In, class Out, class... Xs, class... Ys, class... Zs>
struct imi<Pos, type_list<typed_mpi<In, Out>, Xs...>,
type_list<typed_mpi<In, Out>, Ys...>, type_list<Zs...>>
// case #1a: exact match
template <int Pos, class Out, class... In, class... Xs, class... Ys,
class... Zs>
struct imi<Pos, type_list<Out(In...), Xs...>, type_list<Out(In...), Ys...>,
type_list<Zs...>>
: imi<Pos + 1, type_list<Xs...>, type_list<Zs..., Ys...>, type_list<>> {};
// case #2: match with skip_t
template <int Pos, class In, class... Xs, class Out, class... Ys, class... Zs>
struct imi<Pos, type_list<typed_mpi<In, output_tuple<skip_t>>, Xs...>,
type_list<typed_mpi<In, Out>, Ys...>, type_list<Zs...>>
: imi<Pos + 1, type_list<Xs...>, type_list<Zs..., Ys...>, type_list<>> {};
// case #3: no match at position
template <int Pos, class Xin, class Xout, class... Xs, class Yin, class Yout,
class... Ys, class... Zs>
struct imi<Pos, type_list<typed_mpi<Xin, Xout>, Xs...>,
type_list<typed_mpi<Yin, Yout>, Ys...>, type_list<Zs...>>
: imi<Pos, type_list<typed_mpi<Xin, Xout>, Xs...>, type_list<Ys...>,
type_list<Zs..., typed_mpi<Yin, Yout>>> {};
// case #2: no match at position
template <int Pos, class Xout, class... Xin, class... Xs, class Yout,
class... Yin, class... Ys, class... Zs>
struct imi<Pos, type_list<Xout(Xin...), Xs...>, type_list<Yout(Yin...), Ys...>,
type_list<Zs...>>
: imi<Pos, type_list<Xout(Xin...), Xs...>, type_list<Ys...>,
type_list<Zs..., Yout(Yin...)>> {};
// case #4: no match (error)
// case #3: no match (error)
template <int Pos, class X, class... Xs, class... Zs>
struct imi<Pos, type_list<X, Xs...>, type_list<>, type_list<Zs...>> {
static constexpr int value = Pos;
......@@ -109,7 +100,9 @@ struct imi<Pos, type_list<X, Xs...>, type_list<>, type_list<Zs...>> {
using ys = type_list<Zs...>;
};
} // namespace detail
} // namespace caf::detail
namespace caf {
/// Scans two typed MPI lists for compatibility, returning the index of the
/// first mismatch. Returns the number of elements on a match.
......
......@@ -21,29 +21,22 @@
#include <string>
#include "caf/detail/core_export.hpp"
#include "caf/detail/type_list.hpp"
#include "caf/fwd.hpp"
namespace caf {
/// @cond PRIVATE
/// @private
CAF_CORE_EXPORT std::string
replies_to_type_name(size_t input_size, const std::string* input,
size_t output_size, const std::string* output);
/// @endcond
template <class...>
struct output_tuple {};
template <class Input, class Output>
struct typed_mpi {};
template <class... Is>
struct replies_to {
template <class... Os>
using with = typed_mpi<detail::type_list<Is...>, output_tuple<Os...>>;
using with = result<Os...>(Is...);
};
template <class... Is>
using reacts_to = typed_mpi<detail::type_list<Is...>, output_tuple<void>>;
using reacts_to = result<void>(Is...);
} // namespace caf
......@@ -117,8 +117,8 @@ public:
template <class T = traits, class F = none_t, class OnError = none_t,
class = detail::enable_if_t<T::is_blocking>>
detail::is_handler_for_ef<OnError, error> receive(F f, OnError g) {
static_assert(detail::is_callable<F>::value, "F must provide a single, "
"non-template operator()");
static_assert(detail::is_callable<F>::value,
"F must provide a single, non-template operator()");
static_assert(detail::is_callable_with<OnError, error&>::value,
"OnError must provide an operator() that takes a caf::error");
using result_type = typename detail::get_callable_trait<F>::result_type;
......
......@@ -50,26 +50,32 @@ struct response_type<detail::type_list<>, Xs...> {
};
// case #1: no match
template <class In, class Out, class... Ts, class... Xs>
struct response_type<detail::type_list<typed_mpi<In, Out>, Ts...>, Xs...>
: response_type<detail::type_list<Ts...>, Xs...> {};
// case #2: match
template <class... Out, class... Ts, class... Xs>
struct response_type<
detail::type_list<typed_mpi<detail::type_list<Xs...>, output_tuple<Out...>>,
Ts...>,
Xs...> {
template <class Out, class... In, class... Fs, class... Xs>
struct response_type<detail::type_list<Out(In...), Fs...>, Xs...>
: response_type<detail::type_list<Fs...>, Xs...> {};
// case #2.a: match `result<Out...>(In...)`
template <class... Out, class... In, class... Fs>
struct response_type<detail::type_list<result<Out...>(In...), Fs...>, In...> {
static constexpr bool valid = true;
using type = detail::type_list<Out...>;
using tuple_type = std::tuple<Out...>;
using delegated_type = delegated<Out...>;
};
/// Computes the response message for input `Xs...` from the list
/// of message passing interfaces `Ts`.
template <class Ts, class... Xs>
using response_type_t = typename response_type<Ts, Xs...>::type;
// case #2.b: match `Out(In...)`
template <class Out, class... In, class... Fs>
struct response_type<detail::type_list<Out(In...), Fs...>, In...> {
static constexpr bool valid = true;
using type = detail::type_list<Out>;
using tuple_type = std::tuple<Out>;
using delegated_type = delegated<Out>;
};
/// Computes the response message for input `In...` from the list of message
/// passing interfaces `Fs`.
template <class Fs, class... In>
using response_type_t = typename response_type<Fs, In...>::type;
/// Unboxes `Xs` and calls `response_type`.
template <class Ts, class Xs>
......
......@@ -26,7 +26,7 @@
#include "caf/actor_system.hpp"
#include "caf/composed_type.hpp"
#include "caf/decorator/sequencer.hpp"
#include "caf/detail/mpi_splice.hpp"
#include "caf/fwd.hpp"
#include "caf/intrusive_ptr.hpp"
#include "caf/make_actor.hpp"
#include "caf/replies_to.hpp"
......@@ -37,19 +37,8 @@
namespace caf {
template <class... Sigs>
class typed_event_based_actor;
namespace io {
template <class... Sigs>
class typed_broker;
} // namespace io
/// Identifies a statically typed actor.
/// @tparam Sigs Signature of this actor as `replies_to<...>::with<...>`
/// parameter pack.
/// @tparam Sigs Function signatures for all accepted messages.
template <class... Sigs>
class typed_actor : detail::comparable<typed_actor<Sigs...>>,
detail::comparable<typed_actor<Sigs...>, actor>,
......@@ -58,6 +47,10 @@ class typed_actor : detail::comparable<typed_actor<Sigs...>>,
public:
static_assert(sizeof...(Sigs) > 0, "Empty typed actor handle");
static_assert((detail::is_normalized_signature_v<Sigs> && ...),
"Function signatures must be normalized to the format "
"'result<Out...>(In...)', no qualifiers or references allowed");
// -- friend types that need access to private ctors
friend class local_actor;
......
......@@ -29,14 +29,22 @@
namespace caf ::detail {
// converts a list of replies_to<...>::with<...> elements to a list of
// lists containing the replies_to<...> half only
template <class Signature>
struct input_args;
template <class R, class... Ts>
struct input_args<R(Ts...)> {
using type = type_list<Ts...>;
};
// Converts a list of function signatures to lists of inputs (dropping the
// return type).
template <class List>
struct input_only;
template <class... Ts>
struct input_only<detail::type_list<Ts...>> {
using type = detail::type_list<typename Ts::input_types...>;
struct input_only<type_list<Ts...>> {
using type = type_list<typename input_args<Ts>::type...>;
};
using skip_list = detail::type_list<skip_t>;
......
......@@ -284,7 +284,7 @@ CAF_TEST(string_representation) {
CAF_TEST(mpi_string_representation) {
CAF_CHECK(sys.message_types(a0.dt).empty());
std::set<std::string> st_expected{"caf::replies_to<int32_t>::with<int32_t>"};
std::set<std::string> st_expected{"(int32_t) -> (int32_t)"};
CAF_CHECK_EQUAL(st_expected, sys.message_types(a0.st));
CAF_CHECK_EQUAL(st_expected, sys.message_types<testee_actor>());
}
......
......@@ -38,9 +38,13 @@ using namespace std::string_literals;
namespace {
static_assert(std::is_same<reacts_to<int, int>, result<void>(int, int)>::value);
static_assert(std::is_same<replies_to<double>::with<double>,
result<double>(double)>::value);
// check invariants of type system
using dummy1
= typed_actor<reacts_to<int, int>, replies_to<double>::with<double>>;
using dummy1 = typed_actor<result<void>(int, int), result<double>(double)>;
using dummy2 = dummy1::extend<reacts_to<ok_atom>>;
......@@ -296,10 +300,9 @@ CAF_TEST(event_testee_series) {
auto et = self->spawn<event_testee>();
CAF_MESSAGE("et->message_types() returns an interface description");
typed_actor<replies_to<get_state_atom>::with<string>> sub_et = et;
std::set<string> iface{"caf::replies_to<get_state_atom>::with<std::string>",
"caf::replies_to<std::string>::with<void>",
"caf::replies_to<float>::with<void>",
"caf::replies_to<int32_t>::with<int32_t>"};
std::set<string> iface{"(get_state_atom) -> (std::string)",
"(std::string) -> (void)", "(float) -> (void)",
"(int32_t) -> (int32_t)"};
CAF_CHECK_EQUAL(join(sub_et->message_types(), ","), join(iface, ","));
CAF_MESSAGE("the testee skips messages to drive its internal state machine");
self->send(et, 1);
......@@ -323,7 +326,7 @@ CAF_TEST(string_delegator_chain) {
// run test series with string reverter
auto aut = self->spawn<monitored>(string_delegator,
sys.spawn(string_reverter), true);
std::set<string> iface{"caf::replies_to<std::string>::with<std::string>"};
std::set<string> iface{"(std::string) -> (std::string)"};
CAF_CHECK_EQUAL(aut->message_types(), iface);
inject((string), from(self).to(aut).with("Hello World!"s));
run();
......
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