CodeRigor

FREE LESSON

Packed booleans: vector<bool>, proxies & bitset

Distinguish bit proxies from boolean values, use fixed-width bit operations beyond 64 bits, and preserve snapshots, bounds and subset-sum semantics.

Begin after vectors and views and iterator categories. Most vector code assumes an element is a T object with a T& reference. vector is specialized, so that assumption must become an explicit constraint in generic code.

Save a bool value when the result must be a snapshot

A mutable vector subscript returns a proxy object. Copying the proxy with auto retains access to its bit; converting it to bool reads a value now. The implementation may pack bits into machine words, but code must not depend on word size, exact storage layout or the size of the proxy object.

A proxy and a snapshot have different lifetimes
Copying a proxy copies access to the original packed bit. A bool conversion detaches the value from future changes.
A proxy and a snapshot have different lifetimes
Copying a proxy copies access to the original packed bit. A bool conversion detaches the value from future changes.
C++
#include <vector>
#include <cstdio>
int main() {
    std::vector<bool> flags{true};auto proxy=flags[0];bool snapshot=flags[0];
    flags[0]=false;
    std::printf("proxy %d, snapshot %d\n",int(bool(proxy)),int(snapshot));
}

auto is not inherently wrong: range-for auto bit can deliberately copy a proxy and still modify its original bit. auto&& is another useful generic binding. auto& cannot bind to a temporary proxy on the implementations used here. With an ordinary vector, a loop variable copied by value has a different effect. Decide whether a generic algorithm requires actual references, writable iterator references or only readable values.

There is no contiguous bool array to borrow

Neither vector nor bitset provides an array of bool objects suitable for span. vector is not a contiguous_range. Use vector, array<bool,N> or another explicitly chosen representation when a C API or a contiguous bool view is required. Packing trades addressable elements for compact storage; it is not a free substitution for every vector use case.

A retained proxy can dangle after reallocation or erasure just like any other borrowed access object. A bool snapshot stays a bool. Simultaneous writes to different packed bits may still touch the same underlying memory location; do not assume separate indices make parallel writes race-free.

Taking a flag must preserve the previous value
The output snapshot stays true after clearing the source. Saving auto instead would observe the newly cleared bit.
Taking a flag must preserve the previous value
The output snapshot stays true after clearing the source. Saving auto instead would observe the newly cleared bit.

bitset expresses fixed-width bit operations

bitset stores a compile-time number of bits, indexed from the least significant bit at zero. Its logical width is N even when N exceeds the machine integer width. A textual bit string displays the most significant bit first. Use count, test, set/reset, AND/OR/XOR and shifts without narrowing to an integer. to_ullong() can throw when high set bits do not fit.

Bitset shifts discard shifted-out bits; shifting by N or more yields all zero bits. This differs from shifting a built-in integer by its width, which violates that operator's precondition. The mutable bitset subscript is also a proxy; test(index) is a checked read, whereas subscript has its own bounds contract.

Shift-and-OR uses each weight once
Start with the empty sum. Each new row is the previous bitset OR its shifted snapshot, so a coin is not reused within one step.
Shift-and-OR uses each weight once
Start with the empty sum. Each new row is the previous bitset OR its shifted snapshot, so a coin is not reused within one step.
C++
#include <bitset>
#include <cstdio>
int main() {
    std::bitset<128> sums;sums.set(0);
    for(unsigned weight:{1U,3U,3U})sums|=sums<<weight;
    for(unsigned i=0;i<sums.size();++i)if(sums.test(i))std::printf("%u ",i);
    std::puts("");
}

The build deliberately includes high bits 64 and 127, empty vectors, adjacent word boundaries and shifts larger than the entire bitset. A solution that only works for one machine word, returns a live proxy or accidentally turns the subset calculation into an unbounded knapsack does not satisfy its contract.

Watch & read

Free lectures, talks and readings that cover this skill from another angle. All resources →

Why vector<bool> references are proxies and how auto can retain a reference-like object instead of a boolean value.

Fixed-size bitsets, compile-time use and choosing a representation for boolean flags.