emlabcpp
modern opinionated embedded C++ library
emlabcpp Documentation

C++ embedded library

Documentation

An opinionated C++20 library focusing on embedded development. It provides a wide set of tools, from complex mechanisms (protocol library) to simple utilites (view).

Want help? Find veverak on Amulius Embedded Discord

Installation

Repository is at https://github.com/koniarik/emlabcpp The prefered of getting the library for now is via fetchcontent:

FetchContent_Declare(
emlabcpp
GIT_REPOSITORY https://github.com/koniarik/emlabcpp
GIT_TAG v1.1
)
FetchContent_MakeAvailable(emlabcpp)

Components

The library can be view as a set of components. These are organized based on the root header file for the said component.

algorithm.h

Contains a set of algorithms similar to <algorithm> standard library, with a major change. Functions take as an argument a container itself, rather than iterators. Most of the functions are also able to work with std::tuple. This is expanded with other short functions representing simple algorithms.

The two core functions are find_if and for_each, both are implemented with variant over containers and tuples.

std::tuple< int, std::string > tpl_data;
std::vector< int > vec_data;
for_each(tpl_data, [&]( const auto & item ){
std::cout << item << '\n';
});
for_each(vec_data, [&]( int item ){
std::cout << item << '\n';
});
std::size_t index = find_if(tpl_data, [&]( auto item ){
return std::is_same_v< decltype(item), std::string >;
});
auto iter = find_if(vec_data, [&]( int i ){
return i != 0;
});
constexpr void for_each(Container &&cont, UnaryCallable &&f)
Applies unary callable 'f' to each element of container 'cont'.
Definition: algorithm.h:171
constexpr auto find_if(Container &&cont, PredicateCallable &&f=std::identity())
Returns iterator for first item, for which call to predicate f(*iter) holds true.
Definition: algorithm.h:112

See examples for an overview of algorithms.

assert.h

Provides EMLABCPP_ASSERT(c) macro that has three states:

  1. Does nothing - node instructions are emitted.
  2. Calls assert(c) if EMLABCPP_ASSERT_NATIVE is defined.
  3. Calls EMLABCPP_ASSERT_FUNC(c) if EMLABCPP_ASSERT_FUNC is defined.

By default, none of the macros are defined.

bounded.h

Provides bounded<T,Min,Max> class that envelops type T and provides interface that enforces T to remain within bounds Min and Max. Can be used to relay value constrains in type information.

For example:

using power = bounded<int, -1024, 1024>;
physical_quantity< 2, 1, -3, 0, 0, 0, 0, 0, 0 > power
Definition: physical_quantity.h:126

concepts.h

A set of C++ concepts designed for implementing a checks inside the library.

defer.h

Simple utility class to setup code segments executed after the end of scope:

{
defer d = []{
// do something on exit;
};
// ...
}

This enforces that "finished" message is send after the exec_job call finishes.

enum.h

Provides a function that converts enum value into string representative, this either does simple int->string conversion or uses magic_enum library if it is enabled with EMLABCPP_USE_MAGIC_ENUM define.

For example:

enum e_type {
FOO = 0,
};
std::cout << convert_enum(FOO) << std::endl;

Outputs FOO if magic_enum is enabled and 0 otherwise.

iterator.h

Contains generic_iterator<Derived> CRTP baseclass. This simplifies implementation of custom iterators, as most of the methods/operators we expect of iterators can be implemented based on a small set of functions. (operator+, operator++(int), operator++ can be implemetned with operator+=)

For implementing iterator, you only provide the basic subset for this class and it takes care of the rest. Keep in mind that this is for "general" use case, and for optimallity you may be better served with fully custom iterator.

iterators/numeric.h

Iterator that mimics real data container of sequence of numbers. The number is stored inside the iterator and when iterator is advanced, so is the internal value changed. Use functions like range(from,to) to creates a range from this iterators.

std::vector<int> vec_data = {1,2,3,4,5};
for(std::size_t i : range(vec_data.size()-1))
{
std::cout << vec_data[i] << '-' << vec_data[i+1] << '\n';
}
constexpr view< iterators::numeric_iterator< Numeric > > range(Numeric from, Numeric to)
Builds numeric view over interval [from, to)
Definition: range.h:34

match.h

Match is mechanism similar to std::visit(Callable,Variant), but one that changes the order of arguments and allows mutliple callables. The signature is along the lines of: match(Variant,Callable...). The implementation composes callables together and let's method resolution pick the appropaite callable for alternative present in the variant.

This makes it possible to write constructs such as these:

std::variant<int, std::string> states;
match(states,
[&](int x){
// ...
},
[&](std::string&){
// ...
});
decltype(auto) match(Variant &&var, Callables &&... cals)
Definition: match.h:55

Here, the function executions a lambda for the state that is present in the variant, you can think about it like a switch but for variant.

physical_quantity.h

System of physical quantities based on quantity.h. These represent physical quantity that stores it's unit in it's own type (templated).

This makes it possible to have velocity/length/time represented as distinct types. Also, the result type of operations like length divided by time is of type velocity.

