<bit> Support

Description

The library provides thin wrappers around the C++20 <bit> functions for all safe integer types, including bounded_uint<Min, Max>. Each function extracts the underlying value, delegates to the corresponding std:: function, and wraps the result back into the safe type where appropriate. For u128, the functions delegate to the boost::int128 implementations.

#include <boost/safe_numbers/bit.hpp>

Power-of-Two Functions

has_single_bit

template <unsigned_library_type UnsignedInt>
constexpr auto has_single_bit(UnsignedInt x) noexcept -> bool;

Returns true if x is a power of two. See std::has_single_bit.

bit_ceil

template <unsigned_library_type UnsignedInt>
constexpr auto bit_ceil(UnsignedInt x) noexcept -> UnsignedInt;

Returns the smallest power of two not less than x. Undefined behavior if the result is not representable in the underlying type. See std::bit_ceil.

bit_floor

template <unsigned_library_type UnsignedInt>
constexpr auto bit_floor(UnsignedInt x) noexcept -> UnsignedInt;

Returns the largest power of two not greater than x. Returns zero if x is zero. See std::bit_floor.

bit_width

template <unsigned_library_type UnsignedInt>
constexpr auto bit_width(UnsignedInt x) noexcept -> int;

Returns the number of bits needed to represent x (i.e., 1 + floor(log2(x)) for x > 0, or 0 for x == 0). See std::bit_width.

Rotation Functions

rotl

template <non_bounded_unsigned_library_type UnsignedInt>
constexpr auto rotl(UnsignedInt x, int s) noexcept -> UnsignedInt;

Computes the result of bitwise left-rotating x by s positions. See std::rotl.

rotl is not available for bounded_uint types. Bit rotation can produce values outside the valid bounded range, making the operation semantically meaningless for bounded integers.

rotr

template <non_bounded_unsigned_library_type UnsignedInt>
constexpr auto rotr(UnsignedInt x, int s) noexcept -> UnsignedInt;

Computes the result of bitwise right-rotating x by s positions. See std::rotr.

rotr is not available for bounded_uint types. Bit rotation can produce values outside the valid bounded range, making the operation semantically meaningless for bounded integers.

Counting Functions

countl_zero

template <unsigned_library_type UnsignedInt>
constexpr auto countl_zero(UnsignedInt x) noexcept -> int;

Returns the number of consecutive 0-bits starting from the most significant bit. See std::countl_zero.

countl_one

template <unsigned_library_type UnsignedInt>
constexpr auto countl_one(UnsignedInt x) noexcept -> int;

Returns the number of consecutive 1-bits starting from the most significant bit. See std::countl_one.

countr_zero

template <unsigned_library_type UnsignedInt>
constexpr auto countr_zero(UnsignedInt x) noexcept -> int;

Returns the number of consecutive 0-bits starting from the least significant bit. See std::countr_zero.

countr_one

template <unsigned_library_type UnsignedInt>
constexpr auto countr_one(UnsignedInt x) noexcept -> int;

Returns the number of consecutive 1-bits starting from the least significant bit. See std::countr_one.

popcount

template <unsigned_library_type UnsignedInt>
constexpr auto popcount(UnsignedInt x) noexcept -> int;

Returns the number of 1-bits in x. See std::popcount.

Byte Manipulation

byteswap

template <non_bounded_unsigned_library_type UnsignedInt>
constexpr auto byteswap(UnsignedInt x) noexcept -> UnsignedInt;

Reverses the bytes of x. For standard unsigned types this delegates to std::byteswap (C++23). For u128 this delegates to the boost::int128::byteswap implementation. See std::byteswap.

byteswap is not available for bounded_uint types. Byte reversal can produce values outside the valid bounded range, making the operation semantically meaningless for bounded integers.

Verified Types

The bit functions have special behavior with verified types:

Functions that return int or bool (has_single_bit, bit_width, countl_zero, countl_one, countr_zero, countr_one, popcount) work at runtime with verified types. These functions only read the value via the constexpr conversion operator.

Functions that return the safe type (bit_ceil, bit_floor, rotl, rotr, byteswap) have consteval overloads for verified types, meaning they can only be called at compile time. The consteval overloads use a requires clause to distinguish them:

// Runtime overload (non-verified types)
template <unsigned_library_type UnsignedInt>
    requires (!is_verified_type_v<UnsignedInt>)
constexpr auto bit_ceil(UnsignedInt x) noexcept -> UnsignedInt;

