Unsigned Integer Types

Description

The library provides safe unsigned integer types that detect overflow, underflow, and other undefined behavior at runtime. These types are drop-in replacements for the standard unsigned integer types with added safety guarantees.

Type Underlying Type Width Min Max

u8

std::uint8_t

8 bits

0

255

u16

std::uint16_t

16 bits

0

65,535

u32

std::uint32_t

32 bits

0

4,294,967,295

u64

std::uint64_t

64 bits

0

18,446,744,073,709,551,615

u128

uint128_t

128 bits

0

340,282,366,920,938,463,463,374,607,431,768,211,455

Each type exposes a basis_type member type alias that refers to the underlying integer type, allowing conversion back to built-in types when needed.

#include <boost/safe_numbers/unsigned_integers.hpp>

namespace boost::safe_numbers {

using u8   = detail::unsigned_integer_basis<std::uint8_t>;
using u16  = detail::unsigned_integer_basis<std::uint16_t>;
using u32  = detail::unsigned_integer_basis<std::uint32_t>;
using u64  = detail::unsigned_integer_basis<std::uint64_t>;
using u128 = detail::unsigned_integer_basis<int128::uint128_t>;

template <unsigned_integral BasisType>
class unsigned_integer_basis {

public:
    using basis_type = BasisType;

    // Construction
    constexpr unsigned_integer_basis() noexcept = default;
    explicit constexpr unsigned_integer_basis(BasisType val) noexcept;

    template <typename T>
        requires std::is_same_v<T, bool>
    explicit constexpr unsigned_integer_basis(T) noexcept = delete; // bool prohibited

    // Conversion to underlying types
    template <unsigned_integral OtherBasis>
    explicit constexpr operator OtherBasis() const noexcept;

    // Comparison operators
    friend constexpr auto operator<=>(unsigned_integer_basis lhs, unsigned_integer_basis rhs) noexcept
        -> std::strong_ordering = default;

    // Compound assignment operators
    template <unsigned_integral OtherBasis>
    constexpr auto operator+=(unsigned_integer_basis<OtherBasis> rhs) -> unsigned_integer_basis&;

    template <unsigned_integral OtherBasis>
    constexpr auto operator-=(unsigned_integer_basis<OtherBasis> rhs) -> unsigned_integer_basis&;

    template <unsigned_integral OtherBasis>
    constexpr auto operator*=(unsigned_integer_basis<OtherBasis> rhs) -> unsigned_integer_basis&;

    template <unsigned_integral OtherBasis>
    constexpr auto operator/=(unsigned_integer_basis<OtherBasis> rhs) -> unsigned_integer_basis&;

    template <unsigned_integral OtherBasis>
    constexpr auto operator%=(unsigned_integer_basis<OtherBasis> rhs) -> unsigned_integer_basis&;

