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
31 changes: 31 additions & 0 deletions doc/modules/ROOT/pages/policies.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ enum class overflow_policy
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
Expand Down Expand Up @@ -68,6 +69,11 @@ enum class overflow_policy
| 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
Expand Down Expand Up @@ -217,6 +223,28 @@ These functions call `std::exit(EXIT_FAILURE)` on error, providing a hard termin

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

=== Widening Arithmetic

[source,c++]
----
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: `uint8` -> `u16`, `u16` -> `u32`, `u32` -> `u64`, `u64` -> `uint128`.
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`.

== Generic Policy-Parameterized Arithmetic

[source,c++]
Expand Down Expand Up @@ -260,6 +288,9 @@ The return type depends on the policy:

| `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:
Expand Down
8 changes: 8 additions & 0 deletions include/boost/safe_numbers/detail/type_traits.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,14 @@ struct underlying<bounded_uint<Min, Max>>

} // namespace impl

// Promotes an unsigned integer to the next higher type
// uint128_t becomes bool so that we can static_assert on bool check that we can't widen uint128_t
template <unsigned_integral T>
using promoted_type = std::conditional_t<std::is_same_v<T, std::uint8_t>, std::uint16_t,
std::conditional_t<std::is_same_v<T, std::uint16_t>, std::uint32_t,
std::conditional_t<std::is_same_v<T, std::uint32_t>, std::uint64_t,
std::conditional_t<std::is_same_v<T, std::uint64_t>, int128::uint128_t, bool>>>>;

} // namespace boost::safe_numbers::detail

#endif // BOOST_SAFE_NUMBERS_DETAIL_TYPE_TRAITS_HPP
81 changes: 79 additions & 2 deletions include/boost/safe_numbers/detail/unsigned_integer_basis.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@

#include <boost/safe_numbers/detail/config.hpp>
#include <boost/safe_numbers/detail/concepts.hpp>
#include <boost/safe_numbers/detail/type_traits.hpp>
#include <boost/safe_numbers/overflow_policy.hpp>

#ifndef BOOST_SAFE_NUMBERS_BUILD_MODULE
Expand Down Expand Up @@ -344,10 +345,25 @@ struct add_helper<overflow_policy::wrapping, BasisType>
}
};

// Partial specialization for widening policy
template <unsigned_integral BasisType>
struct add_helper<overflow_policy::widen, BasisType>
{
[[nodiscard]] static constexpr auto apply(const unsigned_integer_basis<BasisType> lhs,
const unsigned_integer_basis<BasisType> rhs) noexcept
{
using promoted_type = promoted_type<BasisType>;
static_assert(!std::is_same_v<promoted_type, bool>, "Widening policy with uint128_t is not supported");

using result_type = unsigned_integer_basis<promoted_type>;
return result_type{static_cast<promoted_type>(static_cast<promoted_type>(lhs) + rhs)};
}
};

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 || Policy == overflow_policy::strict)
noexcept(Policy == overflow_policy::saturate || Policy == overflow_policy::overflow_tuple || Policy == overflow_policy::checked || Policy == overflow_policy::wrapping || Policy == overflow_policy::strict || Policy == overflow_policy::widen)
{
return add_helper<Policy, BasisType>::apply(lhs, rhs);
}
Expand Down Expand Up @@ -981,10 +997,25 @@ struct mul_helper<overflow_policy::wrapping, BasisType>
}
};

// Partial specialization for widening policy
template <unsigned_integral BasisType>
struct mul_helper<overflow_policy::widen, BasisType>
{
[[nodiscard]] static constexpr auto apply(const unsigned_integer_basis<BasisType> lhs,
const unsigned_integer_basis<BasisType> rhs) noexcept
{
using promoted_type = promoted_type<BasisType>;
static_assert(!std::is_same_v<promoted_type, bool>, "Widening policy with uint128_t is not supported");

using result_type = unsigned_integer_basis<promoted_type>;
return result_type{static_cast<promoted_type>(static_cast<promoted_type>(lhs) * rhs)};
}
};

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 || Policy == overflow_policy::strict)
noexcept(Policy == overflow_policy::saturate || Policy == overflow_policy::overflow_tuple || Policy == overflow_policy::checked || Policy == overflow_policy::wrapping || Policy == overflow_policy::strict || Policy == overflow_policy::widen)
{
return mul_helper<Policy, BasisType>::apply(lhs, rhs);
}
Expand Down Expand Up @@ -1637,6 +1668,24 @@ template <detail::unsigned_integral BasisType>

BOOST_SAFE_NUMBERS_DEFINE_MIXED_UNSIGNED_INTEGER_OP("strict modulo", strict_mod)

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

BOOST_SAFE_NUMBERS_DEFINE_MIXED_UNSIGNED_INTEGER_OP("widening add", widening_add)

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

BOOST_SAFE_NUMBERS_DEFINE_MIXED_UNSIGNED_INTEGER_OP("widening mul", widening_mul)

// ------------------------------
// Generic policy-parameterized functions
// ------------------------------
Expand Down Expand Up @@ -1670,6 +1719,14 @@ template <overflow_policy Policy, detail::unsigned_integral BasisType>
{
return strict_add(lhs, rhs);
}
else if constexpr (Policy == overflow_policy::widen)
{
return widening_add(lhs, rhs);
}
else
{
static_assert(detail::dependent_false<BasisType>, "Policy is not supported for addition");
}
}

template <overflow_policy Policy, detail::unsigned_integral BasisType>
Expand Down Expand Up @@ -1701,6 +1758,10 @@ template <overflow_policy Policy, detail::unsigned_integral BasisType>
{
return strict_sub(lhs, rhs);
}
else
{
static_assert(detail::dependent_false<BasisType>, "Policy is not supported for subtraction");
}
}

template <overflow_policy Policy, detail::unsigned_integral BasisType>
Expand Down Expand Up @@ -1732,6 +1793,14 @@ template <overflow_policy Policy, detail::unsigned_integral BasisType>
{
return strict_mul(lhs, rhs);
}
else if constexpr (Policy == overflow_policy::widen)
{
return widening_mul(lhs, rhs);
}
else
{
static_assert(detail::dependent_false<BasisType>, "Policy is not supported for multiplication");
}
}

template <overflow_policy Policy, detail::unsigned_integral BasisType>
Expand Down Expand Up @@ -1763,6 +1832,10 @@ template <overflow_policy Policy, detail::unsigned_integral BasisType>
{
return strict_div(lhs, rhs);
}
else
{
static_assert(detail::dependent_false<BasisType>, "Policy is not supported for division");
}
}

template <overflow_policy Policy, detail::unsigned_integral BasisType>
Expand Down Expand Up @@ -1794,6 +1867,10 @@ template <overflow_policy Policy, detail::unsigned_integral BasisType>
{
return strict_mod(lhs, rhs);
}
else
{
static_assert(detail::dependent_false<BasisType>, "Policy is not supported for modulo");
}
}

} // namespace boost::safe_numbers
Expand Down
1 change: 1 addition & 0 deletions include/boost/safe_numbers/overflow_policy.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ enum class overflow_policy
checked,
wrapping,
strict,
widen,
};

} // namespace boost::safe_numbers
Expand Down
Loading