Did you read my article about C++ templates for embedded code? You learned how to use function templates. This post adds a practical example to this topic.
Bit Manipulation
You may be familiar with bit manipulation code like this:
void setup() { DDRB |= 0b00100000u; PORTB &= ~0b00100000u; }
For simplicity, you like to select single bits using its index number.
void setup() { DDRB |= (1 << 5); PORTB &= ~(1 << 5); }
This is a common request. Embedded libraries often implement a solution using macros 😞, like this one from the AVR toolchain:
#define _BV(bit) (1 << (bit))
You can use it to simplify your code like this:
void setup() { DDRB |= _BV(5); PORTB &= ~_BV(5); }
These macros will clutter the global namespace and are not type-safe. I demonstrate the problem with this example:
#include <iostream> #define _BV(bit) (1 << (bit)) int main() { for (int i = 0; i < 64; ++i) { std::cout << i << ": " << sizeof(_BV(i)) << " / " << _BV(i) << std::endl; } return 0; }
This is regular C++ code you compile like this:
> c++ example.cpp -o example
If you run this program, which output do you expect?
Think... Click on "Result" to reveal the output.
0: 4 / 1 1: 4 / 2 2: 4 / 4 3: 4 / 8 4: 4 / 16 5: 4 / 32 6: 4 / 64 7: 4 / 128 8: 4 / 256 9: 4 / 512 10: 4 / 1024 11: 4 / 2048 12: 4 / 4096 13: 4 / 8192 14: 4 / 16384 15: 4 / 32768 16: 4 / 65536 17: 4 / 131072 18: 4 / 262144 19: 4 / 524288 20: 4 / 1048576 21: 4 / 2097152 22: 4 / 4194304 23: 4 / 8388608 24: 4 / 16777216 25: 4 / 33554432 26: 4 / 67108864 27: 4 / 134217728 28: 4 / 268435456 29: 4 / 536870912 30: 4 / 1073741824 31: 4 / -2147483648 32: 4 / 1 33: 4 / 2 34: 4 / 4 35: 4 / 8 36: 4 / 16 37: 4 / 32 38: 4 / 64 39: 4 / 128 40: 4 / 256 41: 4 / 512 42: 4 / 1024 43: 4 / 2048 44: 4 / 4096 45: 4 / 8192 46: 4 / 16384 47: 4 / 32768 48: 4 / 65536 49: 4 / 131072 50: 4 / 262144 51: 4 / 524288 52: 4 / 1048576 53: 4 / 2097152 54: 4 / 4194304 55: 4 / 8388608 56: 4 / 16777216 57: 4 / 33554432 58: 4 / 67108864 59: 4 / 134217728 60: 4 / 268435456 61: 4 / 536870912 62: 4 / 1073741824 63: 4 / -2147483648
You always get a 32-bit signed integer from _BV()
, therefore you see issues starting at bit index 31. If you use the macro correctly, the negative numbers are no problem, but if you work with 64-bit values the results are unexpected.
Templates
Let us write safer code with function templates for a pure C++ solution.
template<uint8_t bitIndex, typename Type> constexpr Type oneBit() { return static_cast<Type>(1) << bitIndex; }
This function template will not only give us a bit from an index but also requires the expected type. Knowing the type is important to prevent invalid index values and unexpected results.
(You will learn static assertions to implement additional checks at a later time.)
Use the new function template like this:
void setup() { DDRB |= oneBit<5, uint8_t>(); PORTB &= ~oneBit<5, uint8_t>(); }
This is safer code, but looks ugly. The type uint8_t
is repetitive and the compiler could deduce it from the variable. Also, the bitwise assignment operators |=
and &= ~X
maybe familiar to you, but they are not well readable.
Let us introduce additional function templates to name the two operations.
template<uint8_t bitIndex, typename Type> inline void setBit(Type &value) { value |= oneBit<bitIndex, Type>(); } template<uint8_t bitIndex, typename Type> inline void clearBit(Type &value) { value &= ~(oneBit<bitIndex, Type>()); }
Now, you can write code like this:
void setup() { setBit<5>(DDRB); clearBit<5>(PORTB); }
We only pass the bit index to the function template, the type is deduced from the variable. This works, because we declared template parameter which can be guessed by the compiler at the end of the parameter list.
Compiled Firmware
Let us compile the example code for Arduino Uno and check the results:
1aa: 25 9a sbi 0x04, 5 ; 4 1ac: 2d 98 cbi 0x05, 5 ; 5
This is all we got — It is the smallest possible code for the bit operations on these IO registers.
Exercises
- Implement the bit manipulation using regular functions; without templates and macros. Do you get the same firmware?
- What is the difference if you use the bit index as template parameter or as a function parameter?
- These examples only work with a static implementation. Create function templates which support dynamic bit indexes.
Conclusion
C++ templates do not only replace macros, but they also lead to safer and cleaner code.
Do you have any questions, miss information or have feedback? Add a comment below or ask a question on Twitter!
Learn More
While you are waiting for the next article, check out these:

It’s Time to Use #pragma once

Units of Measurements for Safe C++ Variables

Event-based Firmware (Part 1/2)

Class or Module for Singletons?

Event-Based Firmware (Part 2/2)