    // Increment and decrement operators
    constexpr auto operator++() -> unsigned_integer_basis&;
    constexpr auto operator++(int) -> unsigned_integer_basis;
    constexpr auto operator--() -> unsigned_integer_basis&;
    constexpr auto operator--(int) -> unsigned_integer_basis;

}; // class unsigned_integer_basis

// Arithmetic operators (throw on overflow/underflow)
template <unsigned_integral BasisType>
constexpr auto operator+(unsigned_integer_basis<BasisType> lhs,
                         unsigned_integer_basis<BasisType> rhs) -> unsigned_integer_basis<BasisType>;

template <unsigned_integral BasisType>
constexpr auto operator-(unsigned_integer_basis<BasisType> lhs,
                         unsigned_integer_basis<BasisType> rhs) -> unsigned_integer_basis<BasisType>;

template <unsigned_integral BasisType>
constexpr auto operator*(unsigned_integer_basis<BasisType> lhs,
                         unsigned_integer_basis<BasisType> rhs) -> unsigned_integer_basis<BasisType>;

template <unsigned_integral BasisType>
constexpr auto operator/(unsigned_integer_basis<BasisType> lhs,
                         unsigned_integer_basis<BasisType> rhs) -> unsigned_integer_basis<BasisType>;

template <unsigned_integral BasisType>
constexpr auto operator%(unsigned_integer_basis<BasisType> lhs,
                         unsigned_integer_basis<BasisType> rhs) -> unsigned_integer_basis<BasisType>;

// Saturating arithmetic (clamp to min/max on overflow/underflow)
template <UnsignedLibType T>
constexpr T saturating_add(T lhs, T rhs) noexcept;

template <UnsignedLibType T>
constexpr T saturating_sub(T lhs, T rhs) noexcept;

template <UnsignedLibType T>
constexpr T saturating_mul(T lhs, T rhs) noexcept;

template <UnsignedLibType T>
constexpr T saturating_div(T lhs, T rhs);

template <UnsignedLibType T>
constexpr T saturating_mod(T lhs, T rhs);

// Overflowing arithmetic (wrap and return overflow flag)
template <UnsignedLibType T>
constexpr std::pair<T, bool> overflowing_add(T lhs, T rhs) noexcept;

template <UnsignedLibType T>
constexpr std::pair<T, bool> overflowing_sub(T lhs, T rhs) noexcept;

template <UnsignedLibType T>
constexpr std::pair<T, bool> overflowing_mul(T lhs, T rhs) noexcept;

template <UnsignedLibType T>
constexpr std::pair<T, bool> overflowing_div(T lhs, T rhs);

template <UnsignedLibType T>
constexpr std::pair<T, bool> overflowing_mod(T lhs, T rhs);

// Checked arithmetic (return std::nullopt on overflow/underflow)
template <UnsignedLibType T>
constexpr std::optional<T> checked_add(T lhs, T rhs) noexcept;

template <UnsignedLibType T>
constexpr std::optional<T> checked_sub(T lhs, T rhs) noexcept;

template <UnsignedLibType T>
constexpr std::optional<T> checked_mul(T lhs, T rhs) noexcept;

template <UnsignedLibType T>
constexpr std::optional<T> checked_div(T lhs, T rhs) noexcept;

template <UnsignedLibType T>
constexpr std::optional<T> checked_mod(T lhs, T rhs) noexcept;

// Wrapping arithmetic (wrap without indication)
template <UnsignedLibType T>
constexpr T wrapping_add(T lhs, T rhs) noexcept;

template <UnsignedLibType T>
constexpr T wrapping_sub(T lhs, T rhs) noexcept;

template <UnsignedLibType T>
constexpr T wrapping_mul(T lhs, T rhs) noexcept;

template <UnsignedLibType T>
constexpr T wrapping_div(T lhs, T rhs);

template <UnsignedLibType T>
constexpr T wrapping_mod(T lhs, T rhs);

// Strict arithmetic (call std::exit(EXIT_FAILURE) on error)
template <UnsignedLibType T>
constexpr T strict_add(T lhs, T rhs) noexcept;

template <UnsignedLibType T>
constexpr T strict_sub(T lhs, T rhs) noexcept;

template <UnsignedLibType T>
constexpr T strict_mul(T lhs, T rhs) noexcept;

template <UnsignedLibType T>
constexpr T strict_div(T lhs, T rhs) noexcept;

template <UnsignedLibType T>
constexpr T strict_mod(T lhs, T rhs) noexcept;

} // namespace boost::safe_numbers

Operator Behavior

Default Construction

constexpr unsigned_integer_basis() noexcept = default;

Values are default-initialized to zero.

Construction from Underlying Type

explicit constexpr unsigned_integer_basis(BasisType val) noexcept;

Construction from the underlying type is explicit to prevent accidental conversions.

Construction from bool

template <typename T>
    requires std::is_same_v<T, bool>
explicit constexpr unsigned_integer_basis(T) noexcept = delete;

Constructing from bool is a compile-time error.

Conversion to Underlying Types

template <unsigned_integral OtherBasis>
explicit constexpr operator OtherBasis() const noexcept;

Conversion to other unsigned integral types is explicit. Narrowing conversions cause a compile-time error.

Comparison Operators

friend constexpr auto operator<=>(unsigned_integer_basis lhs, unsigned_integer_basis rhs) noexcept
    -> std::strong_ordering = default;

Full three-way comparison is supported via operator<=>, which returns std::strong_ordering. All comparison operators (<, , >, >=, ==, !=) are available.

Arithmetic Operators

template <unsigned_integral BasisType>
constexpr auto operator+(unsigned_integer_basis<BasisType> lhs,
                         unsigned_integer_basis<BasisType> rhs) -> unsigned_integer_basis<BasisType>;

template <unsigned_integral BasisType>
constexpr auto operator-(unsigned_integer_basis<BasisType> lhs,
                         unsigned_integer_basis<BasisType> rhs) -> unsigned_integer_basis<BasisType>;

template <unsigned_integral BasisType>
constexpr auto operator*(unsigned_integer_basis<BasisType> lhs,
                         unsigned_integer_basis<BasisType> rhs) -> unsigned_integer_basis<BasisType>;

template <unsigned_integral BasisType>
constexpr auto operator/(unsigned_integer_basis<BasisType> lhs,
                         unsigned_integer_basis<BasisType> rhs) -> unsigned_integer_basis<BasisType>;

template <unsigned_integral BasisType>
constexpr auto operator%(unsigned_integer_basis<BasisType> lhs,
                         unsigned_integer_basis<BasisType> rhs) -> unsigned_integer_basis<BasisType>;

