-
Notifications
You must be signed in to change notification settings - Fork 2
/
TypeInspection.tex
82 lines (54 loc) · 5.64 KB
/
TypeInspection.tex
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
\section{Type Inspection (Serialization and String Conversion)}
\label{type-inspection}
\lib is designed with distributed systems in mind. Hence, all message types must be serializable and need a platform-neutral, unique name that is configured at startup \see{add-custom-message-type}. Using a message type that is not serializable causes a compiler error \see{unsafe-message-type}. \lib 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 with a single free function. The signature for a class \lstinline^my_class^ is always as follows:
\begin{lstlisting}
template <class Inspector>
typename Inspector::result_type inspect(Inspector& f, my_class& x) {
return f(...);
}
\end{lstlisting}
The function \lstinline^inspect^ passes meta information and data fields to the variadic call operator of the inspector. The following example illustrates an implementation for \lstinline^inspect^ for a simple POD struct.
\lstinputlisting[linerange={23-33}]{../examples/custom_type/custom_types_1.cpp}
The inspector recursively inspects all data fields and has builtin support for (1) \lstinline^std::tuple^, (2) \lstinline^std::pair^, (3) C arrays, (4) any container type with \lstinline^x.size()^, \lstinline^x.empty()^, \lstinline^x.begin()^ and \lstinline^x.end()^.
We consciously made the inspect API as generic as possible to allow for extensibility. This allows users to use \lib's types in other contexts, to implement parsers, etc.
\subsection{Inspector Concept}
The following concept class shows the requirements for inspectors. The placeholder \lstinline^T^ represents any user-defined type. For example, \lstinline^error^ when performing I/O operations or some integer type when implementing a hash function.
\begin{lstlisting}
Inspector {
using result_type = T;
if (inspector only requires read access to the state of T)
static constexpr bool reads_state = true;
else
static constexpr bool writes_state = true;
template <class... Ts>
result_type operator()(Ts&&...);
}
\end{lstlisting}
A saving \lstinline^Inspector^ is required to handle constant lvalue and rvalue references. A loading \lstinline^Inspector^ must only accept mutable lvalue references to data fields, but still allow for constant lvalue references and rvalue references to annotations.
\subsection{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 \lstinline^caf::meta^ and derive from \lstinline^caf::meta::annotation^. An inspector can query whether a type \lstinline^T^ is an annotation with \lstinline^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 \lib:
\begin{itemize}
\item \lstinline^type_name(n)^: Display type name as \lstinline^n^ in human-friendly output (position before data fields).
\item \lstinline^hex_formatted()^: Format the following data field in hex format.
\item \lstinline^omittable()^: Omit the following data field in human-friendly output.
\item \lstinline^omittable_if_empty()^: Omit the following data field if it is empty in human-friendly output.
\item \lstinline^omittable_if_none()^: Omit the following data field if it equals \lstinline^none^ in human-friendly output.
\item \lstinline^save_callback(f)^: Call \lstinline^f^ when serializing (position after data fields).
\item \lstinline^load_callback(f)^: Call \lstinline^f^ after deserializing all data fields (position after data fields).
\end{itemize}
\subsection{Backwards and Third-party Compatibility}
\lib evaluates common free function other than \lstinline^inspect^ in order to simplify users to integrate \lib into existing code bases.
Serializers and deserializers call user-defined \lstinline^serialize^ functions. Both types support \lstinline^operator&^ as well as \lstinline^operator()^ for individual data fields. A \lstinline^serialize^ function has priority over \lstinline^inspect^.
When converting a user-defined type to a string, \lib calls user-defined \lstinline^to_string^ functions and prefers those over \lstinline^inspect^.
\subsection{Whitelisting Unsafe Message Types}
\label{unsafe-message-type}
Message types that are not serializable cause compile time errors when used in actor communication. When using \lib for concurrency only, this errors can be suppressed by whitelisting types with \lstinline^CAF_ALLOW_UNSAFE_MESSAGE_TYPE^. The macro is defined as follows.
\lstinputlisting[linerange={50-54}]{../libcaf_core/caf/allowed_unsafe_message_type.hpp}
\clearpage
\subsection{Splitting Save and Load Operations}
If loading and storing cannot be implemented in a single function, users can query whether the inspector is loading or storing. For example, consider the following class \lstinline^foo^ with getter and setter functions and no public access to its members.
\lstinputlisting[linerange={20-49}]{../examples/custom_type/custom_types_3.cpp}
\clearpage
Since there is no access to the data fields \lstinline^a_^ and \lstinline^b_^ (and assuming no changes to \lstinline^foo^ are possible), we need to split our implementation of \lstinline^inspect^ as shown below.
\lstinputlisting[linerange={76-103}]{../examples/custom_type/custom_types_3.cpp}
The purpose of the scope guard in the example above is to write the content of the temporaries back to \lstinline^foo^ at scope exit automatically. Storing the result of \lstinline^f(...)^ in a temporary first and then writing the changes to \lstinline^foo^ is not possible, because \lstinline^f(...)^ can return \lstinline^void^.