Citidi (Compile Time Dispatch) - a library that allows to query some data with Marker
types.
Additionally, it provides a way to iterate over some group of data with the same Marker
types, but possibly with different value types.
The traditional approach to store some data with different types and meanings is to use variables:
// Input
LidarCalibrationData lidar_calibration_data{};
UltrasoundCalibrationData ultrasound_calibration_data{};
LidarObjectsData lidar_objects_data{};
UltrasoundObjectsData ultrasound_objects_data{};
// Output
LidarHealthData lidar_health_data{};
UltrasoundHealthData ultrasound_health_data{};
Variables is a basic part of almost every programming language. But there is one inconvenience with C++ - it does not have reflection (until C++26), it does not provide standard ways to iterate over group of variables or query them by part of the name.
Citidi is trying to solve this issue and the main idea here - every value is tagged with some Marker
types.
This tagging and wide C++ metaprogramming possibilities allow to query data by Marker
types and iterate over group of data.
Dispatcher
object is used to store all required data and create slices (conditional links) when it is needed.
In the following example, Dispatcher
object type is defined to store sensor data:
using DataDispatcher = Dispatcher<
// Input
Element<LidarCalibrationData, Sensor<kLidarId>, Calibration>,
Element<UltrasoundCalibrationData, Sensor<kUltrasoundId>, Calibration>,
Element<LidarObjectsData, Sensor<kLidarId>, Objects>,
Element<UltrasoundObjectsData, Sensor<kUltrasoundId>, Objects>,
// Output
Element<HealthData, Sensor<kLidarId>, Health>,
Element<HealthData, Sensor<kUlTrasound>, Health>>;
DataDispatcher disp{};
Element
is a simple data cell. It contains actual data and all Marker
types related to the value.
Slice
object is a link to the base data object (Dispatcher
or another Slice
), but scope of the data is decreased by a Condition
object:
auto calibration_slice = disp.Get<Condition>();
If there is only one element in a slice, it might be taken by Single
function:
auto value = slice_object.Single();
Or by Get
function with index or condition:
auto value = slice_object.Get<3>();
auto value = slice_object.Get<Condition>();
Indexing might be used to iterate over a slice of data:
compile_time_for<slice_object.Size()>(
[&slice_object](const auto i)
{
auto& value = slice_object.template Get<i.value>().data;
PrintValue(value);
});
Condition
object is used to build a logic to choose data for slicing. It must have the following function to be used in a condition chain:
template<typename MarkersTupleType>
constexpr static bool Evaluate();
Where MarkersTupleType
is a tuple with all marker types related to a value.
BoolConst
is used to define some bool constant. It might be used to query all data from a Dispatcher
object or for prototypes, where the required data is not defined yet.
Or
object is used to apply logic or operation for results from provided Condition
objects.
And
object is used to apply logic and operation for results from provided Condition
objects.
Not
object is used to invert the result of a provided Condition
.
With
object is used to find given Marker
types inside MarkersTupleType
when the Evaluate
function is called.
WithExactly
object is used to find an exact match between tuple with given Marker
types and MarkersTupleType
when the Evaluate
function is called.
Without
object is used to find given Marker
types inside MarkersTupleType
when the Evaluate
function is called and invert the result.