This increases safety of physical computations, as it enforces correct units in the math. The operator<< is overloaded to output units for the type, such as: 0.25m

auto uniform_accel = [](distance s0, velocity v0, acceleration a, timeq t) -> distance
{
return s0 + v0*t + 0.5F*a*t*t;
};
std::cout << distance{0.25};
physical_quantity< 1, 0, -1, 0, 0, 0, 0, 0, 0 > velocity
Definition: physical_quantity.h:123
length distance
Definition: physical_quantity.h:129
physical_quantity< 0, 0, 1, 0, 0, 0, 0, 0, 0 > timeq
Definition: physical_quantity.h:112
physical_quantity< 1, 0, -2, 0, 0, 0, 0, 0, 0 > acceleration
Definition: physical_quantity.h:119

pid.h

Basic PID regulator implementation using floats, templated based on the time type.

protocol.h

The protocol library serializes and deserialize C++ data structures into binary messages. The principle is that the protocol is defined with library types. Based on the definition, protocol_handler<Def> provides routines for serialization and deserialization of data structures corresponding to said definition.

In case of work with simple robot, we can create simple protocol:

using distance = unsigned;
using angle = int;
enum robot_cmds : uint8_t
{
FORWARD = 0,
LEFT = 1,
RIGHT = 2
};
struct robot_cmd_group
: protocol::command_group< std::endian::little >::with_commands<
protocol::command< FORWARD >::with_args< distance >,
protocol::command< LEFT >::with_args< angle >,
protocol::command< RIGHT >::with_args< angle >
>
{};
physical_quantity< 0, 0, 0, 0, 0, 0, 0, 1, 0 > angle
Definition: physical_quantity.h:117

See examples for more detailed explanation.

quantity.h

Simple thin overlay over numeric types, that gives abillity to implement strongly typed numeric types. This is handy in case you want to enforce correctness on type level. See implementation of physical_quantity as an example.

static_circular_buffer.h

Basic implementation of circular buffer with static maximal size, that can store non-default constructible elements. No dynamic allocation is used.

static_circular_buffer<int, 256> buffr;
for(int i : {0,1,2,3,4,5,6})
{
buffr.push_back(i);
}
for(int i : buffr)
{
std::cout << i << ",";
}
while(!buffr.empty())
{
buffr.pop_front();
}

static_vector.h

Basic implementation of vector with static maximal size, that can store non-default constructible elements. No dynamic allocation is used.

types.h

A library of helpers for type inspection, this contains types similar to type_traits of standard library. This follows the pattern of std:: library - type check is structure with ::value/::type attributes and using for _v/_t suffixed aliases exists.

using data = std::vector<int>;
auto fun = [](int i) -> std::string
{
return std::to_string(i);
};
using fun_t = decltype(fun);
static_assert(std::is_same_v<mapped_t<data, fun_t>, std::string>);
constexpr pointer data() noexcept
Returns pointer to first item of the storage.
Definition: static_storage.h:108

view.h

Simple container storing a pair of iterators - non-owning container of data. This make it possible to pass a subset of container to API expecting a container itself. It is also more sensible for functions that would return std::pair of iterators.

std::vector<int> vec_data{1,2,3,4,5,6};
for(int i : view{vec_data})
{
std::cout << i << ',';
}
std::cout << '\n';
for(int i : view{vec_data.begin() + 2, vec_data.end()})
{
std::cout << i << ',';
}
std::cout << '\n';
for(int i : view_n(vec_data.data(), 4))
{
std::cout << i << ',';
}
std::cout << '\n';
for(int i : reversed(vec_data))
{
std::cout << i << ',';
}
std::cout << '\n';
constexpr auto reversed(referenceable_container auto &container) -> view< decltype(std::rbegin(container)) >
Returns view to the Container in reverse order.
Definition: view.h:223
view(Container &cont) -> view< iterator_of_t< Container > >
The container deduction guide uses iterator_of_t.
constexpr view< Iter > view_n(Iter begin, std::size_t const n)
Creates view over 'n' items of dataset starting at 'begin' This does not check validity of the range!
Definition: view.h:198

visit.h

visit is reimplementation of std::visit that has worse time complexity in exchange of less code being generate for the mechanism. It also drop support for multiple variants.

zip.h

zip iterator over multiple data containers, which dereference value is tuple of references to provided containers.

This is especially handy in combination with numeric iterator. Example is enumerate(cont) which returns zip of range over cont size and cont itself, which behaves same as enumerate on python.

std::vector<int> vec_data{-1,1,-1,1,-1,1};
for(auto [i,val] : enumerate(vec_data))
{
std::cout << i << "\t:" << val << '\n';
}
std::vector<std::string> names = {"john", "clark"};
std::vector<std::string> surnames = {"deer", "kent"};
for(auto [name, surname] : zip(names, surnames))
{
std::cout << name << '\t' << surname << '\n';
}
auto zip(Ts &&... cont)
Creates a view of zip iterators for specified containers.
Definition: zip.h:151
auto enumerate(Container &&cont)
Definition: enumerate.h:30