All arithmetic operators perform runtime checks and throw exceptions when undefined behavior would occur:

  • +: Throws std::overflow_error if the result exceeds the maximum representable value

  • -: Throws std::underflow_error if the result would be negative (wrap around)

  • *: Throws std::overflow_error if the result exceeds the maximum representable value

  • /: Throws std::domain_error if dividing by zero

  • %: Throws std::domain_error if the divisor is zero

Compound Assignment Operators

template <unsigned_integral OtherBasis>
constexpr auto operator+=(unsigned_integer_basis<OtherBasis> rhs) -> unsigned_integer_basis&;

template <unsigned_integral OtherBasis>
constexpr auto operator-=(unsigned_integer_basis<OtherBasis> rhs) -> unsigned_integer_basis&;

template <unsigned_integral OtherBasis>
constexpr auto operator*=(unsigned_integer_basis<OtherBasis> rhs) -> unsigned_integer_basis&;

template <unsigned_integral OtherBasis>
constexpr auto operator/=(unsigned_integer_basis<OtherBasis> rhs) -> unsigned_integer_basis&;

template <unsigned_integral OtherBasis>
constexpr auto operator%=(unsigned_integer_basis<OtherBasis> rhs) -> unsigned_integer_basis&;

Compound assignment operators follow the same exception behavior as their corresponding arithmetic operators.

Increment and Decrement Operators

constexpr auto operator++() -> unsigned_integer_basis&;
constexpr auto operator++(int) -> unsigned_integer_basis;
constexpr auto operator--() -> unsigned_integer_basis&;
constexpr auto operator--(int) -> unsigned_integer_basis;
  • ++ (pre/post): Throws std::overflow_error if the value is already at the maximum

  • -- (pre/post): Throws std::underflow_error if the value is already zero

Mixed-Width Operations

Operations between different width safe integer types are compile-time errors. To perform operations between different widths, explicitly convert to the same type first.

Alternative Arithmetic Functions

For cases where throwing exceptions is not desired, alternative arithmetic functions are provided with different overflow handling policies.

Saturating Arithmetic

template <UnsignedLibType T>
constexpr T saturating_add(T lhs, T rhs) noexcept;

template <UnsignedLibType T>
constexpr T saturating_sub(T lhs, T rhs) noexcept;

template <UnsignedLibType T>
constexpr T saturating_mul(T lhs, T rhs) noexcept;

template <UnsignedLibType T>
constexpr T saturating_div(T lhs, T rhs);

template <UnsignedLibType T>
constexpr T saturating_mod(T lhs, T rhs);

These functions clamp the result to the representable range instead of throwing:

  • saturating_add: Returns the sum, saturating at std::numeric_limits<T>::max() on overflow

  • saturating_sub: Returns the difference, saturating at std::numeric_limits<T>::min() (zero) on underflow

  • saturating_mul: Returns the product, saturating at std::numeric_limits<T>::max() on overflow

  • saturating_div: Returns the quotient; throws std::domain_error on division by zero (overflow is impossible)

  • saturating_mod: Returns the remainder; throws std::domain_error on division by zero (overflow is impossible)

Overflowing Arithmetic

template <UnsignedLibType T>
constexpr std::pair<T, bool> overflowing_add(T lhs, T rhs) noexcept;

template <UnsignedLibType T>
constexpr std::pair<T, bool> overflowing_sub(T lhs, T rhs) noexcept;

template <UnsignedLibType T>
constexpr std::pair<T, bool> overflowing_mul(T lhs, T rhs) noexcept;

template <UnsignedLibType T>
constexpr std::pair<T, bool> overflowing_div(T lhs, T rhs);

template <UnsignedLibType T>
constexpr std::pair<T, bool> overflowing_mod(T lhs, T rhs);

These functions provide well-defined wrapping semantics with a flag to indicate if overflow occurred. This follows normal C family unsigned rollover where UINT_MAX + 1 == 0 and 0 - 1 == UINT_MAX.

  • overflowing_add: Returns the wrapped sum and true if overflow occurred

  • overflowing_sub: Returns the wrapped difference and true if underflow occurred

  • overflowing_mul: Returns the wrapped product and true if overflow occurred

  • overflowing_div: Returns the quotient and false; throws std::domain_error on division by zero

  • overflowing_mod: Returns the remainder and false; throws std::domain_error on division by zero

Checked Arithmetic

template <UnsignedLibType T>
constexpr std::optional<T> checked_add(T lhs, T rhs) noexcept;

template <UnsignedLibType T>
constexpr std::optional<T> checked_sub(T lhs, T rhs) noexcept;

template <UnsignedLibType T>
constexpr std::optional<T> checked_mul(T lhs, T rhs) noexcept;

