Type Inspection¶
CAF is designed with distributed systems in mind. Hence, all message types must
be serializable and need a platform-neutral, unique name (see
Adding Custom Message Types). Using a message type that is not serializable
causes a compiler error (see Whitelisting Unsafe Message Types). CAF serializes
individual elements of a message by using the inspection API. This API allows
users to provide code for serialization as well as string conversion, usually
with a single free function. The signature for a class my_class
is always as
follows:
template <class Inspector>
typename Inspector::result_type inspect(Inspector& f, my_class& x) {
return f(...);
}
The function inspect
passes meta information and data fields to the
variadic call operator of the inspector. The following example illustrates an
implementation of inspect
for a simple POD called foo
.
struct foo {
std::vector<int> a;
int b;
};
template <class Inspector>
typename Inspector::result_type inspect(Inspector& f, foo& x) {
return f(meta::type_name("foo"), x.a, x.b);
}
The inspector recursively inspects all data fields and has builtin support for
std::tuple
, std::pair
, arrays, as well as containers that provide the
member functions size
, empty
, begin
and end
.
We consciously made the inspect API as generic as possible to allow for extensibility. This allows users to use CAF’s types in other contexts, to implement parsers, etc.
Note
When converting a user-defined type to a string, CAF calls user-defined
to_string
functions and prefers those over inspect
.
Inspector Concept¶
The following concept class shows the requirements for inspectors. The
placeholder T
represents any user-defined type. Usually error
or
error_code
.
Inspector {
using result_type = T;
static constexpr bool reads_state = ...;
static constexpr bool writes_state = ...;
template <class... Ts>
result_type operator()(Ts&&...);
}
A saving Inspector
is required to handle constant lvalue and rvalue
references. A loading Inspector
must only accept mutable lvalue
references to data fields, but still allow for constant lvalue references and
rvalue references to annotations.
Inspectors that only visit data fields (such as a serializer) sets
reads_state
to true
and writes_state
to false
. Inspectors that
override data fields (such as a deserializer) assign the inverse values.
These compile-time constants can be used in if constexpr
statements or to
select different inspect
overloads with enable_if
.
Annotations¶
Annotations allow users to fine-tune the behavior of inspectors by providing
addition meta information about a type. All annotations live in the namespace
caf::meta
and derive from caf::meta::annotation
. An
inspector can query whether a type T
is an annotation with
caf::meta::is_annotation<T>::value
. Annotations are passed to the
call operator of the inspector along with data fields. The following list shows
all annotations supported by CAF:
type_name(n)
: Display type name asn
in human-friendly output (position before data fields).hex_formatted()
: Format the following data field in hex format.omittable()
: Omit the following data field in human-friendly output.omittable_if_empty()
: Omit the following data field in human-friendly output if it is empty.omittable_if_none()
: Omit the following data field in human-friendly output if it equalsnone
.save_callback(f)
: Callf
ifreads_state == true
. Pass this callback after the data fields.load_callback(f)
: Callf
writes_state == true
. Pass this callback after the data fields.
Backwards and Third-party Compatibility¶
CAF evaluates common free function other than inspect
in order to simplify
users to integrate CAF into existing code bases.
Serializers and deserializers call user-defined serialize
functions. Both
types support operator&
as well as operator()
for individual data
fields. A serialize
function has priority over inspect
.
Whitelisting Unsafe Message Types¶
Message types that are not serializable cause compile time errors when used in
actor communication. For messages that never cross the network, this errors can
be suppressed by whitelisting types with CAF_ALLOW_UNSAFE_MESSAGE_TYPE
. The
macro is defined as follows.
#define CAF_ALLOW_UNSAFE_MESSAGE_TYPE(type_name) \
namespace caf { \
template <> \
struct allowed_unsafe_message_type<type_name> : std::true_type {}; \
}
Keep in mind that unsafe
means that your program runs into undefined
behavior (or segfaults) when you break your promise and try to serialize
messages that contain unsafe message types.
Saving and Loading with Getters and Setters¶
Many classes shield their member variables with getter and setter functions.
This can be addressed by declaring the inspect
function as friend
, but
only when not dealing with 3rd party library types. For example, consider the
following class foo
with getter and setter functions and no public access to
its members.
class foo {
public:
foo(int a0 = 0, int b0 = 0) : a_(a0), b_(b0) {
// nop
}
foo(const foo&) = default;
foo& operator=(const foo&) = default;
int a() const {
return a_;
}
void set_a(int val) {
a_ = val;
}
int b() const {
return b_;
}
void set_b(int val) {
b_ = val;
}
private:
int a_;
int b_;
};
Since there is no access to the data fields a_
and b_
(and assuming no
changes to foo
are possible), we can serialize or deserialize from/to local
variables and use a load callback to write back to the object when loading
state.
template <class Inspector>
typename Inspector::result_type inspect(Inspector& f, foo& x) {
auto a = x.a();
auto b = x.b();
auto load = meta::load_callback([&]() -> error {
// Write back to x when loading values from the inspector.
x.set_a(a);
x.set_b(b);
return none;
});
return f(meta::type_name("foo"), a, b, load);
}
For more complicated cases, we can also split the inspect overload as follows:
template <class Inspector>
typename std::enable_if<Inspector::reads_state,
typename Inspector::result_type>::type
inspect(Inspector& f, my_class& x) {
// ... serializing ...
}
template <class Inspector>
typename std::enable_if<Inspector::writes_state,
typename Inspector::result_type>::type
inspect(Inspector& f, my_class& x) {
// ... deserializing ...
}