// Compile-time overload (verified types only)
template <unsigned_library_type UnsignedInt>
    requires is_verified_type_v<UnsignedInt>
consteval auto bit_ceil(UnsignedInt x) noexcept -> UnsignedInt;

The same pattern applies to bit_floor, rotl, rotr, and byteswap.

Examples

Example 1. This example demonstrates the bit manipulation functions.
// Copyright 2026 Matt Borland
// Distributed under the Boost Software License, Version 1.0.
// https://www.boost.org/LICENSE_1_0.txt

#include <boost/safe_numbers/unsigned_integers.hpp>
#include <boost/safe_numbers/bounded_integers.hpp>
#include <boost/safe_numbers/bit.hpp>
#include <iostream>

int main()
{
    using namespace boost::safe_numbers;

    const u32 x {0b0000'0000'0000'0000'0000'0000'0010'1000};  // 40

    // Power-of-two queries
    std::cout << "has_single_bit(40) = " << has_single_bit(x) << '\n';
    std::cout << "has_single_bit(32) = " << has_single_bit(u32{32}) << '\n';
    std::cout << "bit_ceil(40)       = " << static_cast<std::uint32_t>(bit_ceil(x)) << '\n';
    std::cout << "bit_floor(40)      = " << static_cast<std::uint32_t>(bit_floor(x)) << '\n';
    std::cout << "bit_width(40)      = " << bit_width(x) << '\n';

    std::cout << '\n';

    // Rotation
    const u8 y {0b1011'0001};  // 177
    std::cout << "rotl(0b10110001, 2) = " << static_cast<unsigned>(rotl(y, 2)) << '\n';
    std::cout << "rotr(0b10110001, 2) = " << static_cast<unsigned>(rotr(y, 2)) << '\n';

    std::cout << '\n';

    // Counting
    const u16 z {0b0000'1111'0000'0000};  // 3840
    std::cout << "countl_zero(0x0F00) = " << countl_zero(z) << '\n';
    std::cout << "countl_one(0x0F00)  = " << countl_one(z) << '\n';
    std::cout << "countr_zero(0x0F00) = " << countr_zero(z) << '\n';
    std::cout << "countr_one(0x0F00)  = " << countr_one(z) << '\n';
    std::cout << "popcount(0x0F00)    = " << popcount(z) << '\n';

    std::cout << '\n';

    // Byteswap
    const u32 w {0x12345678};
    std::cout << std::hex;
    std::cout << "byteswap(0x12345678) = 0x" << static_cast<std::uint32_t>(byteswap(w)) << '\n';

    std::cout << std::dec << '\n';

    // Bounded integer types work the same way
    using byte_val = bounded_uint<0u, 255u>;
    using word_val = bounded_uint<0u, 65535u>;

    const auto bv = byte_val{0b0010'1000u};  // 40
    std::cout << "bounded has_single_bit(40) = " << has_single_bit(bv) << '\n';
    std::cout << "bounded bit_ceil(40)       = " << static_cast<unsigned>(static_cast<std::uint8_t>(bit_ceil(bv))) << '\n';
    std::cout << "bounded bit_floor(40)      = " << static_cast<unsigned>(static_cast<std::uint8_t>(bit_floor(bv))) << '\n';
    std::cout << "bounded bit_width(40)      = " << bit_width(bv) << '\n';
    std::cout << "bounded popcount(40)       = " << popcount(bv) << '\n';

    const auto wv = word_val{0x0F00u};
    std::cout << "bounded countl_zero(0x0F00) = " << countl_zero(wv) << '\n';
    std::cout << "bounded popcount(0x0F00)    = " << popcount(wv) << '\n';

    return 0;
}

Output:

has_single_bit(40) = 0
has_single_bit(32) = 1
bit_ceil(40)       = 64
bit_floor(40)      = 32
bit_width(40)      = 6

rotl(0b10110001, 2) = 198
rotr(0b10110001, 2) = 108

countl_zero(0x0F00) = 4
countl_one(0x0F00)  = 0
countr_zero(0x0F00) = 8
countr_one(0x0F00)  = 0
popcount(0x0F00)    = 4

byteswap(0x12345678) = 0x78563412

bounded has_single_bit(40) = 0
bounded bit_ceil(40)       = 64
bounded bit_floor(40)      = 32
bounded bit_width(40)      = 6
bounded popcount(40)       = 2
bounded countl_zero(0x0F00) = 4
bounded popcount(0x0F00)    = 4