.. raw:: html
Use cases
=========
Objects in a CANOpen dictionary
-------------------------------
The structure of an array-object in a CANOpen dictionary is the
following (for an Integer32 array):
::
SubIndex0: Integer8 // contains the number of subindexes
SubIndex1-n: Integer32 // contains the array of data
It uses a one-based index for the data. It is possible to use an indexed
array starting at 1 to store the data that will be accessible in the
CANOpen dictionary, which will greatly improve the readability of the
code as the same indexing scheme is used both internally and externally.
Migrating code from other languages that uses 1-based indexing
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Similarly, when migrating old code from a language that uses 1-based
indexing (Visual Basic comes to mind), using ``indexed_array`` can
provide a great help, in that it allows keeping the algorithms
untouched, by using the same indexing scheme as the one they were
originally written with.
Associating messages with HTTP status codes:
--------------------------------------------
Using ``indexed_array``, we can easily create a map between HTTP codes
and plain text messages. This map will have an array-like storage, and
can reside in read-only memory.
.. code:: cpp
enum class http_status {
success = 200,
moved_permanently = 301,
found = 302,
bad_request = 400,
unauthorized = 401,
forbidden = 403,
not_found = 404,
internal_error = 500
};
BOOST_DESCRIBE_ENUM(http_status, ...)
indexed_array http_messages {
safe_arg("Ok"),
safe_arg("Moved permanently"),
safe_arg("Found"),
safe_arg("Bad request"),
safe_arg("Unauthorized"),
safe_arg("Forbidden"),
safe_arg("Page not found"),
safe_arg("Internal error")
};
One important feature is that if someone decide that we now must handle
the ``304 NotModified`` status code, and add it to the ``http_status``
enum, the ``http_messages`` array will now fail to compile until we add
the relevant error message.
Handling both standard and manufacturer specific values
-------------------------------------------------------
.. code:: cpp
enum class SomeEnum : std::uint8_t
{
// These values are normative values, do not change them
Foo = 0,
Bar = 1,
Baz = 2,
...
Blah = 12,
// These values are manufacturer extensions, in the reserved range
MSExtension1 = 0x80,
MSLight = 0x81,
...
};
BOOST_DESCRIBE_ENUM(...);
Later in the code:
::
indexed_array vocalMessage {
safe_arg("foo.mp3"),
safe_arg("bar.mp3"),
...
safe_arg("blah_v2.mp3"),
safe_arg("outputactive.mp3"),
...
};
The usage of ``safe_arg`` here ensures that if someone modifies the enum
(because a new revision of the standard added a new value after Blah),
the ``vocalMessage`` array will fail to compile. This makes it easy to
spot all places in the code which will be inpacted by the code change.
A faster ``enum_to_string``
---------------------------
Describe provides an helper function to convert an enum to a string.
This function, however, iterate through all enums, not taking the
advantage of the contiguity of enum values, if applicable. We can thus
make it a bit faster, by using an indexed array to store the strings
associated to each enum value.
First we need to create the list of values:
::
template class L, class... T>
constexpr jbc::indexed_array::indexed_array describe_enumerators_as_indexed_array_impl(L)
{
return jbc::indexed_array::indexed_array{T::name...};
}
template
struct to_string_helper
{
static constexpr jbc::indexed_array::indexed_array describe_enumerators_as_indexed_array
{describe_enumerators_as_indexed_array_impl(boost::describe::describe_enumerators())};
};
And then use that. But we want to use that only if
``indexed_array`` is ``O(1)``, otherwise, there won’t be
any benefit. If that’s not the case, we fallback to normal formatting
via describe. We use the static ``is_o1`` property to enable the
overload only when it will gives a performance boost:
::
template
char const* to_string(T e, typename std::enable_if<
boost::describe::has_describe_enumerators::value &&
jbc::indexed_array::indexed_array::is_o1, T>::type = {})
{
if (jbc::indexed_array::indexed_array::indexer::in_range(e))
return to_string_helper::describe_enumerators_as_indexed_array[e];
return "";
};
template
char const* to_string(T e, typename std::enable_if<
boost::describe::has_describe_enumerators::value &&
jbc::indexed_array::indexed_array::is_o1, T>::type = {})
{
return enum_to_string(e);
};
The overall performance improvement is about 10 to 15% with gcc or clang
for a common enum (around 10 values). The code may also be smaller,
especially when the enum has a lot of values (around 25% smaller with an
enum with thirty values). The bigger the number of values in the enum,
the bigger the gains.
Storing base addresses for different devices
--------------------------------------------
We’re writing a driver for a chip who support four ``i2c`` devices.
.. code:: cpp
enum class i2c_id {
i2c1,
i2c2,
i2c3,
i2c4
};
enum class i2c_register {
set_clock,
read,
write,
ack,
...
};
Now we can store the base address of each i2c device like this:
.. code:: cpp
indexed_array i2c_base_address {
safe_arg(0x1060000u),
safe_arg(0x1070000u),
safe_arg(0x1080000u),
safe_arg(0x1090000u),
};
// and the offsets like that
indexed_array i2c_register_offset {
...
};
And access it like that in the code of the driver:
.. code:: cpp
// current_device is an i2c_id
// i2c_register_offset
auto address = i2c_base_address[current_device] + i2c_register_offset[i2c_register::set_clock];
// write the relevant value at address to set the clock of the i2c chip
If we now decide to support a new chip in our driver, which has slightly
different base address and offsets, we can now write:
.. code:: cpp
enum class mcu_chip {
fictive_chip_revA,
fictive_chip_revB
};
indexed_array i2c_base_address {
safe_arg(0x1060000u),
safe_arg(0x1070000u),
safe_arg(0x1080000u),
safe_arg(0x1090000u),
// i2c1 address has changed in this revision
safe_arg(0x1B60000u),
// others untouched
safe_arg(0x1070000u),
safe_arg(0x1080000u),
safe_arg(0x1090000u),
}
And use it like that in our driver
.. code:: cpp
auto address = i2c_base_address[{current_chip, current_device}] +
i2c_register_offset[{current_chip, i2c_register::set_clock}];
Strongly typed index for multidimensional arrays
------------------------------------------------
Let’s say we have the following class, with multiple 3-dimensional
array:
.. code:: cpp
// xyz indexing
std::array, 200>, 300> initial_data;
// beware: for performance reason, this array is indexed differently (z is first, zyx)
std::array, 200>, 100> calculation_result;
So, we now have a recipe for disasters. Two arrays with different
indexing schemes at the same place. We assume a good design, so this
will not be visible in the public interface. But what about preventing
mistakes when someone touches the implementation? Making it impossible
to write something like:
.. code:: cpp
return calculation_result[coord_x][coord_y][coord_z]; // oops
Let’s make that more explicit:
.. code:: cpp
Enum class x_coordinate : std::size_t {}; // strongly typed alias
enum class y_coordinate : std::size_t {};
enum class z_coordinate : std::size_t {};
indexed_array,
index_range,
index_range> initial_data;
indexed_array,
index_range,
index_range> calculation_result;
// for convenience, we add this into our cpp file
using x = x_coordinate;
using y = y_coordinate;
using z = z_coordinate;
Now, accesses to array content must be done using the following syntax:
.. code:: cpp
// return calculation_result[{x{coord_x}, y{coord_y}, z{coord_z}}]; // does not compile
return calculation_result[{z{coord_z}, y{coord_y}, x{coord_x}}]; // we fixed the bug
This check is done purely at compile time, and will not incurs any
runtime cost.