Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
110 changes: 110 additions & 0 deletions doc/modules/ROOT/pages/unsigned_integers.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -168,6 +168,22 @@ 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
----

Expand Down Expand Up @@ -414,6 +430,36 @@ These functions wrap on overflow without any indication:
- `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

[source,c++]
----
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

|===
Expand Down Expand Up @@ -460,6 +506,70 @@ These functions wrap on overflow without any indication:
| 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.
Expand Down
1 change: 1 addition & 0 deletions include/boost/safe_numbers/detail/overflow_policy.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ enum class overflow_policy
overflow_tuple,
checked,
wrapping,
strict,
};

} // namespace boost::safe_numbers::detail
Expand Down
90 changes: 83 additions & 7 deletions include/boost/safe_numbers/detail/unsigned_integer_basis.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
#include <limits>
#include <stdexcept>
#include <cstdint>
#include <cstdlib>
#include <utility>
#include <optional>

Expand Down Expand Up @@ -204,6 +205,11 @@ struct add_helper
{
res = std::numeric_limits<BasisType>::max();
}
else if constexpr (Policy == overflow_policy::strict)
{
static_cast<void>(res);
std::exit(EXIT_FAILURE);
}
else
{
static_cast<void>(res);
Expand Down Expand Up @@ -336,7 +342,7 @@ struct add_helper<overflow_policy::wrapping, BasisType>
template <overflow_policy Policy, unsigned_integral BasisType>
[[nodiscard]] constexpr auto add_impl(const unsigned_integer_basis<BasisType> lhs,
const unsigned_integer_basis<BasisType> rhs)
noexcept(Policy == overflow_policy::saturate || Policy == overflow_policy::overflow_tuple || Policy == overflow_policy::checked || Policy == overflow_policy::wrapping)
noexcept(Policy == overflow_policy::saturate || Policy == overflow_policy::overflow_tuple || Policy == overflow_policy::checked || Policy == overflow_policy::wrapping || Policy == overflow_policy::strict)
{
return add_helper<Policy, BasisType>::apply(lhs, rhs);
}
Expand Down Expand Up @@ -567,6 +573,11 @@ struct sub_helper
{
res = std::numeric_limits<BasisType>::min();
}
else if constexpr (Policy == overflow_policy::strict)
{
static_cast<void>(res);
std::exit(EXIT_FAILURE);
}
else
{
static_cast<void>(res);
Expand Down Expand Up @@ -699,7 +710,7 @@ struct sub_helper<overflow_policy::wrapping, BasisType>
template <overflow_policy Policy, unsigned_integral BasisType>
[[nodiscard]] constexpr auto sub_impl(const unsigned_integer_basis<BasisType> lhs,
const unsigned_integer_basis<BasisType> rhs)
noexcept(Policy == overflow_policy::saturate || Policy == overflow_policy::overflow_tuple || Policy == overflow_policy::checked || Policy == overflow_policy::wrapping)
noexcept(Policy == overflow_policy::saturate || Policy == overflow_policy::overflow_tuple || Policy == overflow_policy::checked || Policy == overflow_policy::wrapping || Policy == overflow_policy::strict)
{
return sub_helper<Policy, BasisType>::apply(lhs, rhs);
}
Expand Down Expand Up @@ -811,7 +822,7 @@ struct mul_helper
{
[[nodiscard]] static constexpr auto apply(const unsigned_integer_basis<BasisType> lhs,
const unsigned_integer_basis<BasisType> rhs)
noexcept(Policy == overflow_policy::saturate)
noexcept(Policy == overflow_policy::saturate || Policy == overflow_policy::strict)
-> unsigned_integer_basis<BasisType>
{
using result_type = unsigned_integer_basis<BasisType>;
Expand All @@ -831,6 +842,11 @@ struct mul_helper
{
res = std::numeric_limits<BasisType>::max();
}
else if constexpr (Policy == overflow_policy::strict)
{
static_cast<void>(res);
std::exit(EXIT_FAILURE);
}
else
{
static_cast<void>(res);
Expand Down Expand Up @@ -963,7 +979,7 @@ struct mul_helper<overflow_policy::wrapping, BasisType>
template <overflow_policy Policy, unsigned_integral BasisType>
[[nodiscard]] constexpr auto mul_impl(const unsigned_integer_basis<BasisType> lhs,
const unsigned_integer_basis<BasisType> rhs)
noexcept(Policy == overflow_policy::saturate || Policy == overflow_policy::overflow_tuple || Policy == overflow_policy::checked || Policy == overflow_policy::wrapping)
noexcept(Policy == overflow_policy::saturate || Policy == overflow_policy::overflow_tuple || Policy == overflow_policy::checked || Policy == overflow_policy::wrapping || Policy == overflow_policy::strict)
{
return mul_helper<Policy, BasisType>::apply(lhs, rhs);
}
Expand Down Expand Up @@ -996,6 +1012,7 @@ struct div_helper
{
[[nodiscard]] static constexpr auto apply(const unsigned_integer_basis<BasisType> lhs,
const unsigned_integer_basis<BasisType> rhs)
noexcept(Policy == overflow_policy::strict)
-> unsigned_integer_basis<BasisType>
{
using result_type = unsigned_integer_basis<BasisType>;
Expand All @@ -1010,6 +1027,10 @@ struct div_helper
{
BOOST_THROW_EXCEPTION(std::domain_error("Unsigned division by zero"));
}
else if constexpr (Policy == overflow_policy::strict)
{
std::exit(EXIT_FAILURE);
}
else
{
BOOST_SAFE_NUMBERS_UNREACHABLE;
Expand Down Expand Up @@ -1113,7 +1134,7 @@ struct div_helper<overflow_policy::wrapping, BasisType>
template <overflow_policy Policy, unsigned_integral BasisType>
[[nodiscard]] constexpr auto div_impl(const unsigned_integer_basis<BasisType> lhs,
const unsigned_integer_basis<BasisType> rhs)
noexcept(Policy == overflow_policy::checked)
noexcept(Policy == overflow_policy::checked || Policy == overflow_policy::strict)
{
return div_helper<Policy, BasisType>::apply(lhs, rhs);
}
Expand Down Expand Up @@ -1146,6 +1167,7 @@ struct mod_helper
{
[[nodiscard]] static constexpr auto apply(const unsigned_integer_basis<BasisType> lhs,
const unsigned_integer_basis<BasisType> rhs)
noexcept(Policy == overflow_policy::strict)
-> unsigned_integer_basis<BasisType>
{
using result_type = unsigned_integer_basis<BasisType>;
Expand All @@ -1158,7 +1180,11 @@ struct mod_helper
}
else if constexpr (Policy == overflow_policy::saturate)
{
BOOST_THROW_EXCEPTION(std::domain_error("Unsigned division by zero"));
BOOST_THROW_EXCEPTION(std::domain_error("Unsigned modulo by zero"));
}
else if constexpr (Policy == overflow_policy::strict)
{
std::exit(EXIT_FAILURE);
}
else
{
Expand Down Expand Up @@ -1263,7 +1289,7 @@ struct mod_helper<overflow_policy::wrapping, BasisType>
template <overflow_policy Policy, unsigned_integral BasisType>
[[nodiscard]] constexpr auto mod_impl(const unsigned_integer_basis<BasisType> lhs,
const unsigned_integer_basis<BasisType> rhs)
noexcept(Policy == overflow_policy::checked)
noexcept(Policy == overflow_policy::checked || Policy == overflow_policy::strict)
{
return mod_helper<Policy, BasisType>::apply(lhs, rhs);
}
Expand Down Expand Up @@ -1556,6 +1582,56 @@ template <detail::unsigned_integral BasisType>

BOOST_SAFE_NUMBERS_DEFINE_MIXED_UNSIGNED_INTEGER_OP("wrapping modulo", wrapping_mod)

template <detail::unsigned_integral BasisType>
[[nodiscard]] constexpr auto strict_add(const detail::unsigned_integer_basis<BasisType> lhs,
const detail::unsigned_integer_basis<BasisType> rhs) noexcept
-> detail::unsigned_integer_basis<BasisType>
{
return detail::add_impl<detail::overflow_policy::strict>(lhs, rhs);
}

BOOST_SAFE_NUMBERS_DEFINE_MIXED_UNSIGNED_INTEGER_OP("strict addition", strict_add)

template <detail::unsigned_integral BasisType>
[[nodiscard]] constexpr auto strict_sub(const detail::unsigned_integer_basis<BasisType> lhs,
const detail::unsigned_integer_basis<BasisType> rhs) noexcept
-> detail::unsigned_integer_basis<BasisType>
{
return detail::sub_impl<detail::overflow_policy::strict>(lhs, rhs);
}

BOOST_SAFE_NUMBERS_DEFINE_MIXED_UNSIGNED_INTEGER_OP("strict subtraction", strict_sub)

template <detail::unsigned_integral BasisType>
[[nodiscard]] constexpr auto strict_mul(const detail::unsigned_integer_basis<BasisType> lhs,
const detail::unsigned_integer_basis<BasisType> rhs) noexcept
-> detail::unsigned_integer_basis<BasisType>
{
return detail::mul_impl<detail::overflow_policy::strict>(lhs, rhs);
}

BOOST_SAFE_NUMBERS_DEFINE_MIXED_UNSIGNED_INTEGER_OP("strict multiplication", strict_mul)

template <detail::unsigned_integral BasisType>
[[nodiscard]] constexpr auto strict_div(const detail::unsigned_integer_basis<BasisType> lhs,
const detail::unsigned_integer_basis<BasisType> rhs) noexcept
-> detail::unsigned_integer_basis<BasisType>
{
return detail::div_impl<detail::overflow_policy::strict>(lhs, rhs);
}

BOOST_SAFE_NUMBERS_DEFINE_MIXED_UNSIGNED_INTEGER_OP("strict division", strict_div)

template <detail::unsigned_integral BasisType>
[[nodiscard]] constexpr auto strict_mod(const detail::unsigned_integer_basis<BasisType> lhs,
const detail::unsigned_integer_basis<BasisType> rhs) noexcept
-> detail::unsigned_integer_basis<BasisType>
{
return detail::mod_impl<detail::overflow_policy::strict>(lhs, rhs);
}

BOOST_SAFE_NUMBERS_DEFINE_MIXED_UNSIGNED_INTEGER_OP("strict modulo", strict_mod)

} // namespace boost::safe_numbers

#undef BOOST_SAFE_NUMBERS_DEFINE_MIXED_UNSIGNED_INTEGER_OP
Expand Down
5 changes: 5 additions & 0 deletions test/Jamfile
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,11 @@ run test_unsigned_wrapping_subtraction.cpp ;
run test_unsigned_wrapping_multiplication.cpp ;
run test_unsigned_wrapping_division.cpp ;
run test_unsigned_wrapping_mod.cpp ;
run-fail test_unsigned_strict_addition.cpp ;
run-fail test_unsigned_strict_subtraction.cpp ;
run-fail test_unsigned_strict_multiplication.cpp ;
run-fail test_unsigned_strict_division.cpp ;
run-fail test_unsigned_strict_mod.cpp ;

# Exhaustive verification tests
run test_exhaustive_u8_arithmetic.cpp ;
Expand Down
33 changes: 33 additions & 0 deletions test/test_unsigned_strict_addition.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
// Copyright 2025 Matt Borland
// Distributed under the Boost Software License, Version 1.0.
// https://www.boost.org/LICENSE_1_0.txt

// This test verifies that strict_add calls std::exit(EXIT_FAILURE) on overflow
// It is marked as run-fail in the Jamfile

#ifdef BOOST_SAFE_NUMBERS_BUILD_MODULE

import boost.safe_numbers;

#else

#include <boost/safe_numbers.hpp>
#include <limits>

#endif

using namespace boost::safe_numbers;

int main()
{
// Create values that will overflow when added
constexpr u32 max_val {std::numeric_limits<u32>::max()};
constexpr u32 one {1U};

// This should call std::exit(EXIT_FAILURE)
const auto result {strict_add(max_val, one)};

// Should never reach here
static_cast<void>(result);
return 0;
}
Loading
Loading