Introduction
On modern architectures, the smallest unit of memory is a byte.
However, if we’re storing a bool, using a byte of memory to store it as a bit (pun intended) is wasteful. The other 7 bits are unused.
Additionally, using ints with the raw bitwise operators (|, &, ^, <<) can be awkward.
Fortunately, C++ provides a solution in <bitset>.
This allows container not only allows us to pack 8 bits into a byte it grants us access to a suite of methods that performing bit manipulation a breeze.
Let’s go!
Performing bit manipulation using std::bitset
std::bitset exposes four single-bit operations:
| Method name | Description |
|---|---|
test(i) | Allows us to query whether a bit is 0 or 1. |
set(i) | Allows us to turn a bit on. This does nothing if the bit is already on. |
reset(i) | Allows us to turn a bit off. This does nothing if the bit is already off. |
flip(i) | Allows us to flip a bit value from 0 to 1, or from 1 to 0. |
As you can see, the methods grant us a very fine level of control over the bits.
Bits are indexed from 0, with bit 0 being the least significant bit — the same convention as array indexing.
Taking std::bitset for a testdrive
Firstly, we need to import the library, <bitset>
We instantiate a variable of type std::bitset<X>. The number in the angled brackets is the amount of bits you would like to store.
#include <bitset>
#include <iostream>
int main()
{
std::bitset<4> bits{ 0b0101 };
std::cout << "Initial: " << bits << '\n';
bits.reset(0);
std::cout << "Reset 0: " << bits << '\n';
bits.set(3);
std::cout << "Set 3: " << bits << '\n';
bits.flip(3);
std::cout << "Flip 3: " << bits << '\n';
}
// Output
Initial: 0101
Reset 0: 0100
Set 3: 1100
Flip 3: 0100
The bitset prints with the MSB on the left, which aligns with convention.
Each method takes a bit index and either reports or modifies that single position.
Increasing the semantics
Passing in the bit positions as arguments is a bit (pun intended) cryptic.
myByte.set(3);
// What does 3 mean?
Fortunately, we can do better.
Doing Better – An RPG Example
In an RPG, an enemy can have numerous statuses – poisoned, burning, frozen etc.. Let’s store this in in unscoped enum StatusEffect.
NOTE: A regular enum is used to implicitly convert the enumerations into an int. These will be arguments into the methods of std::bitset.
Our bits will represent whether status are active or inactive.
#include <bitset>
#include <iostream>
int main()
{
enum StatusEffect
{
isPoisoned, // 0
isBurning, // 1
isFrozen, // 2
isStunned, // 3
isInvisible, // 4
hasShield, // 5
isSilenced, // 6
isRooted // 7
};
std::bitset<8> status{ 0b0010'0001 };
// bit 0 -> isPoisoned
// bit 5 -> hasShield
status.set(isStunned); // apply stun
status.flip(isInvisible); // toggle invisibility on
status.reset(isPoisoned); // cure poison
std::cout << std::boolalpha;
std::cout << "Stunned: " << status.test(isStunned) << '\n';
std::cout << "Shielded: " << status.test(hasShield) << '\n';
std::cout << "Poisoned: " << status.test(isPoisoned) << '\n';
}
// Output
Stunned: true
Shielded: true
Poisoned: false
It’s much easier to understand statusEffects.set(StatusEffect::isStunned) rather than statusEffects.set(3).
Thus, we shift from working with bit positions to identifiers. This allows us to make the code more semantic and less error-prone.
You’ve probably noticed that the indexed overloads methods of std::bitset apply to a single bit position.
NOTE: std::bitset methods only manipulate one position at a time. To set or clear multiple bits in a single operation, use bit masks — covered in Nibble #036
If we want to set or clear multiple bits in a single operation, bit masks. This is covered in #037 – How to use bit masking to extract meaningful data
Querying std::bitset
std::bitset provides five query methods for inspecting the state as a whole:
| Method name | Description |
|---|---|
size() | Returns the number of bits in the bitset. |
count() | Returns number of bits currently set to 1. |
all() | Returns true if every bit is 1. |
any() | Returns true if at least one bit is 1. |
none() | Returns true if every bit is 0. |
#include <bitset>
#include <iostream>
int main()
{
std::bitset<8> bits{ 0b0000'0101 };
std::cout << std::boolalpha;
std::cout << "Size: " << bits.size() << '\n';
std::cout << "Set bits: " << bits.count() << '\n';
std::cout << "All: " << bits.all() << '\n';
std::cout << "Any: " << bits.any() << '\n';
std::cout << "None: " << bits.none() << '\n';
}
// Output
Size: 8
Set bits: 2
All: false
Any: true
None: false
Takeaway
std::bitset<N> packs N bits into a fixed-size container. It comes out of the box with a suite of bit manipulation methods.
Reach for std::bitset whenever you find yourself maintaining a fixed set of boolean flags, status bits, or feature toggles. Pair it with an unscoped enum of bit positions and suddenly you’ve raised the semantics. status.set(isStunned) reads as plain English, where status |= 1 << 3 does not.