template <UnsignedLibType T>
constexpr std::optional<T> checked_div(T lhs, T rhs) noexcept;

template <UnsignedLibType T>
constexpr std::optional<T> checked_mod(T lhs, T rhs) noexcept;

These functions return std::nullopt on overflow, underflow, or division by zero:

  • checked_add: Returns the sum, or std::nullopt on overflow

  • checked_sub: Returns the difference, or std::nullopt on underflow

  • checked_mul: Returns the product, or std::nullopt on overflow

  • checked_div: Returns the quotient, or std::nullopt on division by zero

  • checked_mod: Returns the remainder, or std::nullopt on division by zero

Wrapping Arithmetic

template <UnsignedLibType T>
constexpr T wrapping_add(T lhs, T rhs) noexcept;

template <UnsignedLibType T>
constexpr T wrapping_sub(T lhs, T rhs) noexcept;

template <UnsignedLibType T>
constexpr T wrapping_mul(T lhs, T rhs) noexcept;

template <UnsignedLibType T>
constexpr T wrapping_div(T lhs, T rhs);

template <UnsignedLibType T>
constexpr T wrapping_mod(T lhs, T rhs);

These functions wrap on overflow without any indication:

  • wrapping_add: Returns the wrapped sum

  • wrapping_sub: Returns the wrapped difference

  • wrapping_mul: Returns the wrapped product

  • wrapping_div: Returns the quotient; throws std::domain_error on division by zero

  • wrapping_mod: Returns the remainder; throws std::domain_error on division by zero

Strict Arithmetic

template <UnsignedLibType T>
constexpr T strict_add(T lhs, T rhs) noexcept;

template <UnsignedLibType T>
constexpr T strict_sub(T lhs, T rhs) noexcept;

template <UnsignedLibType T>
constexpr T strict_mul(T lhs, T rhs) noexcept;

template <UnsignedLibType T>
constexpr T strict_div(T lhs, T rhs) noexcept;

template <UnsignedLibType T>
constexpr T strict_mod(T lhs, T rhs) noexcept;

These functions call std::exit(EXIT_FAILURE) on error, providing a hard termination policy for safety-critical applications where exceptions cannot be used:

  • strict_add: Returns the sum; calls std::exit(EXIT_FAILURE) on overflow

  • strict_sub: Returns the difference; calls std::exit(EXIT_FAILURE) on underflow

  • strict_mul: Returns the product; calls std::exit(EXIT_FAILURE) on overflow

  • strict_div: Returns the quotient; calls std::exit(EXIT_FAILURE) on division by zero

  • strict_mod: Returns the remainder; calls std::exit(EXIT_FAILURE) on modulo by zero

All strict functions are marked noexcept since std::exit does not throw.

Exception Summary

Operation Exception Type Condition

+, +=

std::overflow_error

Result exceeds maximum value

-, -=

std::underflow_error

Result would be negative

*, *=

std::overflow_error

Result exceeds maximum value

/, /=

std::domain_error

Division by zero

%, %=

std::domain_error

Modulo by zero

++ (pre/post)

std::overflow_error

Value is at maximum

-- (pre/post)

std::underflow_error

Value is zero

saturating_div, saturating_mod

std::domain_error

Division by zero

overflowing_div, overflowing_mod

std::domain_error

Division by zero

wrapping_div, wrapping_mod

std::domain_error

Division by zero

Strict Functions Behavior

The strict_* functions do not throw exceptions. Instead, they call std::exit(EXIT_FAILURE) on error:

Operation Behavior Condition

strict_add

std::exit(EXIT_FAILURE)

Overflow

strict_sub

std::exit(EXIT_FAILURE)

Underflow

strict_mul

std::exit(EXIT_FAILURE)

Overflow

strict_div

std::exit(EXIT_FAILURE)

Division by zero

strict_mod

std::exit(EXIT_FAILURE)

Modulo by zero

Policy Summary

Policy Overflow/Underflow Behavior Division by Zero noexcept

Default operators

Throws exception

Throws std::domain_error

No

saturating_*

Clamps to min/max

Throws std::domain_error

Add/Sub/Mul: Yes, Div/Mod: No

overflowing_*

Wraps, returns flag

Throws std::domain_error

Add/Sub/Mul: Yes, Div/Mod: No

checked_*

Returns std::nullopt

Returns std::nullopt

Yes

wrapping_*

Wraps silently

Throws std::domain_error

Add/Sub/Mul: Yes, Div/Mod: No

strict_*

Calls std::exit(EXIT_FAILURE)

Calls std::exit(EXIT_FAILURE)

Yes

Constexpr Support

All operations are constexpr-compatible. Overflow at compile time results in a compiler error.