In your program code, you often deal with unitless values. You can add the units of measurements in the variable or function name, or by adding a comment, but there is still the risk you accidentally make wrong conversions.
I will describe how to handle units of measurements for common time values using a custom template class. I also explain how you use user-defined literals, so you can write values like 100_ms
in your code.
The example code will only work with 32-bit processors, like the SAM D21 used in the Arduino Zero or Adafruit Feather M0 platform. You can adapt the code to compile on the AVR toolchain for Arduino Uno and similar, but this is not part of this article.
You require a C++ compiler with support for C++11. This standard is eight years old and should meanwhile be supported by all compilers.
Why are Unitless Values a Problem?
Imagine an interface where you set the delay of an action:
void setActionDelay(uint32_t delay);
You are missing important information on this interface. Do you pass seconds, milliseconds, nanoseconds or any other unit of measurements to this function?
Without API documentation, the user has to guess or study the implementation to understand the used unit.
/// Set the Delay for the action. /// /// @param delay The delay in milliseconds. /// void setActionDelay(uint32_t delay);
I added API documentation to the interface. Now the user of the interface read it and will expect and use milliseconds. They will write code like this:
void setup() { // ... setActionDelay(1000); // ... }
In this user code, the unit is lost again. If you read this code, you have to look up the API documentation realise 1000
actually means 1000 milliseconds.
Decorating Names is no Solution
We can decorate the function and variable names with a suffix, indicating the unit for the value. The resulting interface looks like this:
/// Set the Delay for the action. /// /// @param delay The delay in milliseconds. /// void setActionDelayMs(uint32_t delay);
Now we use the interface like this:
void setup() { // ... setActionDelayMs(1000); // ... }
This is already an improvement compared to the previous example. If you read this code, you assume the value 1000
is milliseconds. You do not have to look up the API documentation.
Nevertheless, this does not prevent another common mistake. If you use a variable to pass the value to the function, this value is still unitless. The compiler can not detect if a nanosecond value suddenly is used as a millisecond value.
void setup() { // ... const auto delay = getDelayConfig(); setActionDelayMs(delay); // ... }
This code compiles without any errors and also looks valid, but if you test the application, you get unexpected results. Reading the API documentation of getDelayConfig()
resolves the source of the issue:
/// Get the configured delay for actions. /// /// @return The delay in nanoseconds. /// uint32_t getDelayConfig();
Let’s adjust this interface and also add a suffix to the name of the variable. Next, we have to think about how to convert nanoseconds into milliseconds.
void setup() { // ... const auto delayNs = getDelayConfigNs(); setActionDelayMs(delayNs / 1000); // ... }
Perfect, isn’t it? While testing the new code, you still get unexpected results.
After a while, you realize your error: To convert from nanoseconds to milliseconds you have to divide by 1000000
, not just 1000
! You wish the compiler had warned you about this issue.
void setup() { // ... const auto delayNs = getDelayConfigNs(); setActionDelayMs(delayNs / 1000000); // ... }
Finally… your fixed this code, for now…
With this hypothetical example, I demonstrated several problem areas. Each can accidentally lead to a problem in the code. These problems are not only hard to find, but each change in the code also bears the risk to introduce a new one.
Introducing a Duration Class
As an elegant solution, let’s write a template class Duration
to handle all time duration values. It will replace the unitless integer values in our code.
#include <cstdint> #include <ratio> template<typename TickType, typename Ratio> class Duration { public: constexpr explicit Duration(TickType ticks) noexcept : _ticks(ticks) {} public: // Operators constexpr inline bool operator==(const Duration &other) const noexcept { return _ticks == other._ticks; } constexpr inline bool operator!=(const Duration &other) const noexcept { return _ticks != other._ticks; } constexpr inline bool operator>(const Duration &other) const noexcept { return _ticks > other._ticks; } constexpr inline bool operator>=(const Duration &other) const noexcept { return _ticks >= other._ticks; } constexpr inline bool operator<(const Duration &other) const noexcept { return _ticks < other._ticks; } constexpr inline bool operator<=(const Duration &other) const noexcept { return _ticks <= other._ticks; } constexpr inline Duration operator+(const Duration &other) const noexcept { return Duration(_ticks+other._ticks); } constexpr inline Duration operator-(const Duration &other) const noexcept { return Duration(_ticks-other._ticks); } public: constexpr inline TickType ticks() const noexcept { return _ticks; } private: TickType _ticks; ///< The number of ticks. };
There is already an implementation in the standard library for desktop application development. This implementation is for embedded systems, where you want tight control over the size of your data.
Our goal is to prevent wrong assignments and conversions. The Duration
template class is a new numerical type, which acts as a primitive integer, but is bound to one unit.
I removed most comments from the example code to make it more readable. Please write proper API documentation in your code.
There are two template arguments TickType
and Ratio
. The first argument defines the integer type to count the ticks for the duration. The second defines the ratio between the base unit and the specified unit of the duration.
A single constructor takes the number of ticks and a complete set of operators make sure you can use the duration like any other integer value. The only difference to a primitive integer: All operators will only accept a value of the same unit. You cannot mix one unit with another.
The method ticks()
allows direct access to the tick count as a unitless integer value. It will allow converting the duration into a primitive integer to pass it to old functions for compatibility.
I use the constexpr
, inline
and noexcept
keywords to give the compiler hints on how to optimize the code. These will give the compiler as many choices as possible. It will produce the same machine code as using a primitive integer.
In the next step, I declare the actual units:
typedef Duration<uint32_t, std::ratio<1, 1>> Seconds; typedef Duration<uint32_t, std::milli> Milliseconds; typedef Duration<uint32_t, std::micro> Microseconds; typedef Duration<uint32_t, std::nano> Nanoseconds;
First I declare four new types Seconds
, Milliseconds
, Microseconds
and Nanoseconds
. Each one with uint32_t
as value type and the correct ratio set.
Next, I declare a user-defined literal suffix _s
, _ms
, _us
and _ns
for all these types:
constexpr Seconds operator "" _s(unsigned long long int ticks) { return Seconds(static_cast<uint32_t>(ticks)); } constexpr Milliseconds operator "" _ms(unsigned long long int ticks) { return Milliseconds(static_cast<uint32_t>(ticks)); } constexpr Microseconds operator "" _us(unsigned long long int ticks) { return Microseconds(static_cast<uint32_t>(ticks)); } constexpr Nanoseconds operator "" _ns(unsigned long long int ticks) { return Nanoseconds(static_cast<uint32_t>(ticks)); }
Using the Duration Class
First, we adjust the interface and remove the decorations from the names. Next, we replace the unitless integer uint32_t
with the new Milliseconds
and Nanoseconds
types:
/// Set the Delay for the action. /// /// @param delay The delay in milliseconds. /// void setActionDelay(Milliseconds delay); /// Get the configured delay for actions. /// /// @return The delay in nanoseconds. /// Nanoseconds getDelayConfig();
The interface is now documented in two forms: The API documentation and the function syntax.
If we try to use these functions with primitive integer literals, the compiler will stop with an error:
void setup() { // ... setActionDelay(1000); // ... }
The compiler will complain like this:
duration.ino: In function 'void setup()': duration:29:22: error: could not convert '1000' from 'int' to 'Milliseconds {aka Duration<long unsigned int, std::ratio<1ll, 1000ll> >}' setActionDelay(1000);
To assign a literal value, you have to use the correct suffix:
void setup() { // ... setActionDelay(1000_ms); // ... }
It will compile without issue and if someone else reads the code, they will immediately see you pass a millisecond value to the function.
The new type also solves the second problem with unitless variables:
void setup() { // ... const auto delay = getDelayConfig(); setActionDelay(delay); // ... }
The compiler will stop with an error:
duration.ino: In function 'void setup()': duration:29:23: error: could not convert 'delay' from 'const Duration<long unsigned int, std::ratio<1ll, 1000000000ll> >' to 'Milliseconds {aka lr::Duration<long unsigned int, std::ratio<1ll, 1000ll> >}' setActionDelay(delay);
Adding Conversion Functions
Let’s solve the last problem by adding conversion functions to the class. For example, to convert a value to milliseconds, we add the method toMilliseconds()
to the Duration
class:
/// Convert to Milliseconds. /// constexpr inline Duration<TickType, std::milli> toMilliseconds() const noexcept { typedef std::ratio_divide<Ratio, std::milli> r; return Duration<TickType, std::milli>(_ticks * r::num / r::den); }
This method generates the correct millisecond value for any defined unit at compile time. If you are not used to working with templates, this function may look to you like some voodoo. Nevertheless, it is straightforward to explain:
The modifiers constexpr
and inline
tell the compiler to generate the code directly inline and do not create an actual function for this simple conversion. In the end, it will reduce it to a single multiplication or division.
The return type is Duration<TickType, std::milli>
, which is the type of Milliseconds
we declare in the file. We can not use the declared name at this point, because it is declared after Duration
and is based on the Duration
class. For the first template argument, we reuse TickType
to use the same integer and only change the ratio.
The first statement of the implementation is typedef std::ratio_divide<Ratio, std::milli> r;
which divides the ratio of the current template class with the ratio std::milli
of the result. By dividing the two ratios we will get the correct conversion factor.
This factor is applied to the current value with the expression _ticks * r::num / r::den
. This result is used to construct a new Duration
object with the desired ratio and the converted value.
How to Use the Conversion
We use the new conversion function like this:
void setup() { // ... const auto delay = getDelayConfig(); setActionDelay(delay.toMilliseconds()); // ... }
An interesting side effect using the Duration
class: If the unit of getDelayConfig()
changes, there is no change required in the user code. The method toMilliseconds()
will convert any Duration
unit to milliseconds.
Is there a Performance or Size Impact?
The Duration
class has no performance or size impact on the firmware. All operations are optimized in the best possible way. The result is like using a primitive integer value.
While there is no performance and size impact, using the Duration
class will make your code more readable and safer. Any unit mismatch in the code will lead to a compiler error.
Example Code
You can find the example code from this article in the following GitHub repository:
Conclusion
You can make your code safer and more readable using unit types. There is no size or performance impact to the compiled firmware. Give it a try, next time you start a new project!
Do you have any questions, did you miss information, or simply want to provide feedback? Add a comment below or ask a question on Twitter!
Learn More

Extreme Integers – Doom from Below

C++ Templates for Embedded Code

Real Time Counter and Integer Overflow

Bit Manipulation using Templates

Write Less Code using the “auto” Keyword
