Sharing RPC convention¶
Unpadded has been designed in such a way that you don’t need to generate code to make it works. All the information needed to define the RPC convention for your project can be contained in a single header file. That header file must be included in both the caller and the callee codes.
For example, if you are working with git, you may have a repository dedicated to contain your RPC convention headers. This repository is then added as a submodule to the caller and the callee repositories. With this approach, the two repositories can be conveniently synchronized just by updating the RPC repository to its latest version.
In this page, it will be explained what to put in the RPC headers.
Basics : defining a keyring¶
Template lvalue reference parameters trick¶
Note
Keyrings can deal with anything invocable, like lambda expressions and function objects, but for the sake of clarity, this section will only deal with keyrings and plain functions. For binding lamnda expressions and functions objects, see the next section Binding lambda expressions and invocable objects to a keyring.
Keyrings rely on a small trick with C++ template reference parameters. When instanciating a template with a reference to a function or a variable, this function or variable ought not to be defined if you don’t need it to be defined when instanciating the template. Take the following example:
#include <iostream>
extern uint64_t x;
template<auto &X>
auto f() {
return sizeof X;
}
int main() {
std::cout << "Size of `x`: " << f<x>() << std::endl;
return 0;
}
Size of `x`: 8
x
has only been declared, and might never be defined. But that’s okay, we don’t need it to be defined. We only need its size, which can be deduced from its type. Therefore, the compiler won’t complain about x
being undefined.
Defining a keyring¶
Keyrings use the same feature, by holding a list of references to the functions available for RPC. These functions don’t need to be defined, since only two things need to be known to establish the RPC convention:
The indices associated with each function, which can be deduced from the position of each function in the list (simply put, the first function in the list has the index
0
, the second has the index1
, etc.)The signature of each function, used by the caller to know which parameters the callee expects and what the type of the value sent back by the callee is.
Example¶
Imagine you’ve got a toy car with a master device and a slave device. The latter handles the wheels. In pratice, this is how a (naive) keyring would be defined.
#pragma once
#include <cstdint>
#include <upd/format.hpp>
#include <upd/keyring.hpp>
#include <upd/typelist.hpp>
void set_forward_speed(std::uint32_t);
void set_left_steering_speed(std::uint32_t);
void set_right_steering_speed(std::uint32_t);
constexpr upd::keyring motion_keyring{upd::flist<set_forward_speed, set_left_steering_speed, set_right_steering_speed>,
upd::little_endian,
upd::twos_complement};
Warning
For obvious compatibility reasons between the master device and the slave device, please use fixed-width integer types.
motion_keyring.hpp
must then be included both in the code of the master and in the code of the slave. Furthermore, the slave must obviously define set_forward_speed
, set_left_steering_speed
, set_right_steering_speed
, but not the master, since it will never execute these functions itself.
Binding lambda expressions and invocable objects to a keyring¶
Altought the previous section showed examples with function references, keyrings also accept functors. Just add them as you would add a function in the list.
Example¶
We are just replacing some of the functions in the previous example by functors.
#pragma once
#include <cstdint>
#include <upd/format.hpp>
#include <upd/keyring.hpp>
#include <upd/typelist.hpp>
struct my_callable {
double factor;
explicit my_callable(double factor) : factor{factor} {}
void operator()(std::uint32_t);
};
void set_forward_speed(std::uint32_t);
auto set_left_steering_speed = [](std::uint32_t x) { /** implementation **/ };
extern my_callable set_right_steering_speed{1.5};
constexpr upd::keyring motion_keyring{upd::flist<set_forward_speed, set_left_steering_speed, set_right_steering_speed>,
upd::little_endian,
upd::twos_complement};
In that case, set_left_steering_speed
needs to be defined because it is a lambda expression, but my_callable
constructor doesn’t. It has been done in this example for the sake of clarity.
Pre C++17 support¶
You might have noticed that since automatic type deduction of template non-type parameter is not available until C++17, the previous examples won’t compile in C++11 or C++14. If you can’t afford to compile with C++17 or above, Unpadded provides a (rather ugly) workaround with the unevaluated
class template and the make_keyring
template function.
Example¶
Here is what it would look like:
#pragma once
#include <cstdint>
#include <upd/format.hpp>
#include <upd/keyring.hpp>
#include <upd/typelist.hpp>
#include <upd/unevaluated.hpp>
void set_forward_speed(std::uint32_t);
void set_left_steering_speed(std::uint32_t);
void set_right_steering_speed(std::uint32_t);
constexpr auto motion_keyring =
upd::make_keyring(upd::flist_t<unevaluated<decltype(set_forward_speed), set_forward_speed>,
unevaluated<decltype(set_left_steering_speed), set_left_steering_speed>,
unevaluated<decltype(set_right_steering_speed), set_right_steering_speed>>{},
upd::little_endian,
upd::twos_complement);
If you find this to verbose, the UPD_CTREF
macro can ease your pain:
#pragma once
#include <cstdint>
#include <upd/format.hpp>
#include <upd/keyring.hpp>
#include <upd/typelist.hpp>
#include <upd/unevaluated.hpp>
void set_forward_speed(std::uint32_t);
void set_left_steering_speed(std::uint32_t);
void set_right_steering_speed(std::uint32_t);
constexpr auto motion_keyring = upd::make_keyring(upd::flist_t<UPD_CTREF(set_forward_speed),
UPD_CTREF(set_left_steering_speed),
UPD_CTREF(set_right_steering_speed)>{},
upd::little_endian,
upd::twos_complement);
API References¶
keyring
¶
-
template<endianess Endianess, signed_mode Signed_Mode, typename ...Hs>
class keyring¶ Holds a set of callbacks and index them at compile-time.
A keyring introduces a RPC convention between the caller and the callee. The caller must have access to the declarations of the callbacks given to the keyring so it knows what are the parameters to send for each action and what type of value it has to except in return. However, only the callee must have access to the callback definitions, in order to execute any action request from the caller. Each callback is associated with a key in the keyring. For each callback, its key can be used by the caller to build a packet to send to the callee or interpret the value received from the callee. Keyrings also holds the endianess and signed number representation of the data in the packets.
See also
key for further information on how packets are generated.
- Template Parameters:
Endianess, Signed_Mode – Serialization parameters
Hs – Unevaluated references to callbacks available for calling
Public Types
-
using flist_t = upd::flist_t<unevaluated<Fs, Functions>...>¶
Typelist containing unevaluated references to the callbacks.
-
using signatures_t = typelist_t<detail::signature_t<Fs>...>¶
Typelist containing the signatures of the callbacks.
-
using index_t = detail::smallest_unsigned_t<sizeof...(Fs)>¶
Type of the index prepended to the payload when sending a packet to the callee.
-
template<typename H>
using key_t = key<index_t, detail::find<flist_t, H>::value, typename std::remove_pointer<typename H::type>::type, Endianess, Signed_Mode>¶ Type of the key associated to an unevaluated reference to a callback.
- Template Parameters:
H – Unevaluated reference to a callback managed by the keyring
Public Functions
-
inline explicit constexpr keyring(upd::flist_t<unevaluated<Fs, Functions>...>, endianess_h<Endianess>, signed_mode_h<Signed_Mode>)¶
(C++17) Create a keyring managing the given callbacks with the provided serialization parameters
-
template<typename H>
inline constexpr key_t<H> get(H) const¶ Make a key associated with the given unevaluated reference to a callback.
- Template Parameters:
H – Unevaluated reference to a callback managed by the keyring
-
template<auto &Ftor>
inline constexpr auto get() const¶ Make a key associated with the given unevaluated reference to a callback.
- Template Parameters:
Ftor – One of the callbacks managed by the keyring
Public Static Attributes
-
static constexpr auto endianess = Endianess¶
Endianess of the data in the packets built by the keys.
-
static constexpr auto signed_mode = Signed_Mode¶
Signed number representation of the data in the packets built by the keys.
Related
-
template<endianess Endianess, signed_mode Signed_Mode, typename ...Hs>
constexpr keyring<Endianess, Signed_Mode, Hs...> make_keyring(flist_t<Hs...>, endianess_h<Endianess>, signed_mode_h<Signed_Mode>)¶ Make a keyring objects.
unevaluated
¶
flist
¶
-
template<auto&... Functions>
constexpr flist_t<unevaluated<detail::remove_cv_ref_t<decltype(Functions)>*, &Functions>...> upd::flist¶ Typelist holding references to objects with static storage duration.
- Template Parameters:
Functions – Invocable objects bound by reference
-
template<typename ...Fs, Fs... Functions>
struct flist_t<unevaluated<Fs, Functions>...> : public upd::typelist_t<unevaluated<Fs, Functions>...>¶ Typelist holding references to objects with static storage duration.
- Template Parameters:
Functions – Invocable objects bound by reference