Single header only C++20 extension of std::bitset that includes multi-bit insertion and extraction and more.
xstd::bitset
adopts and inherits all of the traits and capabilities ofstd::bitset
.- Express compile time bit field ranges using the
xstd::bitrange
class. - Compile time errors when bit ranges are outside of the range of the bitfield.
- Manipulate volatile integral types (registers) using the
xstd::bitmanip
class, which has performance as good or better than hand written alternatives.
- Currently, the bit range for insertion and extraction must be known at compile time.
Simply download and place the include/libxbitset/bitset.hpp
file in your C++
project's include directory.
If you use conan, you can add libxbitset/0.0.1
to your conanfile.txt
.
TBD
For the detailed API for everything please see the Doxygen page found here.
Note that in every location where xstd::bitmanip
is used, xstd::bitset
can
also be used.
The difference between them is as follows:
xstd::bitmanip
saves a copy and reference to the integral passed to it vsxstd::bitset
which only saves a copy.xstd::bitmanip
supportssave()
which saves the bitset contents to the reference.xstd::bitmanip
supportsupdate()
which reads and overwrites the contents of the bitset with the reference value.xstd::bitmanip
will save its contents to the reference on destruction.
// Example register to act as a substitute for a hardware peripherals' register
// file
struct example_register
{
volatile uint64_t CTRL1 = 0;
volatile uint64_t CTRL2 = 0;
};
// Creating a global instance of example_register to be manipulated
example_register memory{};
// Created a pointer to the memory as this is what will be done on most/all
// system, where memory is accessed by address literals directly.
example_register* reg = &memory;
int main()
{
// Create a compile time xstd::bitrange that starts at bit position 16 and is
// 8-bits in length.
constexpr auto intensity = xstd::bitrange{ .position = 16, .width = 8 };
// Create another compile time bit range but this time using the ::from()
// factory function.
constexpr auto clock_divider = xstd::bitrange::from<42, 50>();
// Create a temporary instance of xstd::bitmanip and have it manipulate
// reg->CTRL1.
//
// The constructor copies the register contents and manipulates that and not
// the register directly as volatile register access is slower than CPU
// register or cache access.
xstd::bitmanip(reg->CTRL1)
.insert<intensity>(7) // Insert 7 into the `intensity` location
.set(4) // Set bit 4 to 1
.reset(0) // Set bit 0 to 0
.insert<clock_divider>(3); // Insert 3 into into `clock_divider` location
// On destruction, the changes are saved to the register
// This assertion should be correct
assert(0x0000'0C00'0007'0010 == reg->CTRL1);
}
Unlike the manipulation case where you modify the registers, this action can be done in a temporary, when you poll a register's bits, you will always need to request the data from the register each time.
// Using a single template argument for the ::from() factory will create a bit
// range for width 1 bit and position 5.
constexpr auto system_ready = xstd::bitrange::from<5>();
// Here a new bitmanip object is constructed each iteration of the while loop
while(xstd::bitmanip(reg->STATUS).test(system_read)) {
continue;
}
// Using a single template argument for the ::from() factory will create a bit
// range for width 1 bit and position 5.
constexpr auto system_ready = xstd::bitrange::from<5>();
// Here we create a bitmanip object and save the object into a status (lvalue)
// variable.
auto status = xstd::bitmanip(reg->STATUS);
// In order to ensure that you are reading from actual current value of the
// register, and not the cached copy you NEED to run the `update()` function,
// before calling test.
while(status.update().test(system_read)) {
continue;
}
Both of these examples of polling are equivalent in performance.
When you create a xstd::bitmanip
lvalue reference (variable),
// Create a xstd::bitmanip control object
xstd::bitmanip control(reg->CTRL2);
// Lets say that this sets some
control.set(5);
// In order for the 5th bit to be saved to the actual register, you'll need to
// use the `save()` method.
control.save();
while(control.update().test(1)) {
continue;
}
Generally, you will see that at reasonable optimization levels (above O0) most compilers eliminate the majority of the code and leave only the few required instructions needed to change the registers. Constructors and destructors get eliminated and almost all function calls are eliminated.
TBD
To install the library as a package and run the unit tests is simple. Simply run
conan create .
Before the install phase of the package creation is unit test validation.