Reading and Writing Bit Streams
Some binary formats use individual bits instead of whole bytes for flags, small numbers, and tightly packed records.
BitReader gives these formats a small sequential cursor, so the parser can
follow the wire layout without repeating byte indexes, masks, and shifts.
This page explains when that cursor is useful, how its boundaries behave, and how to read and revisit fields safely.
When a Bit Reader Fits
Use a bit reader when the order of fields matters and at least some fields are smaller than one byte.
Protocol flags, compact sensor records, image metadata, and entropy-coded headers are typical examples.
The default order consumes bits most-significant first within each byte, which means bit position zero is the high bit
of the first byte.
Select BitOrder::LeastSignificantFirst at construction for formats that define the opposite order.
The selected order is immutable and applies to both byte traversal and the significance of multi-bit fields.
Direct operations on Byte remain simpler when you only need one known flag from one
known byte.
Likewise, a ByteReader is the better fit once every field is byte-aligned.
BitReader earns its place when a format is naturally described as a sequence of bits and the parser benefits from
one cursor that advances with every read.
The reader shares ownership of a ByteBlock.
It is move-only and keeps the data alive for the complete parsing operation.
Writing Bit-Packed Data
Use BitWriter to create the corresponding packed representation.
The writer is move-only and fixes its BitOrder at construction.
writeBool(), writeBits(), and writeByte() overwrite at the current cursor or extend the logical bit count.
Byte operations never align implicitly; call alignToByte() to explicitly write zero padding.
toByteBlock() shares the physical output bytes, including a zero-filled partial final byte, while
takeByteBlockEditor() transfers those bytes and resets the writer.
Complete demo sources
mem/BitReader/ReadClassification.cpp
// Copyright (c) 2026 Tobias Erbsland - https://erbsland.dev
// SPDX-License-Identifier: Apache-2.0
#include <DemoCommon.hpp>
#include <erbsland/BitReader.hpp>
#include <erbsland/BooleanFormat.hpp>
#include <erbsland/Byte.hpp>
#include <erbsland/ByteArray.hpp>
#include <erbsland/ByteBlock.hpp>
#include <erbsland/StandardStreams.hpp>
#include <cstdint>
namespace demo {
/// Read a compact classification record one bit at a time.
///
/// `BitReader` shares a byte block and keeps the position of the next bit.
/// Boundary queries let a parser validate a field before consuming it, while
/// `advance()` and `setBitPosition()` provide safe cursor navigation.
void readClassification() {
const auto record = el::ByteArray{el::Byte{0b10110110U}, el::Byte{0b11000000U}};
auto reader = el::BitReader{el::ByteBlock{record}};
// Validate the fixed header, then consume its flags and three-bit class.
const auto hasHeader = reader.canRead(8U);
const auto potentiallyHazardous = reader.readBool();
const auto confirmed = reader.readBool();
const auto asteroidClass = static_cast<uint8_t>(reader.readBits(3U));
// Skip two reserved bits and read the final header flag.
reader.advance(2U);
const auto manuallyReviewed = reader.readBool();
const auto payloadPosition = reader.bitPosition();
// Revisit the payload and demonstrate that oversized positions clamp to the end.
reader.setBitPosition(payloadPosition);
const auto highConfidence = reader.readBool();
reader.setBitPosition(100U);
el::io::printLine("Klassifikation : Jordnær asteroide"_el);
el::io::printLine("Bits / header ready: "_el, reader.bitCount(), " / "_el, el::BooleanFormat::yesNo(), hasHeader);
el::io::printLine(
"Hazard / confirmed : "_el,
el::BooleanFormat::yesNo(),
potentiallyHazardous,
" / "_el,
el::BooleanFormat::yesNo(),
confirmed);
el::io::printLine("Class : "_el, asteroidClass);
el::io::printLine(
"Reviewed / confidence: "_el,
el::BooleanFormat::yesNo(),
manuallyReviewed,
" / "_el,
el::BooleanFormat::yesNo(),
highConfidence);
el::io::printLine(
"Remaining / at end : "_el, reader.remainingBitCount(), " / "_el, el::BooleanFormat::yesNo(), reader.isAtEnd());
}
}