Overflow Policies

Description

The library provides multiple overflow handling policies for arithmetic and shift operations. The default arithmetic operators (+, -, *, /, %) and shift operators (<<, >>) use the throw_exception policy, but alternative free functions and a generic policy-parameterized interface allow selecting different behavior at the call site.

The overflow_policy Enum

#include <boost/safe_numbers/overflow_policy.hpp>

namespace boost::safe_numbers {

enum class overflow_policy
{
    throw_exception, // Throw an exception on overflow/underflow
    saturate,        // Clamp to the representable range
    overflow_tuple,  // Wrap and return a flag indicating overflow
    checked,         // Return std::nullopt on overflow/underflow
    wrapping,        // Wrap silently
    strict,          // Call std::exit(EXIT_FAILURE) on error
    widen,           // Promote to the next wider type (add/mul only)
};

} // namespace boost::safe_numbers

Policy Summary

Policy Overflow/Underflow Behavior Division by Zero noexcept

throw_exception (default)

Throws exception

Throws std::domain_error

No

saturate

Clamps to min/max

Throws std::domain_error

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

overflow_tuple

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

widen

Promotes to next wider type (add/mul only)

N/A (only add/mul supported)

Yes

Named Arithmetic Functions

For cases where throwing exceptions is not desired, named free functions are provided for each policy.

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.

Widening Arithmetic

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

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

These functions avoid overflow entirely by promoting the result to the next wider unsigned integer type. The promotion chain is: uint8u16, u16u32, u32u64, u64uint128. Since uint128 is the widest supported type, widening is not available for uint128 operands (a static_assert fires).

Only addition and multiplication are provided because subtraction, division, and modulo cannot overflow into a range that requires a wider type.

  • widening_add: Returns the sum in the next wider type

  • widening_mul: Returns the product in the next wider type

Both functions are noexcept.

Named Shift Functions

The same policy variants available for arithmetic operations are also available for shift operations. The widen policy is not supported for shifts.

Overflow Conditions

  • Left shift (<<): Overflow occurs when bit_width(lhs) + rhs >= std::numeric_limits<BasisType>::digits (i.e., bits would be shifted past the type width).

  • Right shift (>>): Overflow occurs when rhs >= std::numeric_limits<BasisType>::digits (i.e., the shift amount is greater than or equal to the type width).

Saturating Shifts

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

template <UnsignedLibType T>
constexpr T saturating_shr(T lhs, T rhs) noexcept;
  • saturating_shl: Returns the shifted value, saturating at std::numeric_limits<T>::max() on overflow

  • saturating_shr: Returns the shifted value, saturating at 0 when the shift amount is >= the type width

Overflowing Shifts

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

template <UnsignedLibType T>
constexpr std::pair<T, bool> overflowing_shr(T lhs, T rhs) noexcept;
  • overflowing_shl: Returns the wrapped shifted value and true if overflow occurred

  • overflowing_shr: Returns 0 and true if the shift amount is >= the type width; otherwise the shifted value and false

Checked Shifts

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

template <UnsignedLibType T>
constexpr std::optional<T> checked_shr(T lhs, T rhs) noexcept;
  • checked_shl: Returns the shifted value, or std::nullopt on overflow

  • checked_shr: Returns the shifted value, or std::nullopt when the shift amount is >= the type width

Wrapping Shifts

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

template <UnsignedLibType T>
constexpr T wrapping_shr(T lhs, T rhs) noexcept;
  • wrapping_shl: Performs the shift, allowing bits to be lost without indication

  • wrapping_shr: Returns 0 when the shift amount is >= the type width; otherwise performs the shift normally

Strict Shifts

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

template <UnsignedLibType T>
constexpr T strict_shr(T lhs, T rhs) noexcept;
  • strict_shl: Returns the shifted value; calls std::exit(EXIT_FAILURE) on overflow

  • strict_shr: Returns the shifted value; calls std::exit(EXIT_FAILURE) when the shift amount is >= the type width

All shift policy functions are noexcept.

Generic Policy-Parameterized Arithmetic

template <overflow_policy Policy, UnsignedLibType T>
constexpr auto add(T lhs, T rhs);

template <overflow_policy Policy, UnsignedLibType T>
constexpr auto sub(T lhs, T rhs);

template <overflow_policy Policy, UnsignedLibType T>
constexpr auto mul(T lhs, T rhs);

template <overflow_policy Policy, UnsignedLibType T>
constexpr auto div(T lhs, T rhs);

template <overflow_policy Policy, UnsignedLibType T>
constexpr auto mod(T lhs, T rhs);

template <overflow_policy Policy, UnsignedLibType T>
constexpr auto shl(T lhs, T rhs);

template <overflow_policy Policy, UnsignedLibType T>
constexpr auto shr(T lhs, T rhs);

These functions accept an overflow_policy as a template parameter and dispatch to the corresponding named function. The widen policy is not supported for shl or shr. The return type depends on the policy:

Policy Return Type

overflow_policy::throw_exception

T

overflow_policy::saturate

T

overflow_policy::overflow_tuple

std::pair<T, bool>

overflow_policy::checked

std::optional<T>

overflow_policy::wrapping

T

overflow_policy::strict

T

overflow_policy::widen

Next wider unsigned integer type (add/mul only)

This allows writing generic code parameterized on the overflow policy:

using namespace boost::safe_numbers;

// The policy can be a template parameter of your own function
template <overflow_policy Policy>
auto compute(u32 a, u32 b)
{
    return add<Policy>(a, b);
}

auto result_sat = compute<overflow_policy::saturate>(u32{100}, u32{200});
auto result_chk = compute<overflow_policy::checked>(u32{100}, u32{200});

Exception Summary

The default operators and some named functions throw exceptions on error:

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

<<, <⇐

std::overflow_error

bit_width(lhs) + rhs >= digits

>>, >>=

std::overflow_error

rhs >= digits

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