diff --git a/README.md b/README.md index d4af6bc..489857d 100644 --- a/README.md +++ b/README.md @@ -43,11 +43,18 @@ class i128; class f32; class f64; +// Every width is also available with a saturating or strict error policy +// in place of the default throwing one, e.g.: + +class sat_u8; // clamps to the numeric limits instead of throwing +class strict_i32; // terminates on any error +class sat_f64; // raw IEEE 754 arithmetic with no checks + } // namespace boost::safe_numbers ``` These types operate much like the built-in numeric types but with far stricter behavior to enforce correctness. -This includes no implicit conversions, no mixed type operations, and throwing an exception on underflow, overflow, or other incorrect operation. +This includes no implicit conversions, no mixed type operations, and (by default) throwing an exception on underflow, overflow, or other incorrect operation. A fully featured implementation analogous to the STL is included with ``, ``, ``, etc. support. Using these types is straightforward and can be learned by [example](https://develop.safe-numbers.cpp.al/examples.html). diff --git a/doc/modules/ROOT/nav.adoc b/doc/modules/ROOT/nav.adoc index 0c619eb..bdba989 100644 --- a/doc/modules/ROOT/nav.adoc +++ b/doc/modules/ROOT/nav.adoc @@ -11,6 +11,8 @@ ** xref:examples.adoc#examples_checked[Checked Arithmetic] ** xref:examples.adoc#examples_strict[Strict Arithmetic] ** xref:examples.adoc#examples_generic[Generic Policy-Parameterized Arithmetic] +** xref:examples.adoc#examples_policy_types[Type-Level Policies] +** xref:examples.adoc#examples_user_defined_handler[User Defined Error Handlers] ** xref:examples.adoc#examples_safety_profile[Integer Safety Profile] ** xref:examples.adoc#examples_literals[Literals] ** xref:examples.adoc#examples_charconv[Character Conversion] diff --git a/doc/modules/ROOT/pages/api_reference.adoc b/doc/modules/ROOT/pages/api_reference.adoc index 148db89..6dd10a4 100644 --- a/doc/modules/ROOT/pages/api_reference.adoc +++ b/doc/modules/ROOT/pages/api_reference.adoc @@ -41,6 +41,15 @@ https://www.boost.org/LICENSE_1_0.txt | xref:unsigned_integers.adoc[`u128`] | Safe unsigned 128-bit integer + +| xref:policies.adoc#policies_type_level[`sat_u8` ... `sat_u128`] +| Saturating counterparts of `u8` ... `u128` (clamp instead of throwing) + +| xref:policies.adoc#policies_type_level[`strict_u8` ... `strict_u128`] +| Strict counterparts of `u8` ... `u128` (terminate on error; host only) + +| xref:policies.adoc#policies_type_level[`basic_u8` ... `basic_u128`] +| Alias templates selecting the policy by type: a tag (`throwing`, `saturating`, `strict`) or a user defined handler |=== === Signed Integer Types @@ -63,6 +72,15 @@ https://www.boost.org/LICENSE_1_0.txt | xref:signed_integers.adoc[`i128`] | Safe signed 128-bit integer + +| xref:policies.adoc#policies_type_level[`sat_i8` ... `sat_i128`] +| Saturating counterparts of `i8` ... `i128` (clamp instead of throwing) + +| xref:policies.adoc#policies_type_level[`strict_i8` ... `strict_i128`] +| Strict counterparts of `i8` ... `i128` (terminate on error; host only) + +| xref:policies.adoc#policies_type_level[`basic_i8` ... `basic_i128`] +| Alias templates selecting the policy by type: a tag (`throwing`, `saturating`, `strict`) or a user defined handler |=== === Floating-Point Types @@ -76,6 +94,12 @@ https://www.boost.org/LICENSE_1_0.txt | xref:floats.adoc[`f64`] | Safe double-precision (binary64) floating-point type + +| xref:policies.adoc#policies_type_level[`sat_f32`, `sat_f64`] +| Raw IEEE 754 counterparts of `f32`, `f64` (no checks; overflow saturates to infinity) + +| xref:policies.adoc#policies_type_level[`basic_f32`, `basic_f64`] +| Alias templates selecting the policy by type: a tag (`throwing`, `saturating`) or a user defined handler |=== === Bounded Types @@ -101,7 +125,10 @@ https://www.boost.org/LICENSE_1_0.txt | Type | Description | xref:policies.adoc[`overflow_policy`] -| Enum class specifying the overflow handling policy for arithmetic operations +| Enum class specifying the overflow handling policy, at the call site or as the type's second template parameter + +| xref:policies.adoc#policies_type_level[`error_kind`] +| Enum class identifying the failure reported to a user defined handler | xref:cuda.adoc#cuda_device_exception_mode[`device_exception_mode`] | Enum class controlling whether CUDA device errors trap the kernel or defer to the host diff --git a/doc/modules/ROOT/pages/comparisons.adoc b/doc/modules/ROOT/pages/comparisons.adoc index 8a2ce08..ddbb653 100644 --- a/doc/modules/ROOT/pages/comparisons.adoc +++ b/doc/modules/ROOT/pages/comparisons.adoc @@ -42,7 +42,7 @@ The matrix below compares Boost.SafeNumbers against the closest C++ libraries (B | Panics in debug, wraps in release; `checked_` / `saturating_` / `wrapping_` methods are explicit. | *Alternative overflow handling* -| Per-operation functions: `saturating_*`, `checked_*`, `overflowing_*`, plus the throwing default. +| Both forms: per-operation functions (`saturating_*`, `checked_*`, `overflowing_*`) and opt-in policy-carrying types (`sat_u8`, `strict_i32`), plus the throwing default. | Selected as a template parameter on the type (`safe`); no per-operation functions. | Prevented by construction; explicit clamp / wrap helpers. | Arithmetic policy (undefined or checked) selectable. diff --git a/doc/modules/ROOT/pages/design.adoc b/doc/modules/ROOT/pages/design.adoc index 62c39c4..7a77421 100644 --- a/doc/modules/ROOT/pages/design.adoc +++ b/doc/modules/ROOT/pages/design.adoc @@ -59,10 +59,11 @@ The programmer writes the same code regardless of whether it will be evaluated a === Type Safety as a First-Class Concern -The safe types (`u8`, `u16`, `u32`, `u64`, `u128`) are concrete, named types, not template wrappers around a policy. +The primary vocabulary of the library is a set of concrete, named aliases (`u8`, `u16`, `u32`, `u64`, `u128`, and their signed and floating-point counterparts) with throwing semantics. This makes them easy to read, easy to teach, and easy to use as drop-in replacements for builtin types. -Overflow policies are expressed through named free functions (`saturating_add`, `checked_add`, etc.) rather than through type-level policy parameters. -This keeps the type system simple (and readable in a debugger) while still allowing full control over overflow behavior at each call site. +Alternative overflow behavior is available two ways: named free functions (`saturating_add`, `checked_add`, etc.) select the behavior at each call site, and an optional second template parameter on the types selects it for every operation on a value. +The policy-carrying aliases are just as concrete and named (`sat_u8`, `strict_i32`), the default remains `throw_exception`, and the debugger visualizers understand both forms. +Policies whose result is not the operand type (`checked`, `overflow_tuple`, `widen`) exist only as free functions, so a value of a safe type is always exactly its numeric value. [source,c++] ---- @@ -92,10 +93,10 @@ clang-darwin.compile.c++ ../../../bin.v2/libs/safe_numbers/test/compile_fail_bas ../examples/compile_fail_basic_usage_constexpr.cpp:18:22: error: constexpr variable 'z' must be initialized by a constant expression 18 | constexpr u8 z {x + y}; | ^ ~~~~~~~ -../../../boost/safe_numbers/detail/unsigned_integer_basis.hpp:397:17: note: subexpression not valid in a constant expression - 397 | throw std::overflow_error("Overflow detected in u8 addition"); +../../../boost/safe_numbers/detail/unsigned_integer_basis.hpp:790:17: note: subexpression not valid in a constant expression + 790 | throw std::overflow_error("Overflow detected in u8 addition"); | ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -../examples/compile_fail_basic_usage_constexpr.cpp:18:25: note: in call to 'operator+({255}, {2})' +../examples/compile_fail_basic_usage_constexpr.cpp:18:25: note: in call to 'operator+({255}, {2})' 18 | constexpr u8 z {x + y}; | ^~~~~ 1 error generated. @@ -185,8 +186,9 @@ What changes is the failure model. Integer overflow is a silent wrap that the library must detect, whereas IEEE 754 arithmetic never traps and always yields a value, saturating to a signed infinity on range escape and producing a NaN on an undefined form. The safe floating-point types therefore do not detect a wrap: they run the native operation and classify its IEEE 754 result, turning saturation to positive or negative infinity into `std::overflow_error` or `std::underflow_error`, and the invalid operations, a NaN operand, and division by zero into `std::domain_error`. -For this reason the overflow policies described below do not apply to the floating-point types. -IEEE 754 already defines saturation to infinity and the propagation of NaN, so there is no `saturate` or `checked` variant to select; the safe types simply report those exceptional results rather than offering alternative numeric behavior. +For this reason most of the overflow policies described below do not apply to the floating-point types. +IEEE 754 already defines saturation to infinity and the propagation of NaN, so there is no `checked` or `strict` variant to select; the safe types simply report those exceptional results rather than offering alternative numeric behavior. +The one policy floats do support, as the type-level `saturate` (`sat_f32`, `sat_f64`), embraces that fact: it is raw IEEE 754 arithmetic with no classification at all, matching the value component of the `overflowing_*` functions. The operation surface is also deliberately smaller than for the integer types: only `pass:[+]`, `-`, `pass:[*]`, and `/` are provided, with no compound assignment, increment, decrement, unary, bitwise, or remainder operators, so that every value change passes through a single point where the IEEE 754 result is checked. The dual compile-time and runtime behavior is unchanged: an exceptional result reached during constant evaluation is a compile-time error, exactly as for the integer types. See xref:floats.adoc[] for the full type behavior and xref:verification.adoc[] for the formal verification of the classification logic. @@ -214,6 +216,10 @@ auto [c, overflow] = overflowing_add(u8{200}, u8{100}); // c == u8{44}, overflow This approach is inspired by Rust's primitive type API, where `checked_add` and `saturating_add` are methods on integer types. +When a whole value or algorithm should follow one behavior rather than each call site choosing, the policy can live in the type instead: `sat_u8` saturates on every operator and `strict_u32` terminates on any error, mirroring Rust's `Wrapping`-style wrapper types. +The value-returning policies remain call-site only because they change the result type. +See xref:policies.adoc#policies_type_level[Policies as Part of the Type]. + For generic code that needs to be parameterized on the overflow policy, the library provides policy-parameterized free functions: [source,c++] diff --git a/doc/modules/ROOT/pages/examples.adoc b/doc/modules/ROOT/pages/examples.adoc index c177429..8669917 100644 --- a/doc/modules/ROOT/pages/examples.adoc +++ b/doc/modules/ROOT/pages/examples.adoc @@ -249,6 +249,52 @@ add(max, 1) = 4294967295 ---- ==== +[#examples_policy_types] +== Type-Level Policies + +The overflow policy can also be part of the type itself: every operator on `sat_u8` saturates and every operator on `strict_u32` terminates on error, with no per-call ceremony. +See xref:policies.adoc#policies_type_level[Policies as Part of the Type] for the full model. + +.This https://github.com/boostorg/safe_numbers/blob/develop/examples/policy_types.cpp[example] demonstrates the saturating type aliases, their equivalence with the named free functions, and their constexpr behavior. +==== +[source, c++] +---- +include::example$policy_types.cpp[] +---- + +Output: +---- +sum of 100 tens in a sat_u8 = 255 +sat_u8{200} + sat_u8{100} = 255 +saturating_add(u8{200}, u8{100}) = 255 +constexpr sat_u8{255} + sat_u8{1} = 255 +sat_f32{FLT_MAX} + sat_f32{FLT_MAX} = inf +---- +==== + +[#examples_user_defined_handler] +== User Defined Error Handlers + +Any stateless type with a suitable `on_error` can be the policy, selected by name through the `basic_*` alias templates: `basic_u8`. +See xref:policies.adoc#policies_type_level[Policies as Part of the Type] for the handler contract. + +.This https://github.com/boostorg/safe_numbers/blob/develop/examples/user_defined_handler.cpp[example] demonstrates a wrapping handler, a logging handler, and the tag types that select the built-in policies. +==== +[source, c++] +---- +include::example$user_defined_handler.cpp[] +---- + +Output: +---- +wrap_u8{250} += 10 = 4 +constexpr wrap_u8{255} + wrap_u8{1} = 0 +recovered: Overflow detected in u8 addition +log_u8{200} + log_u8{200} = 144 +ieee_f32{FLT_MAX} + ieee_f32{FLT_MAX} = inf +---- +==== + [#examples_safety_profile] == Integer Safety Profile diff --git a/doc/modules/ROOT/pages/floats.adoc b/doc/modules/ROOT/pages/floats.adoc index aed110e..adba2c7 100644 --- a/doc/modules/ROOT/pages/floats.adoc +++ b/doc/modules/ROOT/pages/floats.adoc @@ -33,7 +33,12 @@ namespace boost::safe_numbers { using f32 = detail::float_basis; using f64 = detail::float_basis; -template +// Raw IEEE 754 semantics: overflow saturates to infinity, NaN propagates, +// and division by zero yields infinity. No checks run. +using sat_f32 = detail::float_basis; +using sat_f64 = detail::float_basis; + +template class float_basis { public: @@ -276,9 +281,10 @@ The following operations are **not** provided: Bitwise operators have no meaning for floating-point values, and the remaining operations are omitted to keep the type minimal: every value change goes through one of the five checked binary operators, so there is a single place where IEEE 754 exceptional results are intercepted. -The policy-based free functions offered for the integer types (`saturating_*`, `overflowing_*`, `checked_*`, `strict_*`, and `widening_*`) are likewise **not** provided for floating-point types. +Most of the policy-based free functions offered for the integer types (`saturating_*`, `checked_*`, `strict_*`, and `widening_*`) are likewise **not** provided for floating-point types; the one exception is the `overflowing_*` family documented above. IEEE 754 already defines saturation to infinity and the propagation of NaN; the safe floating-point types intercept exactly those exceptional results and report them, rather than offering alternative numeric policies. -See xref:policies.adoc[] for the policy model as it applies to the integer types. +For code that wants the raw IEEE behavior on every operation, the type-level `saturate` policy (`sat_f32`, `sat_f64`) provides it with no per-call ceremony. +See xref:policies.adoc[] for the full policy model. == Mixed-Width Operations diff --git a/doc/modules/ROOT/pages/policies.adoc b/doc/modules/ROOT/pages/policies.adoc index 41b20d2..e0e0c79 100644 --- a/doc/modules/ROOT/pages/policies.adoc +++ b/doc/modules/ROOT/pages/policies.adoc @@ -11,10 +11,15 @@ https://www.boost.org/LICENSE_1_0.txt == 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. +There are two ways to select a policy: -These policies apply to the integer types. -The floating-point types `f32` and `f64` follow IEEE 754 semantics instead: they do not participate in the policy model, and their exceptional results (saturation to infinity, invalid operations, and NaN) are reported as exceptions directly. +* Per call site: named free functions (`saturating_add`, `checked_add`, ...) and a generic policy-parameterized interface (`add`). +* Per type: a second template parameter on the numeric types themselves, with ready-made aliases such as `sat_u8` and `strict_i32`. + The default arithmetic operators (`+`, `-`, `*`, `/`, `%`) and shift operators (`<<`, `>>`) follow the type's policy, which is `throw_exception` for the plain aliases (`u8`, `i32`, `f64`, ...). + +The call-site families apply to the integer types. +The floating-point types `f32` and `f64` follow IEEE 754 semantics instead: their exceptional results (saturation to infinity, invalid operations, and NaN) are reported as exceptions directly, and the only call-site family provided for them is `overflowing_*`. +As a type-level policy, floats additionally support `saturate`, which is raw IEEE 754 arithmetic with no checks at all. See xref:floats.adoc[]. == The `overflow_policy` Enum @@ -74,6 +79,173 @@ enum class overflow_policy | Yes |=== +[#policies_type_level] +== Policies as Part of the Type + +Every numeric basis template takes the policy as a second template parameter, defaulted to `throw_exception`: + +[source,c++] +---- +namespace boost::safe_numbers::detail { + +template +class unsigned_integer_basis; + +template +class signed_integer_basis; + +template +class float_basis; + +} // namespace boost::safe_numbers::detail +---- + +An operator on a policy-carrying type behaves exactly like the corresponding named free function: `sat_u8 + sat_u8` is `saturating_add`, `strict_i32 / strict_i32` is `strict_div`, and so on. +This includes the edge cases: division and modulo by zero still throw `std::domain_error` under `saturate`, and `sat_i8{-128} / sat_i8{-1}` clamps to `127`. +Increment, decrement, compound assignment, and (for signed types) unary minus follow the policy as well. +Conversions and stream extraction always throw regardless of the policy. + +Only policies whose result is the operand type can live in the type. +`overflow_tuple`, `checked`, and `widen` change the result type of every operation, so requesting them as a type-level policy is a compile error that points you at the `overflowing_*`, `checked_*`, and `widening_*` free functions. +`strict` is integer only. + +=== The `basic_*` Alias Templates + +Spelling the detail template with its underlying type is never necessary. +Every width has an alias template that selects the policy by type, defaulting to throwing: + +[source,c++] +---- +namespace boost::safe_numbers { + +// Tag types selecting the built-in policies +struct throwing {}; +struct saturating {}; +struct strict {}; + +template +using basic_u8 = detail::unsigned_integer_basis>; + +// ... basic_u16 through basic_u128, basic_i8 through basic_i128, basic_f32, basic_f64 + +} // namespace boost::safe_numbers +---- + +`basic_u8<>` is exactly `u8`, `basic_u8` is exactly `sat_u8`, and `basic_u8` is exactly `strict_u8`; the same identities hold for every width. +Any other type argument is a user defined handler. + +=== User Defined Handlers + +A handler is a stateless class whose `on_error` decides the result of a failed operation: + +[source,c++] +---- +#include + +// Wrapping semantics, like Rust's Wrapping +struct wrapping_handler +{ + template + constexpr auto on_error(const boost::safe_numbers::error_kind, + const T value, + const char*) const noexcept -> T + { + return value; + } +}; + +using wrap_u32 = boost::safe_numbers::basic_u32; + +wrap_u32 a {4294967295U}; +++a; // a == 0, no exception +---- + +The handler contract, checked by the `error_handler_for` concept: + +* The type must be empty (stateless): the policy is part of the type, so per-value state would be lost. +* `on_error` must be callable on a `const` object with `(error_kind, BasisType, const char*)` and return `BasisType`. +* Whatever `on_error` returns becomes the result of the operation. Throwing or terminating instead is equally valid. + +The `error_kind` argument identifies the failure (`overflow`, `underflow`, `divide_by_zero`, and for floats `nan_operation` and `invalid_operation`), and the message is the same diagnostic string the throwing policy would have used. +The value argument is a defined fallback, chosen so that returning it unchanged gives wrapping semantics: + +|=== +| Error site | Value handed to `on_error` + +| Integer add, sub, mul, increment, decrement, unary minus, `MIN / -1` +| The two's complement wrapped result + +| Integer division or modulo by zero +| The dividend (left operand) + +| Integer `MIN % -1` +| `0` (the mathematical result) + +| Unsigned shifts past the type width +| The shift with the amount reduced modulo the width + +| Every float operator +| The raw IEEE 754 result (infinity, NaN, ...) +|=== + +Behavioral notes: + +* `noexcept` of every operator follows the handler: a `noexcept` `on_error` makes the arithmetic `noexcept`. +* A handler that returns a value works in constant expressions; one that throws makes a constant-evaluated error a compile error, exactly like the throwing policy. +* Handler-typed values do not mix with other policies or other handlers, and the compile-time overflow checks (xref:compile_time_checks.adoc[]) do not fire for handler types since their results are defined by the handler. +* On CUDA and SYCL devices the handler is called directly, so it must be usable in device code (a `constexpr` handler generally is). +* Handlers work with every width, including the 128-bit types. + +=== Provided Aliases + +|=== +| Policy | Unsigned | Signed | Float + +| `throw_exception` (default) +| `u8`, `u16`, `u32`, `u64`, `u128` +| `i8`, `i16`, `i32`, `i64`, `i128` +| `f32`, `f64` + +| `saturate` +| `sat_u8`, `sat_u16`, `sat_u32`, `sat_u64`, `sat_u128` +| `sat_i8`, `sat_i16`, `sat_i32`, `sat_i64`, `sat_i128` +| `sat_f32`, `sat_f64` + +| `strict` +| `strict_u8`, `strict_u16`, `strict_u32`, `strict_u64`, `strict_u128` +| `strict_i8`, `strict_i16`, `strict_i32`, `strict_i64`, `strict_i128` +| Not available +|=== + +The saturating float aliases perform raw IEEE 754 arithmetic: overflow saturates to infinity, NaN propagates, and division by zero yields infinity. +They run no checks, matching the value component of the `overflowing_*` functions bit for bit. + +=== Mixed Policies + +Types with different policies do not mix, exactly like types with different widths: + +[source,c++] +---- +sat_u8 a {200}; +u8 b {100}; + +auto c = a + b; // Compile error: convert explicitly through basis_type first +auto d = a + sat_u8{static_cast(b)}; // OK +---- + +The named free functions accept any policy-carrying type and preserve its policy in the result, so `overflowing_add(sat_u8{200}, sat_u8{100})` returns `std::pair`. +A generic call such as `add(sat_a, sat_b)` follows the requested call-site policy, not the type's. + +=== Behavioral Notes + +* `noexcept` follows the policy: `sat_u8` addition is `noexcept`, its division is not (division by zero still throws), and every `strict` operation is `noexcept` because termination is not an exception. +* Saturating overflow is a defined value, so it works in constant expressions: `constexpr sat_u8 x {sat_u8{255} + sat_u8{1}};` yields `255`. + Under `throw_exception` and `strict` the same expression remains a compile error. +* `strict` types are host only: `std::exit` has no meaning in CUDA or SYCL kernels, so using them in device code fails to compile. + The `throw_exception` and `saturate` types work on device, where throwing reports through the device error machinery as usual. +* The compile-time overflow checks (see xref:compile_time_checks.adoc[]) fire for `throw_exception` and `strict` types and are automatically excluded for `saturate` types, whose results are always defined. +* The active policy is queryable as a static member: `sat_u8::error_policy == overflow_policy::saturate`. + == Named Arithmetic Functions For cases where throwing exceptions is not desired, named free functions are provided for each policy. diff --git a/doc/modules/ROOT/pages/pretty_printers.adoc b/doc/modules/ROOT/pages/pretty_printers.adoc index 86a251f..60747db 100644 --- a/doc/modules/ROOT/pages/pretty_printers.adoc +++ b/doc/modules/ROOT/pages/pretty_printers.adoc @@ -12,6 +12,7 @@ https://www.boost.org/LICENSE_1_0.txt The library ships with debugger pretty printers in the `extra/` directory: Python scripts for GDB and LLDB, and a NATVIS visualizer for the Visual Studio (MSVC) debugger. When loaded, they display safe number types as human-readable values instead of showing the internal class layout. +The policy-carrying aliases (`sat_u8`, `strict_i32`, `sat_f64`, ...) display exactly like their throwing counterparts, since the stored value is identical. == Loading the Printers diff --git a/doc/modules/ROOT/pages/signed_integers.adoc b/doc/modules/ROOT/pages/signed_integers.adoc index d0523b3..dfec841 100644 --- a/doc/modules/ROOT/pages/signed_integers.adoc +++ b/doc/modules/ROOT/pages/signed_integers.adoc @@ -25,6 +25,10 @@ These types are drop-in replacements for the standard signed integer types with 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. +Every width is also available with a saturating or strict error policy in place of the default throwing one: `sat_i8` through `sat_i128` clamp instead of throwing (including `MIN / -1` and negation of `MIN`) and `strict_i8` through `strict_i128` terminate on error. +The `basic_i8` through `basic_i128` alias templates select the policy by type, including user defined handlers: `basic_i32`. +See xref:policies.adoc#policies_type_level[Policies as Part of the Type]. + [source,c++] ---- #include @@ -37,7 +41,13 @@ using i32 = detail::signed_integer_basis; using i64 = detail::signed_integer_basis; using i128 = detail::signed_integer_basis; -template +using sat_i8 = detail::signed_integer_basis; +// ... sat_i16, sat_i32, sat_i64, sat_i128 + +using strict_i8 = detail::signed_integer_basis; +// ... strict_i16, strict_i32, strict_i64, strict_i128 + +template class signed_integer_basis { public: diff --git a/doc/modules/ROOT/pages/unsigned_integers.adoc b/doc/modules/ROOT/pages/unsigned_integers.adoc index 842252f..86a3599 100644 --- a/doc/modules/ROOT/pages/unsigned_integers.adoc +++ b/doc/modules/ROOT/pages/unsigned_integers.adoc @@ -25,6 +25,10 @@ These types are drop-in replacements for the standard unsigned integer types wit 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. +Every width is also available with a saturating or strict error policy in place of the default throwing one: `sat_u8` through `sat_u128` clamp instead of throwing and `strict_u8` through `strict_u128` terminate on error. +The `basic_u8` through `basic_u128` alias templates select the policy by type, including user defined handlers: `basic_u8`. +See xref:policies.adoc#policies_type_level[Policies as Part of the Type]. + [source,c++] ---- #include @@ -37,7 +41,13 @@ using u32 = detail::unsigned_integer_basis; using u64 = detail::unsigned_integer_basis; using u128 = detail::unsigned_integer_basis; -template +using sat_u8 = detail::unsigned_integer_basis; +// ... sat_u16, sat_u32, sat_u64, sat_u128 + +using strict_u8 = detail::unsigned_integer_basis; +// ... strict_u16, strict_u32, strict_u64, strict_u128 + +template class unsigned_integer_basis { public: diff --git a/examples/policy_types.cpp b/examples/policy_types.cpp new file mode 100644 index 0000000..630bbf4 --- /dev/null +++ b/examples/policy_types.cpp @@ -0,0 +1,78 @@ +// Copyright 2026 Matt Borland +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt + +// This example demonstrates the type-level error policies. +// Instead of selecting a policy at each call site (saturating_add and friends), +// the policy can be part of the type itself: every operator on sat_u8 saturates +// and every operator on strict_u32 terminates on error. + +#include +#include +#include +#include +#include +#include + +int main() +{ + using boost::safe_numbers::u8; + using boost::safe_numbers::sat_u8; + using boost::safe_numbers::sat_f32; + using boost::safe_numbers::saturating_add; + + // A saturating accumulator: no exception handling needed, the value + // simply pins at the maximum once the sum no longer fits + { + sat_u8 total {0U}; + for (int i = 0; i < 100; ++i) + { + total += sat_u8{10U}; + } + + std::cout << "sum of 100 tens in a sat_u8 = " << total << std::endl; + // Output: 255 (saturated at UINT8_MAX) + } + + // The operators are exactly the named free functions of the same policy + { + const sat_u8 a {200U}; + const sat_u8 b {100U}; + + std::cout << "sat_u8{200} + sat_u8{100} = " << a + b << std::endl; + std::cout << "saturating_add(u8{200}, u8{100}) = " + << saturating_add(u8{200U}, u8{100U}) << std::endl; + // Both output: 255 + } + + // Saturating overflow is a defined value, so it works in constant expressions + { + constexpr sat_u8 clamped {sat_u8{255U} + sat_u8{1U}}; + static_assert(static_cast(clamped) == 255U); + + std::cout << "constexpr sat_u8{255} + sat_u8{1} = " << clamped << std::endl; + // Output: 255 (under the default throwing policy this would not compile) + } + + // Saturating floats are raw IEEE 754 arithmetic: overflow goes to infinity + { + const sat_f32 big {std::numeric_limits::max()}; + const sat_f32 doubled {big + big}; + + std::cout << "sat_f32{FLT_MAX} + sat_f32{FLT_MAX} = " << doubled << std::endl; + // Output: inf + } + + // Types with different policies do not mix, exactly like different widths. + // Uncommenting the next line produces a static_assert telling you to + // convert explicitly through basis_type first: + // + // auto bad = sat_u8{1U} + u8{1U}; + + // strict_u8, strict_i32, and the other strict_ aliases call + // std::exit(EXIT_FAILURE) on any error instead of throwing, for code that + // must never unwind. Their success paths behave identically to the + // throwing types, so they are not exercised here. + + return 0; +} diff --git a/examples/user_defined_handler.cpp b/examples/user_defined_handler.cpp new file mode 100644 index 0000000..337d637 --- /dev/null +++ b/examples/user_defined_handler.cpp @@ -0,0 +1,97 @@ +// Copyright 2026 Matt Borland +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt + +// This example demonstrates user defined error handlers. +// A handler is a stateless type whose on_error decides the result of a failed +// operation; it is selected through the basic_* alias templates, so no detail +// machinery or underlying type ever appears in user code. + +#include +#include +#include +#include +#include +#include + +// Returning the fallback value unchanged gives wrapping semantics, +// like Rust's Wrapping +struct wrapping_handler +{ + template + constexpr auto on_error(const boost::safe_numbers::error_kind, + const T value, + const char*) const noexcept -> T + { + return value; + } +}; + +// Handlers can also throw their own exception types with the library message +struct logging_handler +{ + template + auto on_error(const boost::safe_numbers::error_kind, + const T value, + const char* msg) const -> T + { + std::cerr << "recovered: " << msg << "\n"; + return value; + } +}; + +int main() +{ + using boost::safe_numbers::basic_u8; + using boost::safe_numbers::basic_f32; + using boost::safe_numbers::saturating; + using boost::safe_numbers::u8; + using boost::safe_numbers::sat_u8; + + // The tags select the built-in policies through the same spelling: + // basic_u8<> is u8 and basic_u8 is sat_u8 + static_assert(std::is_same_v, u8>); + static_assert(std::is_same_v, sat_u8>); + + using wrap_u8 = basic_u8; + using log_u8 = basic_u8; + + // Wrapping arithmetic: no exception, two's complement wrap + { + wrap_u8 counter {250U}; + counter += wrap_u8{10U}; + + std::cout << "wrap_u8{250} += 10 = " << counter << std::endl; + // Output: 4 + } + + // Wrapping works in constant expressions because the result is defined + { + constexpr wrap_u8 wrapped {wrap_u8{255U} + wrap_u8{1U}}; + static_assert(static_cast(wrapped) == 0U); + + std::cout << "constexpr wrap_u8{255} + wrap_u8{1} = " << wrapped << std::endl; + // Output: 0 + } + + // A handler observes the error kind and the library's diagnostic message + { + const log_u8 big {200U}; + const auto result {big + big}; + + std::cout << "log_u8{200} + log_u8{200} = " << result << std::endl; + // Output on stderr: recovered: Overflow detected in u8 addition + // Output: 144 + } + + // Float handlers receive the raw IEEE 754 result + { + using ieee_f32 = basic_f32; + const ieee_f32 big {std::numeric_limits::max()}; + + std::cout << "ieee_f32{FLT_MAX} + ieee_f32{FLT_MAX} = " << big + big << std::endl; + // Output: inf + } + + return 0; +} diff --git a/extra/safe_numbers.natvis b/extra/safe_numbers.natvis index fb13b81..04d6b65 100644 --- a/extra/safe_numbers.natvis +++ b/extra/safe_numbers.natvis @@ -22,7 +22,7 @@ - + {basis_,u} basis_ @@ -30,7 +30,7 @@ - + {basis_} basis_ @@ -40,7 +40,7 @@ - + {basis_,d} basis_ @@ -48,7 +48,7 @@ - + {basis_} basis_ @@ -58,7 +58,7 @@ - + {basis_} basis_ diff --git a/extra/safe_numbers_printer_gdb.py b/extra/safe_numbers_printer_gdb.py index 476c104..cee7ea9 100644 --- a/extra/safe_numbers_printer_gdb.py +++ b/extra/safe_numbers_printer_gdb.py @@ -467,46 +467,46 @@ def lookup_safe_numbers_type(val): # Patterns to match for the various types u8_pattern = re.compile( - r"^(boost::safe_numbers::detail::unsigned_integer_basis|(\w+::)*u8)( &| \*)?$" + r"^(boost::safe_numbers::detail::unsigned_integer_basis]+)?>|(\w+::)*(sat_|strict_)?u8)( &| \*)?$" ) u16_pattern = re.compile( - r"^(boost::safe_numbers::detail::unsigned_integer_basis|(\w+::)*u16)( &| \*)?$" + r"^(boost::safe_numbers::detail::unsigned_integer_basis]+)?>|(\w+::)*(sat_|strict_)?u16)( &| \*)?$" ) u32_pattern = re.compile( - r"^(boost::safe_numbers::detail::unsigned_integer_basis|(\w+::)*u32)( &| \*)?$" + r"^(boost::safe_numbers::detail::unsigned_integer_basis]+)?>|(\w+::)*(sat_|strict_)?u32)( &| \*)?$" ) u64_pattern = re.compile( - r"^(boost::safe_numbers::detail::unsigned_integer_basis|boost::safe_numbers::detail::unsigned_integer_basis|(\w+::)*u64)( &| \*)?$" + r"^(boost::safe_numbers::detail::unsigned_integer_basis]+)?>|boost::safe_numbers::detail::unsigned_integer_basis]+)?>|(\w+::)*(sat_|strict_)?u64)( &| \*)?$" ) u128_pattern = re.compile( - r"^(boost::safe_numbers::detail::unsigned_integer_basis|(\w+::)*u128)( &| \*)?$" + r"^(boost::safe_numbers::detail::unsigned_integer_basis]+)?>|(\w+::)*(sat_|strict_)?u128)( &| \*)?$" ) bounded_uint_pattern = re.compile( r"^boost::safe_numbers::bounded_uint<[^>]+>( &| \*)?$" ) i8_pattern = re.compile( - r"^(boost::safe_numbers::detail::signed_integer_basis|(\w+::)*i8)( &| \*)?$" + r"^(boost::safe_numbers::detail::signed_integer_basis]+)?>|(\w+::)*(sat_|strict_)?i8)( &| \*)?$" ) i16_pattern = re.compile( - r"^(boost::safe_numbers::detail::signed_integer_basis|(\w+::)*i16)( &| \*)?$" + r"^(boost::safe_numbers::detail::signed_integer_basis]+)?>|(\w+::)*(sat_|strict_)?i16)( &| \*)?$" ) i32_pattern = re.compile( - r"^(boost::safe_numbers::detail::signed_integer_basis|(\w+::)*i32)( &| \*)?$" + r"^(boost::safe_numbers::detail::signed_integer_basis]+)?>|(\w+::)*(sat_|strict_)?i32)( &| \*)?$" ) i64_pattern = re.compile( - r"^(boost::safe_numbers::detail::signed_integer_basis|boost::safe_numbers::detail::signed_integer_basis|(\w+::)*i64)( &| \*)?$" + r"^(boost::safe_numbers::detail::signed_integer_basis]+)?>|boost::safe_numbers::detail::signed_integer_basis]+)?>|(\w+::)*(sat_|strict_)?i64)( &| \*)?$" ) i128_pattern = re.compile( - r"^(boost::safe_numbers::detail::signed_integer_basis|(\w+::)*i128)( &| \*)?$" + r"^(boost::safe_numbers::detail::signed_integer_basis]+)?>|(\w+::)*(sat_|strict_)?i128)( &| \*)?$" ) bounded_int_pattern = re.compile( r"^boost::safe_numbers::bounded_int<[^>]+>( &| \*)?$" ) f32_pattern = re.compile( - r"^(boost::safe_numbers::detail::float_basis|(\w+::)*f32)( &| \*)?$" + r"^(boost::safe_numbers::detail::float_basis]+)?>|(\w+::)*(sat_)?f32)( &| \*)?$" ) f64_pattern = re.compile( - r"^(boost::safe_numbers::detail::float_basis|(\w+::)*f64)( &| \*)?$" + r"^(boost::safe_numbers::detail::float_basis]+)?>|(\w+::)*(sat_)?f64)( &| \*)?$" ) bounded_float_pattern = re.compile( r"^boost::safe_numbers::bounded_float<.+>( &| \*)?$" diff --git a/extra/safe_numbers_printer_lldb.py b/extra/safe_numbers_printer_lldb.py index dbbec8b..8ed4cde 100644 --- a/extra/safe_numbers_printer_lldb.py +++ b/extra/safe_numbers_printer_lldb.py @@ -360,20 +360,20 @@ def __lldb_init_module(debugger, internal_dict): # quantified group under "^"/"$" anchors (it silently fails to match the # alias, e.g. boost::safe_numbers::u128). The explicit "[A-Za-z0-9_]" # class is equivalent and matches reliably, so use it instead of "\w". - u8_pattern = r"^(const )?(boost::safe_numbers::detail::unsigned_integer_basis|([A-Za-z0-9_]+::)*u8)( &| \*)?$" - u16_pattern = r"^(const )?(boost::safe_numbers::detail::unsigned_integer_basis|([A-Za-z0-9_]+::)*u16)( &| \*)?$" - u32_pattern = r"^(const )?(boost::safe_numbers::detail::unsigned_integer_basis|([A-Za-z0-9_]+::)*u32)( &| \*)?$" - u64_pattern = r"^(const )?(boost::safe_numbers::detail::unsigned_integer_basis|boost::safe_numbers::detail::unsigned_integer_basis|([A-Za-z0-9_]+::)*u64)( &| \*)?$" - u128_pattern = r"^(const )?(boost::safe_numbers::detail::unsigned_integer_basis|([A-Za-z0-9_]+::)*u128)( &| \*)?$" + u8_pattern = r"^(const )?(boost::safe_numbers::detail::unsigned_integer_basis]+)?>|([A-Za-z0-9_]+::)*(sat_|strict_)?u8)( &| \*)?$" + u16_pattern = r"^(const )?(boost::safe_numbers::detail::unsigned_integer_basis]+)?>|([A-Za-z0-9_]+::)*(sat_|strict_)?u16)( &| \*)?$" + u32_pattern = r"^(const )?(boost::safe_numbers::detail::unsigned_integer_basis]+)?>|([A-Za-z0-9_]+::)*(sat_|strict_)?u32)( &| \*)?$" + u64_pattern = r"^(const )?(boost::safe_numbers::detail::unsigned_integer_basis]+)?>|boost::safe_numbers::detail::unsigned_integer_basis]+)?>|([A-Za-z0-9_]+::)*(sat_|strict_)?u64)( &| \*)?$" + u128_pattern = r"^(const )?(boost::safe_numbers::detail::unsigned_integer_basis]+)?>|([A-Za-z0-9_]+::)*(sat_|strict_)?u128)( &| \*)?$" bounded_uint_pattern = r"^(const )?boost::safe_numbers::bounded_uint<[^>]+>( &| \*)?$" - i8_pattern = r"^(const )?(boost::safe_numbers::detail::signed_integer_basis|([A-Za-z0-9_]+::)*i8)( &| \*)?$" - i16_pattern = r"^(const )?(boost::safe_numbers::detail::signed_integer_basis|([A-Za-z0-9_]+::)*i16)( &| \*)?$" - i32_pattern = r"^(const )?(boost::safe_numbers::detail::signed_integer_basis|([A-Za-z0-9_]+::)*i32)( &| \*)?$" - i64_pattern = r"^(const )?(boost::safe_numbers::detail::signed_integer_basis|boost::safe_numbers::detail::signed_integer_basis|([A-Za-z0-9_]+::)*i64)( &| \*)?$" - i128_pattern = r"^(const )?(boost::safe_numbers::detail::signed_integer_basis|([A-Za-z0-9_]+::)*i128)( &| \*)?$" + i8_pattern = r"^(const )?(boost::safe_numbers::detail::signed_integer_basis]+)?>|([A-Za-z0-9_]+::)*(sat_|strict_)?i8)( &| \*)?$" + i16_pattern = r"^(const )?(boost::safe_numbers::detail::signed_integer_basis]+)?>|([A-Za-z0-9_]+::)*(sat_|strict_)?i16)( &| \*)?$" + i32_pattern = r"^(const )?(boost::safe_numbers::detail::signed_integer_basis]+)?>|([A-Za-z0-9_]+::)*(sat_|strict_)?i32)( &| \*)?$" + i64_pattern = r"^(const )?(boost::safe_numbers::detail::signed_integer_basis]+)?>|boost::safe_numbers::detail::signed_integer_basis]+)?>|([A-Za-z0-9_]+::)*(sat_|strict_)?i64)( &| \*)?$" + i128_pattern = r"^(const )?(boost::safe_numbers::detail::signed_integer_basis]+)?>|([A-Za-z0-9_]+::)*(sat_|strict_)?i128)( &| \*)?$" bounded_int_pattern = r"^(const )?boost::safe_numbers::bounded_int<[^>]+>( &| \*)?$" - f32_pattern = r"^(const )?(boost::safe_numbers::detail::float_basis|([A-Za-z0-9_]+::)*f32)( &| \*)?$" - f64_pattern = r"^(const )?(boost::safe_numbers::detail::float_basis|([A-Za-z0-9_]+::)*f64)( &| \*)?$" + f32_pattern = r"^(const )?(boost::safe_numbers::detail::float_basis]+)?>|([A-Za-z0-9_]+::)*(sat_)?f32)( &| \*)?$" + f64_pattern = r"^(const )?(boost::safe_numbers::detail::float_basis]+)?>|([A-Za-z0-9_]+::)*(sat_)?f64)( &| \*)?$" bounded_float_pattern = r"^(const )?boost::safe_numbers::bounded_float<.+>( &| \*)?$" debugger.HandleCommand( diff --git a/include/boost/safe_numbers.hpp b/include/boost/safe_numbers.hpp index aab9494..69bef26 100644 --- a/include/boost/safe_numbers.hpp +++ b/include/boost/safe_numbers.hpp @@ -5,6 +5,7 @@ #ifndef BOOST_SAFENUMBERS_HPP #define BOOST_SAFENUMBERS_HPP +#include #include #include #include diff --git a/include/boost/safe_numbers/bit.hpp b/include/boost/safe_numbers/bit.hpp index 35e6c33..c62ce04 100644 --- a/include/boost/safe_numbers/bit.hpp +++ b/include/boost/safe_numbers/bit.hpp @@ -41,7 +41,7 @@ BOOST_SAFE_NUMBERS_HOST_DEVICE [[nodiscard]] constexpr auto has_single_bit(const #else - if constexpr (std::is_same_v) + if constexpr (std::is_same_v, int128::uint128_t>) { return boost::int128::has_single_bit(static_cast(x)); } @@ -65,7 +65,7 @@ BOOST_SAFE_NUMBERS_HOST_DEVICE [[nodiscard]] constexpr auto bit_ceil(const Unsig #else - if constexpr (std::is_same_v) + if constexpr (std::is_same_v, int128::uint128_t>) { return UnsignedInt{boost::int128::bit_ceil(static_cast(x))}; } @@ -89,7 +89,7 @@ BOOST_SAFE_NUMBERS_HOST_DEVICE [[nodiscard]] constexpr auto bit_floor(const Unsi #else - if constexpr (std::is_same_v) + if constexpr (std::is_same_v, int128::uint128_t>) { return UnsignedInt{boost::int128::bit_floor(static_cast(x))}; } @@ -113,7 +113,7 @@ BOOST_SAFE_NUMBERS_HOST_DEVICE [[nodiscard]] constexpr auto bit_width(const Unsi #else - if constexpr (std::is_same_v) + if constexpr (std::is_same_v, int128::uint128_t>) { return static_cast(boost::int128::bit_width(static_cast(x))); } @@ -137,7 +137,7 @@ BOOST_SAFE_NUMBERS_HOST_DEVICE [[nodiscard]] constexpr auto rotl(const UnsignedI #else - if constexpr (std::is_same_v) + if constexpr (std::is_same_v, int128::uint128_t>) { return UnsignedInt{boost::int128::rotl(static_cast(x), s)}; } @@ -161,7 +161,7 @@ BOOST_SAFE_NUMBERS_HOST_DEVICE [[nodiscard]] constexpr auto rotr(const UnsignedI #else - if constexpr (std::is_same_v) + if constexpr (std::is_same_v, int128::uint128_t>) { return UnsignedInt{boost::int128::rotr(static_cast(x), s)}; } @@ -185,7 +185,7 @@ BOOST_SAFE_NUMBERS_HOST_DEVICE [[nodiscard]] constexpr auto countl_zero(const Un #else - if constexpr (std::is_same_v) + if constexpr (std::is_same_v, int128::uint128_t>) { return boost::int128::countl_zero(static_cast(x)); } @@ -209,7 +209,7 @@ BOOST_SAFE_NUMBERS_HOST_DEVICE [[nodiscard]] constexpr auto countl_one(const Uns #else - if constexpr (std::is_same_v) + if constexpr (std::is_same_v, int128::uint128_t>) { return boost::int128::countl_one(static_cast(x)); } @@ -233,7 +233,7 @@ BOOST_SAFE_NUMBERS_HOST_DEVICE [[nodiscard]] constexpr auto countr_zero(const Un #else - if constexpr (std::is_same_v) + if constexpr (std::is_same_v, int128::uint128_t>) { return boost::int128::countr_zero(static_cast(x)); } @@ -257,7 +257,7 @@ BOOST_SAFE_NUMBERS_HOST_DEVICE [[nodiscard]] constexpr auto countr_one(const Uns #else - if constexpr (std::is_same_v) + if constexpr (std::is_same_v, int128::uint128_t>) { return boost::int128::countr_one(static_cast(x)); } @@ -281,7 +281,7 @@ BOOST_SAFE_NUMBERS_HOST_DEVICE [[nodiscard]] constexpr auto popcount(const Unsig #else - if constexpr (std::is_same_v) + if constexpr (std::is_same_v, int128::uint128_t>) { return boost::int128::popcount(static_cast(x)); } @@ -337,7 +337,7 @@ BOOST_SAFE_NUMBERS_HOST_DEVICE [[nodiscard]] constexpr auto byteswap(const Int x #else - if constexpr (std::is_same_v) + if constexpr (std::is_same_v, int128::uint128_t>) { return Int{boost::int128::byteswap(static_cast(x))}; } diff --git a/include/boost/safe_numbers/detail/float_basis.hpp b/include/boost/safe_numbers/detail/float_basis.hpp index b5f8e90..5c3bac8 100644 --- a/include/boost/safe_numbers/detail/float_basis.hpp +++ b/include/boost/safe_numbers/detail/float_basis.hpp @@ -29,7 +29,7 @@ namespace boost::safe_numbers::detail { -template +template class float_basis { public: @@ -37,6 +37,21 @@ class float_basis // This is exposed to the user so that they can convert back to built-in using basis_type = BasisType; + static_assert(is_overflow_policy_v || error_handler_for, + "ErrorPolicy must be a boost::safe_numbers::overflow_policy enumerator or a stateless " + "handler type providing on_error(error_kind, BasisType, const char*) returning BasisType"); + + static_assert(!is_value_returning_policy(), + "overflow_tuple, checked, and widen change the result type of every operation, " + "so they can not be type-level policies: use the overflowing_* free functions instead"); + + static_assert(!is_overflow_policy_v || is_valid_type_policy(basis_kind::floating_point), + "float_basis supports overflow_policy::throw_exception and overflow_policy::saturate; " + "strict is integer only"); + + // Exposed so that generic code and tests can query the type-level policy + static constexpr auto error_policy {ErrorPolicy}; + private: BasisType basis_ {}; @@ -765,108 +780,153 @@ BOOST_SAFE_NUMBERS_HOST_DEVICE constexpr auto throw_invalid_add() -> void } // namespace impl -template +// The saturating policy is raw IEEE 754, so overflow to infinity is the intended result +#ifdef _MSC_VER +# pragma warning(push) +# pragma warning(disable:4756) +#endif + +template BOOST_SAFE_NUMBERS_HOST_DEVICE -[[nodiscard]] constexpr auto operator+(const float_basis lhs, - const float_basis rhs) -> float_basis +[[nodiscard]] constexpr auto operator+(const float_basis lhs, + const float_basis rhs) + noexcept(policy_is_nothrow_arith()) -> float_basis { const auto lhs_basis {static_cast(lhs)}; const auto rhs_basis {static_cast(rhs)}; - [[maybe_unused]] BasisType res {}; - // The throw branches are inlined here (rather than calling impl::throw_*_add) - // so BOOST_THROW_EXCEPTION captures operator+ as the source location of the throw. - switch (impl::checked_float_addition(lhs_basis, rhs_basis, res)) + if constexpr (policy_equals(overflow_policy::saturate)) { - case impl::error_category::no_error: - break; - case impl::error_category::overflow: - #if !defined(BOOST_SAFE_NUMBERS_HAS_GPU_SUPPORT) - if (std::is_constant_evaluated()) - { - if constexpr (std::is_same_v) + // Raw IEEE 754 semantics: overflow saturates to infinity and NaN propagates. + // Same expression as the value component of overflowing_add. + return float_basis{static_cast(lhs_basis + rhs_basis)}; + } + else if constexpr (is_user_handler_v) + { + // The classifier stores the raw IEEE 754 result, which is the defined + // fallback value handed to the handler + BasisType res {}; + switch (impl::checked_float_addition(lhs_basis, rhs_basis, res)) + { + case impl::error_category::overflow: + res = ErrorPolicy.on_error(error_kind::overflow, res, overflow_add_msg()); + break; + case impl::error_category::underflow: + res = ErrorPolicy.on_error(error_kind::underflow, res, underflow_add_msg()); + break; + case impl::error_category::nan_op: + res = ErrorPolicy.on_error(error_kind::nan_operation, res, nan_add_msg()); + break; + case impl::error_category::invalid_op: + res = ErrorPolicy.on_error(error_kind::invalid_operation, res, invalid_add_msg()); + break; + default: + break; + } + return float_basis{res}; + } + else + { + [[maybe_unused]] BasisType res {}; + + // The throw branches are inlined here (rather than calling impl::throw_*_add) + // so BOOST_THROW_EXCEPTION captures operator+ as the source location of the throw. + switch (impl::checked_float_addition(lhs_basis, rhs_basis, res)) + { + case impl::error_category::no_error: + break; + case impl::error_category::overflow: + #if !defined(BOOST_SAFE_NUMBERS_HAS_GPU_SUPPORT) + if (std::is_constant_evaluated()) { - throw std::overflow_error("Overflow detected in f32 addition"); // LCOV_EXCL_LINE + if constexpr (std::is_same_v) + { + throw std::overflow_error("Overflow detected in f32 addition"); // LCOV_EXCL_LINE + } + else + { + throw std::overflow_error("Overflow detected in f64 addition"); // LCOV_EXCL_LINE + } } else + #endif { - throw std::overflow_error("Overflow detected in f64 addition"); // LCOV_EXCL_LINE + BOOST_SAFE_NUMBERS_THROW_EXCEPTION(std::overflow_error, overflow_add_msg()); } - } - else - #endif - { - BOOST_SAFE_NUMBERS_THROW_EXCEPTION(std::overflow_error, overflow_add_msg()); - } - break; - case impl::error_category::underflow: - #if !defined(BOOST_SAFE_NUMBERS_HAS_GPU_SUPPORT) - if (std::is_constant_evaluated()) - { - if constexpr (std::is_same_v) + break; + case impl::error_category::underflow: + #if !defined(BOOST_SAFE_NUMBERS_HAS_GPU_SUPPORT) + if (std::is_constant_evaluated()) { - throw std::underflow_error("Underflow detected in f32 addition"); // LCOV_EXCL_LINE + if constexpr (std::is_same_v) + { + throw std::underflow_error("Underflow detected in f32 addition"); // LCOV_EXCL_LINE + } + else + { + throw std::underflow_error("Underflow detected in f64 addition"); // LCOV_EXCL_LINE + } } else + #endif { - throw std::underflow_error("Underflow detected in f64 addition"); // LCOV_EXCL_LINE + BOOST_SAFE_NUMBERS_THROW_EXCEPTION(std::underflow_error, underflow_add_msg()); } - } - else - #endif - { - BOOST_SAFE_NUMBERS_THROW_EXCEPTION(std::underflow_error, underflow_add_msg()); - } - break; - case impl::error_category::nan_op: - #if !defined(BOOST_SAFE_NUMBERS_HAS_GPU_SUPPORT) - if (std::is_constant_evaluated()) - { - if constexpr (std::is_same_v) + break; + case impl::error_category::nan_op: + #if !defined(BOOST_SAFE_NUMBERS_HAS_GPU_SUPPORT) + if (std::is_constant_evaluated()) { - throw std::domain_error("Operation with NAN detected in f32 addition"); // LCOV_EXCL_LINE + if constexpr (std::is_same_v) + { + throw std::domain_error("Operation with NAN detected in f32 addition"); // LCOV_EXCL_LINE + } + else + { + throw std::domain_error("Operation with NAN detected in f64 addition"); // LCOV_EXCL_LINE + } } else + #endif { - throw std::domain_error("Operation with NAN detected in f64 addition"); // LCOV_EXCL_LINE + BOOST_SAFE_NUMBERS_THROW_EXCEPTION(std::domain_error, nan_add_msg()); } - } - else - #endif - { - BOOST_SAFE_NUMBERS_THROW_EXCEPTION(std::domain_error, nan_add_msg()); - } - break; - case impl::error_category::invalid_op: - #if !defined(BOOST_SAFE_NUMBERS_HAS_GPU_SUPPORT) - if (std::is_constant_evaluated()) - { - if constexpr (std::is_same_v) + break; + case impl::error_category::invalid_op: + #if !defined(BOOST_SAFE_NUMBERS_HAS_GPU_SUPPORT) + if (std::is_constant_evaluated()) { - throw std::domain_error("Invalid operation (IEEE 754-2008 section 7.2) detected in f32 addition"); // LCOV_EXCL_LINE + if constexpr (std::is_same_v) + { + throw std::domain_error("Invalid operation (IEEE 754-2008 section 7.2) detected in f32 addition"); // LCOV_EXCL_LINE + } + else + { + throw std::domain_error("Invalid operation (IEEE 754-2008 section 7.2) detected in f64 addition"); // LCOV_EXCL_LINE + } } else + #endif { - throw std::domain_error("Invalid operation (IEEE 754-2008 section 7.2) detected in f64 addition"); // LCOV_EXCL_LINE + BOOST_SAFE_NUMBERS_THROW_EXCEPTION(std::domain_error, invalid_add_msg()); } - } - else - #endif - { - BOOST_SAFE_NUMBERS_THROW_EXCEPTION(std::domain_error, invalid_add_msg()); - } - break; - case impl::error_category::divide_by_zero: - BOOST_SAFE_NUMBERS_UNREACHABLE; // LCOV_EXCL_LINE - break; // LCOV_EXCL_LINE - default: - BOOST_SAFE_NUMBERS_UNREACHABLE; // LCOV_EXCL_LINE - break; // LCOV_EXCL_LINE - } + break; + case impl::error_category::divide_by_zero: + BOOST_SAFE_NUMBERS_UNREACHABLE; // LCOV_EXCL_LINE + break; // LCOV_EXCL_LINE + default: + BOOST_SAFE_NUMBERS_UNREACHABLE; // LCOV_EXCL_LINE + break; // LCOV_EXCL_LINE + } - return float_basis{res}; + return float_basis{res}; + } } +#ifdef _MSC_VER +# pragma warning(pop) +#endif + // ------------------------------ // Subtraction // ------------------------------ @@ -1013,108 +1073,153 @@ BOOST_SAFE_NUMBERS_HOST_DEVICE constexpr auto throw_invalid_sub() -> void } // namespace impl -template +// The saturating policy is raw IEEE 754, so overflow to infinity is the intended result +#ifdef _MSC_VER +# pragma warning(push) +# pragma warning(disable:4756) +#endif + +template BOOST_SAFE_NUMBERS_HOST_DEVICE -[[nodiscard]] constexpr auto operator-(const float_basis lhs, - const float_basis rhs) -> float_basis +[[nodiscard]] constexpr auto operator-(const float_basis lhs, + const float_basis rhs) + noexcept(policy_is_nothrow_arith()) -> float_basis { const auto lhs_basis {static_cast(lhs)}; const auto rhs_basis {static_cast(rhs)}; - [[maybe_unused]] BasisType res {}; - // The throw branches are inlined here (rather than calling impl::throw_*_sub) - // so BOOST_THROW_EXCEPTION captures operator- as the source location of the throw. - switch (impl::checked_float_subtraction(lhs_basis, rhs_basis, res)) + if constexpr (policy_equals(overflow_policy::saturate)) { - case impl::error_category::no_error: - break; - case impl::error_category::overflow: - #if !defined(BOOST_SAFE_NUMBERS_HAS_GPU_SUPPORT) - if (std::is_constant_evaluated()) - { - if constexpr (std::is_same_v) + // Raw IEEE 754 semantics: overflow saturates to infinity and NaN propagates. + // Same expression as the value component of overflowing_sub. + return float_basis{static_cast(lhs_basis - rhs_basis)}; + } + else if constexpr (is_user_handler_v) + { + // The classifier stores the raw IEEE 754 result, which is the defined + // fallback value handed to the handler + BasisType res {}; + switch (impl::checked_float_subtraction(lhs_basis, rhs_basis, res)) + { + case impl::error_category::overflow: + res = ErrorPolicy.on_error(error_kind::overflow, res, overflow_sub_msg()); + break; + case impl::error_category::underflow: + res = ErrorPolicy.on_error(error_kind::underflow, res, underflow_sub_msg()); + break; + case impl::error_category::nan_op: + res = ErrorPolicy.on_error(error_kind::nan_operation, res, nan_sub_msg()); + break; + case impl::error_category::invalid_op: + res = ErrorPolicy.on_error(error_kind::invalid_operation, res, invalid_sub_msg()); + break; + default: + break; + } + return float_basis{res}; + } + else + { + [[maybe_unused]] BasisType res {}; + + // The throw branches are inlined here (rather than calling impl::throw_*_sub) + // so BOOST_THROW_EXCEPTION captures operator- as the source location of the throw. + switch (impl::checked_float_subtraction(lhs_basis, rhs_basis, res)) + { + case impl::error_category::no_error: + break; + case impl::error_category::overflow: + #if !defined(BOOST_SAFE_NUMBERS_HAS_GPU_SUPPORT) + if (std::is_constant_evaluated()) { - throw std::overflow_error("Overflow detected in f32 subtraction"); // LCOV_EXCL_LINE + if constexpr (std::is_same_v) + { + throw std::overflow_error("Overflow detected in f32 subtraction"); // LCOV_EXCL_LINE + } + else + { + throw std::overflow_error("Overflow detected in f64 subtraction"); // LCOV_EXCL_LINE + } } else + #endif { - throw std::overflow_error("Overflow detected in f64 subtraction"); // LCOV_EXCL_LINE + BOOST_SAFE_NUMBERS_THROW_EXCEPTION(std::overflow_error, overflow_sub_msg()); } - } - else - #endif - { - BOOST_SAFE_NUMBERS_THROW_EXCEPTION(std::overflow_error, overflow_sub_msg()); - } - break; - case impl::error_category::underflow: - #if !defined(BOOST_SAFE_NUMBERS_HAS_GPU_SUPPORT) - if (std::is_constant_evaluated()) - { - if constexpr (std::is_same_v) + break; + case impl::error_category::underflow: + #if !defined(BOOST_SAFE_NUMBERS_HAS_GPU_SUPPORT) + if (std::is_constant_evaluated()) { - throw std::underflow_error("Underflow detected in f32 subtraction"); // LCOV_EXCL_LINE + if constexpr (std::is_same_v) + { + throw std::underflow_error("Underflow detected in f32 subtraction"); // LCOV_EXCL_LINE + } + else + { + throw std::underflow_error("Underflow detected in f64 subtraction"); // LCOV_EXCL_LINE + } } else + #endif { - throw std::underflow_error("Underflow detected in f64 subtraction"); // LCOV_EXCL_LINE + BOOST_SAFE_NUMBERS_THROW_EXCEPTION(std::underflow_error, underflow_sub_msg()); } - } - else - #endif - { - BOOST_SAFE_NUMBERS_THROW_EXCEPTION(std::underflow_error, underflow_sub_msg()); - } - break; - case impl::error_category::nan_op: - #if !defined(BOOST_SAFE_NUMBERS_HAS_GPU_SUPPORT) - if (std::is_constant_evaluated()) - { - if constexpr (std::is_same_v) + break; + case impl::error_category::nan_op: + #if !defined(BOOST_SAFE_NUMBERS_HAS_GPU_SUPPORT) + if (std::is_constant_evaluated()) { - throw std::domain_error("Operation with NAN detected in f32 subtraction"); // LCOV_EXCL_LINE + if constexpr (std::is_same_v) + { + throw std::domain_error("Operation with NAN detected in f32 subtraction"); // LCOV_EXCL_LINE + } + else + { + throw std::domain_error("Operation with NAN detected in f64 subtraction"); // LCOV_EXCL_LINE + } } else + #endif { - throw std::domain_error("Operation with NAN detected in f64 subtraction"); // LCOV_EXCL_LINE + BOOST_SAFE_NUMBERS_THROW_EXCEPTION(std::domain_error, nan_sub_msg()); } - } - else - #endif - { - BOOST_SAFE_NUMBERS_THROW_EXCEPTION(std::domain_error, nan_sub_msg()); - } - break; - case impl::error_category::invalid_op: - #if !defined(BOOST_SAFE_NUMBERS_HAS_GPU_SUPPORT) - if (std::is_constant_evaluated()) - { - if constexpr (std::is_same_v) + break; + case impl::error_category::invalid_op: + #if !defined(BOOST_SAFE_NUMBERS_HAS_GPU_SUPPORT) + if (std::is_constant_evaluated()) { - throw std::domain_error("Invalid operation (IEEE 754-2008 section 7.2) detected in f32 subtraction"); // LCOV_EXCL_LINE + if constexpr (std::is_same_v) + { + throw std::domain_error("Invalid operation (IEEE 754-2008 section 7.2) detected in f32 subtraction"); // LCOV_EXCL_LINE + } + else + { + throw std::domain_error("Invalid operation (IEEE 754-2008 section 7.2) detected in f64 subtraction"); // LCOV_EXCL_LINE + } } else + #endif { - throw std::domain_error("Invalid operation (IEEE 754-2008 section 7.2) detected in f64 subtraction"); // LCOV_EXCL_LINE + BOOST_SAFE_NUMBERS_THROW_EXCEPTION(std::domain_error, invalid_sub_msg()); } - } - else - #endif - { - BOOST_SAFE_NUMBERS_THROW_EXCEPTION(std::domain_error, invalid_sub_msg()); - } - break; - case impl::error_category::divide_by_zero: - BOOST_SAFE_NUMBERS_UNREACHABLE; // LCOV_EXCL_LINE - break; // LCOV_EXCL_LINE - default: - BOOST_SAFE_NUMBERS_UNREACHABLE; // LCOV_EXCL_LINE - break; // LCOV_EXCL_LINE - } + break; + case impl::error_category::divide_by_zero: + BOOST_SAFE_NUMBERS_UNREACHABLE; // LCOV_EXCL_LINE + break; // LCOV_EXCL_LINE + default: + BOOST_SAFE_NUMBERS_UNREACHABLE; // LCOV_EXCL_LINE + break; // LCOV_EXCL_LINE + } - return float_basis{res}; + return float_basis{res}; + } } +#ifdef _MSC_VER +# pragma warning(pop) +#endif + // ------------------------------ // Multiplication // ------------------------------ @@ -1271,108 +1376,153 @@ BOOST_SAFE_NUMBERS_HOST_DEVICE constexpr auto throw_invalid_mul() -> void } // namespace impl -template +// The saturating policy is raw IEEE 754, so overflow to infinity is the intended result +#ifdef _MSC_VER +# pragma warning(push) +# pragma warning(disable:4756) +#endif + +template BOOST_SAFE_NUMBERS_HOST_DEVICE -[[nodiscard]] constexpr auto operator*(const float_basis lhs, - const float_basis rhs) -> float_basis +[[nodiscard]] constexpr auto operator*(const float_basis lhs, + const float_basis rhs) + noexcept(policy_is_nothrow_arith()) -> float_basis { const auto lhs_basis {static_cast(lhs)}; const auto rhs_basis {static_cast(rhs)}; - [[maybe_unused]] BasisType res {}; - // The throw branches are inlined here (rather than calling impl::throw_*_mul) - // so BOOST_THROW_EXCEPTION captures operator* as the source location of the throw. - switch (impl::checked_float_multiplication(lhs_basis, rhs_basis, res)) + if constexpr (policy_equals(overflow_policy::saturate)) { - case impl::error_category::no_error: - break; - case impl::error_category::overflow: - #if !defined(BOOST_SAFE_NUMBERS_HAS_GPU_SUPPORT) - if (std::is_constant_evaluated()) - { - if constexpr (std::is_same_v) + // Raw IEEE 754 semantics: overflow saturates to infinity and NaN propagates. + // Same expression as the value component of overflowing_mul. + return float_basis{static_cast(lhs_basis * rhs_basis)}; + } + else if constexpr (is_user_handler_v) + { + // The classifier stores the raw IEEE 754 result, which is the defined + // fallback value handed to the handler + BasisType res {}; + switch (impl::checked_float_multiplication(lhs_basis, rhs_basis, res)) + { + case impl::error_category::overflow: + res = ErrorPolicy.on_error(error_kind::overflow, res, overflow_mul_msg()); + break; + case impl::error_category::underflow: + res = ErrorPolicy.on_error(error_kind::underflow, res, underflow_mul_msg()); + break; + case impl::error_category::nan_op: + res = ErrorPolicy.on_error(error_kind::nan_operation, res, nan_mul_msg()); + break; + case impl::error_category::invalid_op: + res = ErrorPolicy.on_error(error_kind::invalid_operation, res, invalid_mul_msg()); + break; + default: + break; + } + return float_basis{res}; + } + else + { + [[maybe_unused]] BasisType res {}; + + // The throw branches are inlined here (rather than calling impl::throw_*_mul) + // so BOOST_THROW_EXCEPTION captures operator* as the source location of the throw. + switch (impl::checked_float_multiplication(lhs_basis, rhs_basis, res)) + { + case impl::error_category::no_error: + break; + case impl::error_category::overflow: + #if !defined(BOOST_SAFE_NUMBERS_HAS_GPU_SUPPORT) + if (std::is_constant_evaluated()) { - throw std::overflow_error("Overflow detected in f32 multiplication"); // LCOV_EXCL_LINE + if constexpr (std::is_same_v) + { + throw std::overflow_error("Overflow detected in f32 multiplication"); // LCOV_EXCL_LINE + } + else + { + throw std::overflow_error("Overflow detected in f64 multiplication"); // LCOV_EXCL_LINE + } } else + #endif { - throw std::overflow_error("Overflow detected in f64 multiplication"); // LCOV_EXCL_LINE + BOOST_SAFE_NUMBERS_THROW_EXCEPTION(std::overflow_error, overflow_mul_msg()); } - } - else - #endif - { - BOOST_SAFE_NUMBERS_THROW_EXCEPTION(std::overflow_error, overflow_mul_msg()); - } - break; - case impl::error_category::underflow: - #if !defined(BOOST_SAFE_NUMBERS_HAS_GPU_SUPPORT) - if (std::is_constant_evaluated()) - { - if constexpr (std::is_same_v) + break; + case impl::error_category::underflow: + #if !defined(BOOST_SAFE_NUMBERS_HAS_GPU_SUPPORT) + if (std::is_constant_evaluated()) { - throw std::underflow_error("Underflow detected in f32 multiplication"); // LCOV_EXCL_LINE + if constexpr (std::is_same_v) + { + throw std::underflow_error("Underflow detected in f32 multiplication"); // LCOV_EXCL_LINE + } + else + { + throw std::underflow_error("Underflow detected in f64 multiplication"); // LCOV_EXCL_LINE + } } else + #endif { - throw std::underflow_error("Underflow detected in f64 multiplication"); // LCOV_EXCL_LINE + BOOST_SAFE_NUMBERS_THROW_EXCEPTION(std::underflow_error, underflow_mul_msg()); } - } - else - #endif - { - BOOST_SAFE_NUMBERS_THROW_EXCEPTION(std::underflow_error, underflow_mul_msg()); - } - break; - case impl::error_category::nan_op: - #if !defined(BOOST_SAFE_NUMBERS_HAS_GPU_SUPPORT) - if (std::is_constant_evaluated()) - { - if constexpr (std::is_same_v) + break; + case impl::error_category::nan_op: + #if !defined(BOOST_SAFE_NUMBERS_HAS_GPU_SUPPORT) + if (std::is_constant_evaluated()) { - throw std::domain_error("Operation with NAN detected in f32 multiplication"); // LCOV_EXCL_LINE + if constexpr (std::is_same_v) + { + throw std::domain_error("Operation with NAN detected in f32 multiplication"); // LCOV_EXCL_LINE + } + else + { + throw std::domain_error("Operation with NAN detected in f64 multiplication"); // LCOV_EXCL_LINE + } } else + #endif { - throw std::domain_error("Operation with NAN detected in f64 multiplication"); // LCOV_EXCL_LINE + BOOST_SAFE_NUMBERS_THROW_EXCEPTION(std::domain_error, nan_mul_msg()); } - } - else - #endif - { - BOOST_SAFE_NUMBERS_THROW_EXCEPTION(std::domain_error, nan_mul_msg()); - } - break; - case impl::error_category::invalid_op: - #if !defined(BOOST_SAFE_NUMBERS_HAS_GPU_SUPPORT) - if (std::is_constant_evaluated()) - { - if constexpr (std::is_same_v) + break; + case impl::error_category::invalid_op: + #if !defined(BOOST_SAFE_NUMBERS_HAS_GPU_SUPPORT) + if (std::is_constant_evaluated()) { - throw std::domain_error("Invalid operation (IEEE 754-2008 section 7.2) detected in f32 multiplication"); // LCOV_EXCL_LINE + if constexpr (std::is_same_v) + { + throw std::domain_error("Invalid operation (IEEE 754-2008 section 7.2) detected in f32 multiplication"); // LCOV_EXCL_LINE + } + else + { + throw std::domain_error("Invalid operation (IEEE 754-2008 section 7.2) detected in f64 multiplication"); // LCOV_EXCL_LINE + } } else + #endif { - throw std::domain_error("Invalid operation (IEEE 754-2008 section 7.2) detected in f64 multiplication"); // LCOV_EXCL_LINE + BOOST_SAFE_NUMBERS_THROW_EXCEPTION(std::domain_error, invalid_mul_msg()); } - } - else - #endif - { - BOOST_SAFE_NUMBERS_THROW_EXCEPTION(std::domain_error, invalid_mul_msg()); - } - break; - case impl::error_category::divide_by_zero: - BOOST_SAFE_NUMBERS_UNREACHABLE; // LCOV_EXCL_LINE - break; // LCOV_EXCL_LINE - default: - BOOST_SAFE_NUMBERS_UNREACHABLE; // LCOV_EXCL_LINE - break; // LCOV_EXCL_LINE - } + break; + case impl::error_category::divide_by_zero: + BOOST_SAFE_NUMBERS_UNREACHABLE; // LCOV_EXCL_LINE + break; // LCOV_EXCL_LINE + default: + BOOST_SAFE_NUMBERS_UNREACHABLE; // LCOV_EXCL_LINE + break; // LCOV_EXCL_LINE + } - return float_basis{res}; + return float_basis{res}; + } } +#ifdef _MSC_VER +# pragma warning(pop) +#endif + // ------------------------------ // Division // ------------------------------ @@ -1567,124 +1717,172 @@ BOOST_SAFE_NUMBERS_HOST_DEVICE constexpr auto throw_divbyzero_div() -> void } // namespace impl -template +// The saturating policy is raw IEEE 754, so overflow to infinity is the intended result +#ifdef _MSC_VER +# pragma warning(push) +# pragma warning(disable:4756) +#endif + +template BOOST_SAFE_NUMBERS_HOST_DEVICE -[[nodiscard]] constexpr auto operator/(const float_basis lhs, - const float_basis rhs) -> float_basis +[[nodiscard]] constexpr auto operator/(const float_basis lhs, + const float_basis rhs) + noexcept(policy_is_nothrow_arith()) -> float_basis { const auto lhs_basis {static_cast(lhs)}; const auto rhs_basis {static_cast(rhs)}; - [[maybe_unused]] BasisType res {}; - // The throw branches are inlined here (rather than calling impl::throw_*_div) - // so BOOST_THROW_EXCEPTION captures operator/ as the source location of the throw. - switch (impl::checked_float_division(lhs_basis, rhs_basis, res)) + if constexpr (policy_equals(overflow_policy::saturate)) { - case impl::error_category::no_error: - break; - case impl::error_category::overflow: - #if !defined(BOOST_SAFE_NUMBERS_HAS_GPU_SUPPORT) - if (std::is_constant_evaluated()) - { - if constexpr (std::is_same_v) + // Raw IEEE 754 semantics: overflow saturates to infinity and NaN propagates. + // Same expression as the value component of overflowing_div. + return float_basis{static_cast(lhs_basis / rhs_basis)}; + } + else if constexpr (is_user_handler_v) + { + // The classifier stores the raw IEEE 754 result, which is the defined + // fallback value handed to the handler + BasisType res {}; + switch (impl::checked_float_division(lhs_basis, rhs_basis, res)) + { + case impl::error_category::overflow: + res = ErrorPolicy.on_error(error_kind::overflow, res, overflow_div_msg()); + break; + case impl::error_category::underflow: + res = ErrorPolicy.on_error(error_kind::underflow, res, underflow_div_msg()); + break; + case impl::error_category::nan_op: + res = ErrorPolicy.on_error(error_kind::nan_operation, res, nan_div_msg()); + break; + case impl::error_category::invalid_op: + res = ErrorPolicy.on_error(error_kind::invalid_operation, res, invalid_div_msg()); + break; + case impl::error_category::divide_by_zero: + res = ErrorPolicy.on_error(error_kind::divide_by_zero, res, divbyzero_div_msg()); + break; + default: + break; + } + return float_basis{res}; + } + else + { + [[maybe_unused]] BasisType res {}; + + // The throw branches are inlined here (rather than calling impl::throw_*_div) + // so BOOST_THROW_EXCEPTION captures operator/ as the source location of the throw. + switch (impl::checked_float_division(lhs_basis, rhs_basis, res)) + { + case impl::error_category::no_error: + break; + case impl::error_category::overflow: + #if !defined(BOOST_SAFE_NUMBERS_HAS_GPU_SUPPORT) + if (std::is_constant_evaluated()) { - throw std::overflow_error("Overflow detected in f32 division"); // LCOV_EXCL_LINE + if constexpr (std::is_same_v) + { + throw std::overflow_error("Overflow detected in f32 division"); // LCOV_EXCL_LINE + } + else + { + throw std::overflow_error("Overflow detected in f64 division"); // LCOV_EXCL_LINE + } } else + #endif { - throw std::overflow_error("Overflow detected in f64 division"); // LCOV_EXCL_LINE + BOOST_SAFE_NUMBERS_THROW_EXCEPTION(std::overflow_error, overflow_div_msg()); } - } - else - #endif - { - BOOST_SAFE_NUMBERS_THROW_EXCEPTION(std::overflow_error, overflow_div_msg()); - } - break; - case impl::error_category::underflow: - #if !defined(BOOST_SAFE_NUMBERS_HAS_GPU_SUPPORT) - if (std::is_constant_evaluated()) - { - if constexpr (std::is_same_v) + break; + case impl::error_category::underflow: + #if !defined(BOOST_SAFE_NUMBERS_HAS_GPU_SUPPORT) + if (std::is_constant_evaluated()) { - throw std::underflow_error("Underflow detected in f32 division"); // LCOV_EXCL_LINE + if constexpr (std::is_same_v) + { + throw std::underflow_error("Underflow detected in f32 division"); // LCOV_EXCL_LINE + } + else + { + throw std::underflow_error("Underflow detected in f64 division"); // LCOV_EXCL_LINE + } } else + #endif { - throw std::underflow_error("Underflow detected in f64 division"); // LCOV_EXCL_LINE + BOOST_SAFE_NUMBERS_THROW_EXCEPTION(std::underflow_error, underflow_div_msg()); } - } - else - #endif - { - BOOST_SAFE_NUMBERS_THROW_EXCEPTION(std::underflow_error, underflow_div_msg()); - } - break; - case impl::error_category::nan_op: - #if !defined(BOOST_SAFE_NUMBERS_HAS_GPU_SUPPORT) - if (std::is_constant_evaluated()) - { - if constexpr (std::is_same_v) + break; + case impl::error_category::nan_op: + #if !defined(BOOST_SAFE_NUMBERS_HAS_GPU_SUPPORT) + if (std::is_constant_evaluated()) { - throw std::domain_error("Operation with NAN detected in f32 division"); // LCOV_EXCL_LINE + if constexpr (std::is_same_v) + { + throw std::domain_error("Operation with NAN detected in f32 division"); // LCOV_EXCL_LINE + } + else + { + throw std::domain_error("Operation with NAN detected in f64 division"); // LCOV_EXCL_LINE + } } else + #endif { - throw std::domain_error("Operation with NAN detected in f64 division"); // LCOV_EXCL_LINE + BOOST_SAFE_NUMBERS_THROW_EXCEPTION(std::domain_error, nan_div_msg()); } - } - else - #endif - { - BOOST_SAFE_NUMBERS_THROW_EXCEPTION(std::domain_error, nan_div_msg()); - } - break; - case impl::error_category::invalid_op: - #if !defined(BOOST_SAFE_NUMBERS_HAS_GPU_SUPPORT) - if (std::is_constant_evaluated()) - { - if constexpr (std::is_same_v) + break; + case impl::error_category::invalid_op: + #if !defined(BOOST_SAFE_NUMBERS_HAS_GPU_SUPPORT) + if (std::is_constant_evaluated()) { - throw std::domain_error("Invalid operation (IEEE 754-2008 section 7.2) detected in f32 division"); // LCOV_EXCL_LINE + if constexpr (std::is_same_v) + { + throw std::domain_error("Invalid operation (IEEE 754-2008 section 7.2) detected in f32 division"); // LCOV_EXCL_LINE + } + else + { + throw std::domain_error("Invalid operation (IEEE 754-2008 section 7.2) detected in f64 division"); // LCOV_EXCL_LINE + } } else + #endif { - throw std::domain_error("Invalid operation (IEEE 754-2008 section 7.2) detected in f64 division"); // LCOV_EXCL_LINE + BOOST_SAFE_NUMBERS_THROW_EXCEPTION(std::domain_error, invalid_div_msg()); } - } - else - #endif - { - BOOST_SAFE_NUMBERS_THROW_EXCEPTION(std::domain_error, invalid_div_msg()); - } - break; - case impl::error_category::divide_by_zero: - #if !defined(BOOST_SAFE_NUMBERS_HAS_GPU_SUPPORT) - if (std::is_constant_evaluated()) - { - if constexpr (std::is_same_v) + break; + case impl::error_category::divide_by_zero: + #if !defined(BOOST_SAFE_NUMBERS_HAS_GPU_SUPPORT) + if (std::is_constant_evaluated()) { - throw std::domain_error("Division by zero detected in f32 division"); // LCOV_EXCL_LINE + if constexpr (std::is_same_v) + { + throw std::domain_error("Division by zero detected in f32 division"); // LCOV_EXCL_LINE + } + else + { + throw std::domain_error("Division by zero detected in f64 division"); // LCOV_EXCL_LINE + } } else + #endif { - throw std::domain_error("Division by zero detected in f64 division"); // LCOV_EXCL_LINE + BOOST_SAFE_NUMBERS_THROW_EXCEPTION(std::domain_error, divbyzero_div_msg()); } - } - else - #endif - { - BOOST_SAFE_NUMBERS_THROW_EXCEPTION(std::domain_error, divbyzero_div_msg()); - } - break; - default: - BOOST_SAFE_NUMBERS_UNREACHABLE; // LCOV_EXCL_LINE - break; // LCOV_EXCL_LINE - } + break; + default: + BOOST_SAFE_NUMBERS_UNREACHABLE; // LCOV_EXCL_LINE + break; // LCOV_EXCL_LINE + } - return float_basis{res}; + return float_basis{res}; + } } +#ifdef _MSC_VER +# pragma warning(pop) +#endif + } // namespace boost::safe_numbers::detail // Block any mixed floating point type operation (e.g. f32 and f64) with a @@ -1692,14 +1890,20 @@ BOOST_SAFE_NUMBERS_HOST_DEVICE // outside the namespace so the generated operators are plain free functions. #define BOOST_SAFE_NUMBERS_DEFINE_MIXED_FLOAT_OP(OP_NAME, OP_SYMBOL) \ -template \ - requires (!std::is_same_v) \ +template \ + requires (!std::is_same_v || boost::safe_numbers::detail::policies_differ()) \ BOOST_SAFE_NUMBERS_HOST_DEVICE \ -constexpr auto OP_SYMBOL(const boost::safe_numbers::detail::float_basis, \ - const boost::safe_numbers::detail::float_basis) \ +constexpr auto OP_SYMBOL(const boost::safe_numbers::detail::float_basis, \ + const boost::safe_numbers::detail::float_basis) \ { \ - if constexpr (std::is_same_v) \ + if constexpr (std::is_same_v) \ + { \ + static_assert(boost::safe_numbers::detail::dependent_false, \ + "Can not perform " OP_NAME " between same width types with different overflow policies " \ + "(e.g. f32 and sat_f32): convert explicitly through basis_type first"); \ + } \ + else if constexpr (std::is_same_v) \ { \ if constexpr (std::is_same_v) \ { \ @@ -1726,7 +1930,7 @@ constexpr auto OP_SYMBOL(const boost::safe_numbers::detail::float_basis, "Can not perform " OP_NAME " on mixed floating point types"); \ } \ \ - return boost::safe_numbers::detail::float_basis{LHSBasis{0}}; \ + return boost::safe_numbers::detail::float_basis{LHSBasis{0}}; \ } namespace boost::safe_numbers::detail { @@ -1760,55 +1964,65 @@ BOOST_SAFE_NUMBERS_HOST_DEVICE [[nodiscard]] constexpr auto nonfinite_result(con } // namespace detail::impl +// These return the raw IEEE result alongside the flag, so overflow is expected here +#ifdef _MSC_VER +# pragma warning(push) +# pragma warning(disable:4756) +#endif + // Non-throwing counterparts of the checked float operators for hot loops. The // value is the raw IEEE result and the bool is true exactly when the checked // operator would have thrown. The branch-free form keeps loops vectorizable: // accumulate the flags in an unsigned value and test once at a boundary. -template -BOOST_SAFE_NUMBERS_HOST_DEVICE [[nodiscard]] constexpr auto overflowing_add(const detail::float_basis lhs, - const detail::float_basis rhs) noexcept - -> std::pair, bool> +template +BOOST_SAFE_NUMBERS_HOST_DEVICE [[nodiscard]] constexpr auto overflowing_add(const detail::float_basis lhs, + const detail::float_basis rhs) noexcept + -> std::pair, bool> { const auto res {static_cast(static_cast(lhs) + static_cast(rhs))}; - return std::make_pair(detail::float_basis{res}, detail::impl::nonfinite_result(res)); + return std::make_pair(detail::float_basis{res}, detail::impl::nonfinite_result(res)); } BOOST_SAFE_NUMBERS_DEFINE_MIXED_FLOAT_OP("overflowing addition", overflowing_add) -template -BOOST_SAFE_NUMBERS_HOST_DEVICE [[nodiscard]] constexpr auto overflowing_sub(const detail::float_basis lhs, - const detail::float_basis rhs) noexcept - -> std::pair, bool> +template +BOOST_SAFE_NUMBERS_HOST_DEVICE [[nodiscard]] constexpr auto overflowing_sub(const detail::float_basis lhs, + const detail::float_basis rhs) noexcept + -> std::pair, bool> { const auto res {static_cast(static_cast(lhs) - static_cast(rhs))}; - return std::make_pair(detail::float_basis{res}, detail::impl::nonfinite_result(res)); + return std::make_pair(detail::float_basis{res}, detail::impl::nonfinite_result(res)); } BOOST_SAFE_NUMBERS_DEFINE_MIXED_FLOAT_OP("overflowing subtraction", overflowing_sub) -template -BOOST_SAFE_NUMBERS_HOST_DEVICE [[nodiscard]] constexpr auto overflowing_mul(const detail::float_basis lhs, - const detail::float_basis rhs) noexcept - -> std::pair, bool> +template +BOOST_SAFE_NUMBERS_HOST_DEVICE [[nodiscard]] constexpr auto overflowing_mul(const detail::float_basis lhs, + const detail::float_basis rhs) noexcept + -> std::pair, bool> { const auto res {static_cast(static_cast(lhs) * static_cast(rhs))}; - return std::make_pair(detail::float_basis{res}, detail::impl::nonfinite_result(res)); + return std::make_pair(detail::float_basis{res}, detail::impl::nonfinite_result(res)); } BOOST_SAFE_NUMBERS_DEFINE_MIXED_FLOAT_OP("overflowing multiplication", overflowing_mul) -template -BOOST_SAFE_NUMBERS_HOST_DEVICE [[nodiscard]] constexpr auto overflowing_div(const detail::float_basis lhs, - const detail::float_basis rhs) noexcept - -> std::pair, bool> +template +BOOST_SAFE_NUMBERS_HOST_DEVICE [[nodiscard]] constexpr auto overflowing_div(const detail::float_basis lhs, + const detail::float_basis rhs) noexcept + -> std::pair, bool> { const auto res {static_cast(static_cast(lhs) / static_cast(rhs))}; - return std::make_pair(detail::float_basis{res}, detail::impl::nonfinite_result(res)); + return std::make_pair(detail::float_basis{res}, detail::impl::nonfinite_result(res)); } BOOST_SAFE_NUMBERS_DEFINE_MIXED_FLOAT_OP("overflowing division", overflowing_div) +#ifdef _MSC_VER +# pragma warning(pop) +#endif + } // namespace boost::safe_numbers #undef BOOST_SAFE_NUMBERS_DEFINE_MIXED_FLOAT_OP diff --git a/include/boost/safe_numbers/detail/signed_integer_basis.hpp b/include/boost/safe_numbers/detail/signed_integer_basis.hpp index 3bf4584..58b8b1b 100644 --- a/include/boost/safe_numbers/detail/signed_integer_basis.hpp +++ b/include/boost/safe_numbers/detail/signed_integer_basis.hpp @@ -28,7 +28,7 @@ namespace boost::safe_numbers::detail { -template +template class signed_integer_basis { public: @@ -36,6 +36,22 @@ class signed_integer_basis // This is exposed to the user so that they can convert back to built-in using basis_type = BasisType; + static_assert(is_overflow_policy_v || error_handler_for, + "ErrorPolicy must be a boost::safe_numbers::overflow_policy enumerator or a stateless " + "handler type providing on_error(error_kind, BasisType, const char*) returning BasisType"); + + static_assert(!is_value_returning_policy(), + "overflow_tuple, checked, and widen change the result type of every operation, " + "so they can not be type-level policies: use the overflowing_*, checked_*, " + "and widening_* free functions instead"); + + static_assert(!is_overflow_policy_v || is_valid_type_policy(basis_kind::signed_integer), + "signed_integer_basis supports overflow_policy::throw_exception, " + "overflow_policy::saturate, and overflow_policy::strict"); + + // Exposed so that generic code and tests can query the type-level policy + static constexpr auto error_policy {ErrorPolicy}; + private: BasisType basis_ {0U}; @@ -63,22 +79,28 @@ class signed_integer_basis BOOST_SAFE_NUMBERS_HOST_DEVICE [[nodiscard]] constexpr auto operator+() const noexcept -> signed_integer_basis; - BOOST_SAFE_NUMBERS_HOST_DEVICE [[nodiscard]] constexpr auto operator-() const -> signed_integer_basis; + BOOST_SAFE_NUMBERS_HOST_DEVICE [[nodiscard]] constexpr auto operator-() const + noexcept(policy_is_nothrow_arith()) -> signed_integer_basis; - template - BOOST_SAFE_NUMBERS_HOST_DEVICE constexpr auto operator+=(signed_integer_basis rhs) -> signed_integer_basis&; + template + BOOST_SAFE_NUMBERS_HOST_DEVICE constexpr auto operator+=(signed_integer_basis rhs) + noexcept(policy_is_nothrow_arith()) -> signed_integer_basis&; - template - BOOST_SAFE_NUMBERS_HOST_DEVICE constexpr auto operator-=(signed_integer_basis rhs) -> signed_integer_basis&; + template + BOOST_SAFE_NUMBERS_HOST_DEVICE constexpr auto operator-=(signed_integer_basis rhs) + noexcept(policy_is_nothrow_arith()) -> signed_integer_basis&; - template - BOOST_SAFE_NUMBERS_HOST_DEVICE constexpr auto operator*=(signed_integer_basis rhs) -> signed_integer_basis&; + template + BOOST_SAFE_NUMBERS_HOST_DEVICE constexpr auto operator*=(signed_integer_basis rhs) + noexcept(policy_is_nothrow_arith()) -> signed_integer_basis&; - template - BOOST_SAFE_NUMBERS_HOST_DEVICE constexpr auto operator/=(signed_integer_basis rhs) -> signed_integer_basis&; + template + BOOST_SAFE_NUMBERS_HOST_DEVICE constexpr auto operator/=(signed_integer_basis rhs) + noexcept(policy_is_nothrow_div()) -> signed_integer_basis&; - template - BOOST_SAFE_NUMBERS_HOST_DEVICE constexpr auto operator%=(signed_integer_basis rhs) -> signed_integer_basis&; + template + BOOST_SAFE_NUMBERS_HOST_DEVICE constexpr auto operator%=(signed_integer_basis rhs) + noexcept(policy_is_nothrow_div()) -> signed_integer_basis&; BOOST_SAFE_NUMBERS_HOST_DEVICE constexpr auto operator&=(signed_integer_basis) -> signed_integer_basis& { @@ -110,13 +132,17 @@ class signed_integer_basis return *this; // LCOV_EXCL_LINE : deliberately unreachable } - BOOST_SAFE_NUMBERS_HOST_DEVICE constexpr auto operator++() -> signed_integer_basis&; + BOOST_SAFE_NUMBERS_HOST_DEVICE constexpr auto operator++() + noexcept(policy_is_nothrow_arith()) -> signed_integer_basis&; - BOOST_SAFE_NUMBERS_HOST_DEVICE constexpr auto operator++(int) -> signed_integer_basis; + BOOST_SAFE_NUMBERS_HOST_DEVICE constexpr auto operator++(int) + noexcept(policy_is_nothrow_arith()) -> signed_integer_basis; - BOOST_SAFE_NUMBERS_HOST_DEVICE constexpr auto operator--() -> signed_integer_basis&; + BOOST_SAFE_NUMBERS_HOST_DEVICE constexpr auto operator--() + noexcept(policy_is_nothrow_arith()) -> signed_integer_basis&; - BOOST_SAFE_NUMBERS_HOST_DEVICE constexpr auto operator--(int) -> signed_integer_basis; + BOOST_SAFE_NUMBERS_HOST_DEVICE constexpr auto operator--(int) + noexcept(policy_is_nothrow_arith()) -> signed_integer_basis; }; // Helper for diagnostic messages @@ -320,9 +346,9 @@ BOOST_SAFE_NUMBERS_HOST_DEVICE constexpr auto signed_underflow_conversion_msg() } } -template +template template -BOOST_SAFE_NUMBERS_HOST_DEVICE constexpr signed_integer_basis::operator OtherBasis() const +BOOST_SAFE_NUMBERS_HOST_DEVICE constexpr signed_integer_basis::operator OtherBasis() const { if constexpr (sizeof(OtherBasis) < sizeof(BasisType)) { @@ -339,18 +365,34 @@ BOOST_SAFE_NUMBERS_HOST_DEVICE constexpr signed_integer_basis::operat return static_cast(basis_); } -template -BOOST_SAFE_NUMBERS_HOST_DEVICE constexpr auto signed_integer_basis::operator+() const noexcept -> signed_integer_basis +template +BOOST_SAFE_NUMBERS_HOST_DEVICE constexpr auto signed_integer_basis::operator+() const noexcept -> signed_integer_basis { return signed_integer_basis{basis_}; } -template -BOOST_SAFE_NUMBERS_HOST_DEVICE constexpr auto signed_integer_basis::operator-() const -> signed_integer_basis +template +BOOST_SAFE_NUMBERS_HOST_DEVICE constexpr auto signed_integer_basis::operator-() const + noexcept(policy_is_nothrow_arith()) -> signed_integer_basis { if (basis_ == std::numeric_limits::min()) [[unlikely]] { - BOOST_SAFE_NUMBERS_THROW_EXCEPTION(std::domain_error, signed_unary_minus_overflow_msg()); + if constexpr (policy_equals(overflow_policy::throw_exception)) + { + BOOST_SAFE_NUMBERS_THROW_EXCEPTION(std::domain_error, signed_unary_minus_overflow_msg()); + } + else if constexpr (policy_equals(overflow_policy::saturate)) + { + return signed_integer_basis{std::numeric_limits::max()}; + } + else if constexpr (policy_equals(overflow_policy::strict)) + { + std::exit(EXIT_FAILURE); + } + else + { + return signed_integer_basis{ErrorPolicy.on_error(error_kind::overflow, std::numeric_limits::min(), signed_unary_minus_overflow_msg())}; + } } return signed_integer_basis{static_cast(-basis_)}; @@ -488,16 +530,17 @@ BOOST_SAFE_NUMBERS_HOST_DEVICE constexpr auto signed_no_intrin_add(const T lhs, return signed_overflow_status::no_error; } -template +template struct signed_add_helper { + template BOOST_SAFE_NUMBERS_HOST_DEVICE - [[nodiscard]] static constexpr auto apply(const signed_integer_basis lhs, - const signed_integer_basis rhs) - noexcept(Policy != overflow_policy::throw_exception) - -> signed_integer_basis + [[nodiscard]] static constexpr auto apply(const signed_integer_basis lhs, + const signed_integer_basis rhs) + noexcept(policy_is_nothrow_arith()) + -> signed_integer_basis { - using result_type = signed_integer_basis; + using result_type = signed_integer_basis; const auto lhs_basis {static_cast(lhs)}; const auto rhs_basis {static_cast(rhs)}; @@ -506,7 +549,9 @@ struct signed_add_helper auto handle_error = [&result](signed_overflow_status status) { #if !defined(BOOST_SAFE_NUMBERS_HAS_GPU_SUPPORT) - if (std::is_constant_evaluated()) + // Saturation produces a defined value, so it must keep evaluating at + // constant evaluation time instead of failing the build with a throw + if (std::is_constant_evaluated() && !policy_equals(overflow_policy::saturate) && !is_user_handler_v) { if (status == signed_overflow_status::overflow) { @@ -558,7 +603,7 @@ struct signed_add_helper else #endif { - if constexpr (Policy == overflow_policy::throw_exception) + if constexpr (policy_equals(overflow_policy::throw_exception)) { static_cast(result); @@ -571,7 +616,7 @@ struct signed_add_helper BOOST_SAFE_NUMBERS_THROW_EXCEPTION(std::underflow_error, signed_underflow_add_msg()); } } - else if constexpr (Policy == overflow_policy::saturate) + else if constexpr (policy_equals(overflow_policy::saturate)) { if (status == signed_overflow_status::overflow) { @@ -582,11 +627,22 @@ struct signed_add_helper result = std::numeric_limits::min(); } } - else if constexpr (Policy == overflow_policy::strict) + else if constexpr (policy_equals(overflow_policy::strict)) { static_cast(result); std::exit(EXIT_FAILURE); } + else if constexpr (is_user_handler_v) + { + if (status == signed_overflow_status::overflow) + { + result = Policy.on_error(error_kind::overflow, result, signed_overflow_add_msg()); + } + else + { + result = Policy.on_error(error_kind::underflow, result, signed_underflow_add_msg()); + } + } else { static_cast(result); @@ -609,7 +665,7 @@ struct signed_add_helper const auto status {impl::signed_intrin_add(lhs_basis, rhs_basis, result)}; #ifdef BOOST_SAFE_NUMBERS_ENABLE_COMPILE_ASSERT - if constexpr (Policy == overflow_policy::throw_exception || Policy == overflow_policy::strict) + if constexpr (policy_equals(overflow_policy::throw_exception) || policy_equals(overflow_policy::strict)) { BOOST_SAFE_NUMBERS_COMPILE_ASSERT_CONST_P(status == signed_overflow_status::no_error, "signed addition overflow"); } @@ -631,7 +687,7 @@ struct signed_add_helper const auto status {impl::signed_no_intrin_add(lhs_basis, rhs_basis, result)}; #ifdef BOOST_SAFE_NUMBERS_ENABLE_COMPILE_ASSERT - if constexpr (Policy == overflow_policy::throw_exception || Policy == overflow_policy::strict) + if constexpr (policy_equals(overflow_policy::throw_exception) || policy_equals(overflow_policy::strict)) { BOOST_SAFE_NUMBERS_COMPILE_ASSERT_CONST_P(status == signed_overflow_status::no_error, "signed addition overflow"); } @@ -650,12 +706,13 @@ struct signed_add_helper template struct signed_add_helper { + template BOOST_SAFE_NUMBERS_HOST_DEVICE - [[nodiscard]] static constexpr auto apply(const signed_integer_basis lhs, - const signed_integer_basis rhs) noexcept - -> std::pair, bool> + [[nodiscard]] static constexpr auto apply(const signed_integer_basis lhs, + const signed_integer_basis rhs) noexcept + -> std::pair, bool> { - using result_type = signed_integer_basis; + using result_type = signed_integer_basis; const auto lhs_basis {static_cast(lhs)}; const auto rhs_basis {static_cast(rhs)}; @@ -673,12 +730,13 @@ struct signed_add_helper template struct signed_add_helper { + template BOOST_SAFE_NUMBERS_HOST_DEVICE - [[nodiscard]] static constexpr auto apply(const signed_integer_basis lhs, - const signed_integer_basis rhs) noexcept - -> std::optional> + [[nodiscard]] static constexpr auto apply(const signed_integer_basis lhs, + const signed_integer_basis rhs) noexcept + -> std::optional> { - using result_type = signed_integer_basis; + using result_type = signed_integer_basis; const auto lhs_basis {static_cast(lhs)}; const auto rhs_basis {static_cast(rhs)}; @@ -698,108 +756,119 @@ struct signed_add_helper template struct signed_add_helper { + template BOOST_SAFE_NUMBERS_HOST_DEVICE - [[nodiscard]] static constexpr auto apply(const signed_integer_basis lhs, - const signed_integer_basis rhs) noexcept + [[nodiscard]] static constexpr auto apply(const signed_integer_basis lhs, + const signed_integer_basis rhs) noexcept { using promoted = signed_promoted_type; static_assert(!std::is_same_v, "Widening policy with int128_t is not supported"); - using result_type = signed_integer_basis; + using result_type = signed_integer_basis; return result_type{static_cast(static_cast(static_cast(lhs)) + static_cast(rhs))}; } }; -template +template BOOST_SAFE_NUMBERS_HOST_DEVICE -[[nodiscard]] constexpr auto add_impl(const signed_integer_basis lhs, - const signed_integer_basis rhs) - noexcept(Policy == overflow_policy::saturate || Policy == overflow_policy::overflow_tuple || - Policy == overflow_policy::checked || Policy == overflow_policy::strict || - Policy == overflow_policy::widen) +[[nodiscard]] constexpr auto add_impl(const signed_integer_basis lhs, + const signed_integer_basis rhs) + noexcept(policy_is_nothrow_arith()) { return signed_add_helper::apply(lhs,rhs); } } // namespace impl -template +template BOOST_SAFE_NUMBERS_HOST_DEVICE -[[nodiscard]] constexpr auto operator+(const signed_integer_basis lhs, - const signed_integer_basis rhs) -> signed_integer_basis +[[nodiscard]] constexpr auto operator+(const signed_integer_basis lhs, + const signed_integer_basis rhs) + noexcept(policy_is_nothrow_arith()) -> signed_integer_basis { #if !defined(BOOST_SAFE_NUMBERS_HAS_GPU_SUPPORT) - if (std::is_constant_evaluated()) + // Only the throwing policy needs the duplicated diagnostics; the test is if constexpr + // so the throw is discarded where the operator is noexcept (GCC -Wterminate) + if constexpr (policy_equals(overflow_policy::throw_exception)) { - BasisType res {}; - const auto status {impl::signed_no_intrin_add(static_cast(lhs), static_cast(rhs), res)}; - if (status == impl::signed_overflow_status::overflow) - { - if constexpr (std::is_same_v) - { - throw std::overflow_error("Overflow detected in i8 addition"); // LCOV_EXCL_LINE - } - else if constexpr (std::is_same_v) - { - throw std::overflow_error("Overflow detected in i16 addition"); // LCOV_EXCL_LINE - } - else if constexpr (std::is_same_v) - { - throw std::overflow_error("Overflow detected in i32 addition"); // LCOV_EXCL_LINE - } - else if constexpr (std::is_same_v) - { - throw std::overflow_error("Overflow detected in i64 addition"); // LCOV_EXCL_LINE - } - else - { - throw std::overflow_error("Overflow detected in i128 addition"); // LCOV_EXCL_LINE - } - } - else if (status == impl::signed_overflow_status::underflow) + if (std::is_constant_evaluated()) { - if constexpr (std::is_same_v) - { - throw std::underflow_error("Underflow detected in i8 addition"); // LCOV_EXCL_LINE - } - else if constexpr (std::is_same_v) - { - throw std::underflow_error("Underflow detected in i16 addition"); // LCOV_EXCL_LINE - } - else if constexpr (std::is_same_v) - { - throw std::underflow_error("Underflow detected in i32 addition"); // LCOV_EXCL_LINE - } - else if constexpr (std::is_same_v) + BasisType res {}; + const auto status {impl::signed_no_intrin_add(static_cast(lhs), static_cast(rhs), res)}; + if (status == impl::signed_overflow_status::overflow) { - throw std::underflow_error("Underflow detected in i64 addition"); // LCOV_EXCL_LINE + if constexpr (std::is_same_v) + { + throw std::overflow_error("Overflow detected in i8 addition"); // LCOV_EXCL_LINE + } + else if constexpr (std::is_same_v) + { + throw std::overflow_error("Overflow detected in i16 addition"); // LCOV_EXCL_LINE + } + else if constexpr (std::is_same_v) + { + throw std::overflow_error("Overflow detected in i32 addition"); // LCOV_EXCL_LINE + } + else if constexpr (std::is_same_v) + { + throw std::overflow_error("Overflow detected in i64 addition"); // LCOV_EXCL_LINE + } + else + { + throw std::overflow_error("Overflow detected in i128 addition"); // LCOV_EXCL_LINE + } } - else + else if (status == impl::signed_overflow_status::underflow) { - throw std::underflow_error("Underflow detected in i128 addition"); // LCOV_EXCL_LINE + if constexpr (std::is_same_v) + { + throw std::underflow_error("Underflow detected in i8 addition"); // LCOV_EXCL_LINE + } + else if constexpr (std::is_same_v) + { + throw std::underflow_error("Underflow detected in i16 addition"); // LCOV_EXCL_LINE + } + else if constexpr (std::is_same_v) + { + throw std::underflow_error("Underflow detected in i32 addition"); // LCOV_EXCL_LINE + } + else if constexpr (std::is_same_v) + { + throw std::underflow_error("Underflow detected in i64 addition"); // LCOV_EXCL_LINE + } + else + { + throw std::underflow_error("Underflow detected in i128 addition"); // LCOV_EXCL_LINE + } } - } - return signed_integer_basis{res}; + return signed_integer_basis{res}; + } } #endif - return impl::signed_add_helper::apply(lhs, rhs); + return impl::signed_add_helper::apply(lhs, rhs); } } // namespace boost::safe_numbers::detail #define BOOST_SAFE_NUMBERS_DEFINE_MIXED_SIGNED_INTEGER_OP(OP_NAME, OP_SYMBOL) \ -template \ - requires (!std::is_same_v) \ +template \ + requires (!std::is_same_v || boost::safe_numbers::detail::policies_differ()) \ BOOST_SAFE_NUMBERS_HOST_DEVICE \ -constexpr auto OP_SYMBOL(const boost::safe_numbers::detail::signed_integer_basis, \ - const boost::safe_numbers::detail::signed_integer_basis) \ +constexpr auto OP_SYMBOL(const boost::safe_numbers::detail::signed_integer_basis, \ + const boost::safe_numbers::detail::signed_integer_basis) \ { \ - if constexpr (std::is_same_v) \ + if constexpr (std::is_same_v) \ + { \ + static_assert(boost::safe_numbers::detail::dependent_false, \ + "Can not perform " OP_NAME " between same width types with different overflow policies " \ + "(e.g. i8 and sat_i8): convert explicitly through basis_type first"); \ + } \ + else if constexpr (std::is_same_v) \ { \ if constexpr (std::is_same_v) \ { \ @@ -919,7 +988,7 @@ constexpr auto OP_SYMBOL(const boost::safe_numbers::detail::signed_integer_basis static_assert(boost::safe_numbers::detail::dependent_false, "Can not perform " OP_NAME " on mixed width signed integer types"); \ } \ \ - return boost::safe_numbers::detail::signed_integer_basis(0); \ + return boost::safe_numbers::detail::signed_integer_basis(0); \ } @@ -930,11 +999,11 @@ BOOST_SAFE_NUMBERS_DEFINE_MIXED_SIGNED_INTEGER_OP("equality", operator==) BOOST_SAFE_NUMBERS_DEFINE_MIXED_SIGNED_INTEGER_OP("addition", operator+) -template -template +template +template BOOST_SAFE_NUMBERS_HOST_DEVICE -constexpr auto signed_integer_basis::operator+=(const signed_integer_basis rhs) - -> signed_integer_basis& +constexpr auto signed_integer_basis::operator+=(const signed_integer_basis rhs) + noexcept(policy_is_nothrow_arith()) -> signed_integer_basis& { *this = *this + rhs; return *this; @@ -1090,16 +1159,17 @@ BOOST_SAFE_NUMBERS_HOST_DEVICE constexpr auto signed_no_intrin_sub(const T lhs, return signed_overflow_status::no_error; } -template +template struct signed_sub_helper { + template BOOST_SAFE_NUMBERS_HOST_DEVICE - [[nodiscard]] static constexpr auto apply(const signed_integer_basis lhs, - const signed_integer_basis rhs) - noexcept(Policy != overflow_policy::throw_exception) - -> signed_integer_basis + [[nodiscard]] static constexpr auto apply(const signed_integer_basis lhs, + const signed_integer_basis rhs) + noexcept(policy_is_nothrow_arith()) + -> signed_integer_basis { - using result_type = signed_integer_basis; + using result_type = signed_integer_basis; const auto lhs_basis {static_cast(lhs)}; const auto rhs_basis {static_cast(rhs)}; @@ -1108,7 +1178,9 @@ struct signed_sub_helper auto handle_error = [&result](signed_overflow_status status) { #if !defined(BOOST_SAFE_NUMBERS_HAS_GPU_SUPPORT) - if (std::is_constant_evaluated()) + // Saturation produces a defined value, so it must keep evaluating at + // constant evaluation time instead of failing the build with a throw + if (std::is_constant_evaluated() && !policy_equals(overflow_policy::saturate) && !is_user_handler_v) { if (status == signed_overflow_status::overflow) { @@ -1160,7 +1232,7 @@ struct signed_sub_helper else #endif { - if constexpr (Policy == overflow_policy::throw_exception) + if constexpr (policy_equals(overflow_policy::throw_exception)) { static_cast(result); @@ -1173,7 +1245,7 @@ struct signed_sub_helper BOOST_SAFE_NUMBERS_THROW_EXCEPTION(std::underflow_error, signed_underflow_sub_msg()); } } - else if constexpr (Policy == overflow_policy::saturate) + else if constexpr (policy_equals(overflow_policy::saturate)) { if (status == signed_overflow_status::overflow) { @@ -1184,11 +1256,22 @@ struct signed_sub_helper result = std::numeric_limits::min(); } } - else if constexpr (Policy == overflow_policy::strict) + else if constexpr (policy_equals(overflow_policy::strict)) { static_cast(result); std::exit(EXIT_FAILURE); } + else if constexpr (is_user_handler_v) + { + if (status == signed_overflow_status::overflow) + { + result = Policy.on_error(error_kind::overflow, result, signed_overflow_sub_msg()); + } + else + { + result = Policy.on_error(error_kind::underflow, result, signed_underflow_sub_msg()); + } + } else { static_cast(result); @@ -1211,7 +1294,7 @@ struct signed_sub_helper const auto status {impl::signed_intrin_sub(lhs_basis, rhs_basis, result)}; #ifdef BOOST_SAFE_NUMBERS_ENABLE_COMPILE_ASSERT - if constexpr (Policy == overflow_policy::throw_exception || Policy == overflow_policy::strict) + if constexpr (policy_equals(overflow_policy::throw_exception) || policy_equals(overflow_policy::strict)) { BOOST_SAFE_NUMBERS_COMPILE_ASSERT_CONST_P(status == signed_overflow_status::no_error, "signed subtraction overflow"); } @@ -1233,7 +1316,7 @@ struct signed_sub_helper const auto status {impl::signed_no_intrin_sub(lhs_basis, rhs_basis, result)}; #ifdef BOOST_SAFE_NUMBERS_ENABLE_COMPILE_ASSERT - if constexpr (Policy == overflow_policy::throw_exception || Policy == overflow_policy::strict) + if constexpr (policy_equals(overflow_policy::throw_exception) || policy_equals(overflow_policy::strict)) { BOOST_SAFE_NUMBERS_COMPILE_ASSERT_CONST_P(status == signed_overflow_status::no_error, "signed subtraction overflow"); } @@ -1252,12 +1335,13 @@ struct signed_sub_helper template struct signed_sub_helper { + template BOOST_SAFE_NUMBERS_HOST_DEVICE - [[nodiscard]] static constexpr auto apply(const signed_integer_basis lhs, - const signed_integer_basis rhs) noexcept - -> std::pair, bool> + [[nodiscard]] static constexpr auto apply(const signed_integer_basis lhs, + const signed_integer_basis rhs) noexcept + -> std::pair, bool> { - using result_type = signed_integer_basis; + using result_type = signed_integer_basis; const auto lhs_basis {static_cast(lhs)}; const auto rhs_basis {static_cast(rhs)}; @@ -1275,12 +1359,13 @@ struct signed_sub_helper template struct signed_sub_helper { + template BOOST_SAFE_NUMBERS_HOST_DEVICE - [[nodiscard]] static constexpr auto apply(const signed_integer_basis lhs, - const signed_integer_basis rhs) noexcept - -> std::optional> + [[nodiscard]] static constexpr auto apply(const signed_integer_basis lhs, + const signed_integer_basis rhs) noexcept + -> std::optional> { - using result_type = signed_integer_basis; + using result_type = signed_integer_basis; const auto lhs_basis {static_cast(lhs)}; const auto rhs_basis {static_cast(rhs)}; @@ -1296,91 +1381,94 @@ struct signed_sub_helper } }; -template +template BOOST_SAFE_NUMBERS_HOST_DEVICE -[[nodiscard]] constexpr auto sub_impl(const signed_integer_basis lhs, - const signed_integer_basis rhs) - noexcept(Policy == overflow_policy::saturate || Policy == overflow_policy::overflow_tuple || - Policy == overflow_policy::checked || Policy == overflow_policy::strict) +[[nodiscard]] constexpr auto sub_impl(const signed_integer_basis lhs, + const signed_integer_basis rhs) + noexcept(policy_is_nothrow_arith()) { return signed_sub_helper::apply(lhs, rhs); } } // namespace impl -template +template BOOST_SAFE_NUMBERS_HOST_DEVICE -[[nodiscard]] constexpr auto operator-(const signed_integer_basis lhs, - const signed_integer_basis rhs) -> signed_integer_basis +[[nodiscard]] constexpr auto operator-(const signed_integer_basis lhs, + const signed_integer_basis rhs) + noexcept(policy_is_nothrow_arith()) -> signed_integer_basis { #if !defined(BOOST_SAFE_NUMBERS_HAS_GPU_SUPPORT) - if (std::is_constant_evaluated()) + if constexpr (policy_equals(overflow_policy::throw_exception)) { - BasisType res {}; - const auto status {impl::signed_no_intrin_sub(static_cast(lhs), static_cast(rhs), res)}; - if (status == impl::signed_overflow_status::overflow) - { - if constexpr (std::is_same_v) - { - throw std::overflow_error("Overflow detected in i8 subtraction"); // LCOV_EXCL_LINE - } - else if constexpr (std::is_same_v) - { - throw std::overflow_error("Overflow detected in i16 subtraction"); // LCOV_EXCL_LINE - } - else if constexpr (std::is_same_v) - { - throw std::overflow_error("Overflow detected in i32 subtraction"); // LCOV_EXCL_LINE - } - else if constexpr (std::is_same_v) - { - throw std::overflow_error("Overflow detected in i64 subtraction"); // LCOV_EXCL_LINE - } - else - { - throw std::overflow_error("Overflow detected in i128 subtraction"); // LCOV_EXCL_LINE - } - } - else if (status == impl::signed_overflow_status::underflow) + if (std::is_constant_evaluated()) { - if constexpr (std::is_same_v) - { - throw std::underflow_error("Underflow detected in i8 subtraction"); // LCOV_EXCL_LINE - } - else if constexpr (std::is_same_v) - { - throw std::underflow_error("Underflow detected in i16 subtraction"); // LCOV_EXCL_LINE - } - else if constexpr (std::is_same_v) - { - throw std::underflow_error("Underflow detected in i32 subtraction"); // LCOV_EXCL_LINE - } - else if constexpr (std::is_same_v) + BasisType res {}; + const auto status {impl::signed_no_intrin_sub(static_cast(lhs), static_cast(rhs), res)}; + if (status == impl::signed_overflow_status::overflow) { - throw std::underflow_error("Underflow detected in i64 subtraction"); // LCOV_EXCL_LINE + if constexpr (std::is_same_v) + { + throw std::overflow_error("Overflow detected in i8 subtraction"); // LCOV_EXCL_LINE + } + else if constexpr (std::is_same_v) + { + throw std::overflow_error("Overflow detected in i16 subtraction"); // LCOV_EXCL_LINE + } + else if constexpr (std::is_same_v) + { + throw std::overflow_error("Overflow detected in i32 subtraction"); // LCOV_EXCL_LINE + } + else if constexpr (std::is_same_v) + { + throw std::overflow_error("Overflow detected in i64 subtraction"); // LCOV_EXCL_LINE + } + else + { + throw std::overflow_error("Overflow detected in i128 subtraction"); // LCOV_EXCL_LINE + } } - else + else if (status == impl::signed_overflow_status::underflow) { - throw std::underflow_error("Underflow detected in i128 subtraction"); // LCOV_EXCL_LINE + if constexpr (std::is_same_v) + { + throw std::underflow_error("Underflow detected in i8 subtraction"); // LCOV_EXCL_LINE + } + else if constexpr (std::is_same_v) + { + throw std::underflow_error("Underflow detected in i16 subtraction"); // LCOV_EXCL_LINE + } + else if constexpr (std::is_same_v) + { + throw std::underflow_error("Underflow detected in i32 subtraction"); // LCOV_EXCL_LINE + } + else if constexpr (std::is_same_v) + { + throw std::underflow_error("Underflow detected in i64 subtraction"); // LCOV_EXCL_LINE + } + else + { + throw std::underflow_error("Underflow detected in i128 subtraction"); // LCOV_EXCL_LINE + } } - } - return signed_integer_basis{res}; + return signed_integer_basis{res}; + } } #endif - return impl::signed_sub_helper::apply(lhs, rhs); + return impl::signed_sub_helper::apply(lhs, rhs); } BOOST_SAFE_NUMBERS_DEFINE_MIXED_SIGNED_INTEGER_OP("subtraction", operator-) -template -template +template +template BOOST_SAFE_NUMBERS_HOST_DEVICE -constexpr auto signed_integer_basis::operator-=(const signed_integer_basis rhs) - -> signed_integer_basis& +constexpr auto signed_integer_basis::operator-=(const signed_integer_basis rhs) + noexcept(policy_is_nothrow_arith()) -> signed_integer_basis& { *this = *this - rhs; return *this; @@ -1660,16 +1748,17 @@ BOOST_SAFE_NUMBERS_HOST_DEVICE constexpr auto signed_no_intrin_mul(const T lhs, } } -template +template struct signed_mul_helper { + template BOOST_SAFE_NUMBERS_HOST_DEVICE - [[nodiscard]] static constexpr auto apply(const signed_integer_basis lhs, - const signed_integer_basis rhs) - noexcept(Policy != overflow_policy::throw_exception) - -> signed_integer_basis + [[nodiscard]] static constexpr auto apply(const signed_integer_basis lhs, + const signed_integer_basis rhs) + noexcept(policy_is_nothrow_arith()) + -> signed_integer_basis { - using result_type = signed_integer_basis; + using result_type = signed_integer_basis; const auto lhs_basis {static_cast(lhs)}; const auto rhs_basis {static_cast(rhs)}; @@ -1678,7 +1767,9 @@ struct signed_mul_helper auto handle_error = [&result](signed_overflow_status status) { #if !defined(BOOST_SAFE_NUMBERS_HAS_GPU_SUPPORT) - if (std::is_constant_evaluated()) + // Saturation produces a defined value, so it must keep evaluating at + // constant evaluation time instead of failing the build with a throw + if (std::is_constant_evaluated() && !policy_equals(overflow_policy::saturate) && !is_user_handler_v) { if (status == signed_overflow_status::overflow) { @@ -1730,7 +1821,7 @@ struct signed_mul_helper else #endif { - if constexpr (Policy == overflow_policy::throw_exception) + if constexpr (policy_equals(overflow_policy::throw_exception)) { static_cast(result); @@ -1743,7 +1834,7 @@ struct signed_mul_helper BOOST_SAFE_NUMBERS_THROW_EXCEPTION(std::underflow_error, signed_underflow_mul_msg()); } } - else if constexpr (Policy == overflow_policy::saturate) + else if constexpr (policy_equals(overflow_policy::saturate)) { if (status == signed_overflow_status::overflow) { @@ -1754,11 +1845,22 @@ struct signed_mul_helper result = std::numeric_limits::min(); } } - else if constexpr (Policy == overflow_policy::strict) + else if constexpr (policy_equals(overflow_policy::strict)) { static_cast(result); std::exit(EXIT_FAILURE); } + else if constexpr (is_user_handler_v) + { + if (status == signed_overflow_status::overflow) + { + result = Policy.on_error(error_kind::overflow, result, signed_overflow_mul_msg()); + } + else + { + result = Policy.on_error(error_kind::underflow, result, signed_underflow_mul_msg()); + } + } else { static_cast(result); @@ -1785,7 +1887,7 @@ struct signed_mul_helper const auto status {impl::signed_intrin_mul(lhs_basis, rhs_basis, result)}; #ifdef BOOST_SAFE_NUMBERS_ENABLE_COMPILE_ASSERT - if constexpr (Policy == overflow_policy::throw_exception || Policy == overflow_policy::strict) + if constexpr (policy_equals(overflow_policy::throw_exception) || policy_equals(overflow_policy::strict)) { BOOST_SAFE_NUMBERS_COMPILE_ASSERT_CONST_P(status == signed_overflow_status::no_error, "signed multiplication overflow"); } @@ -1807,7 +1909,7 @@ struct signed_mul_helper const auto status {impl::signed_no_intrin_mul(lhs_basis, rhs_basis, result)}; #ifdef BOOST_SAFE_NUMBERS_ENABLE_COMPILE_ASSERT - if constexpr (Policy == overflow_policy::throw_exception || Policy == overflow_policy::strict) + if constexpr (policy_equals(overflow_policy::throw_exception) || policy_equals(overflow_policy::strict)) { BOOST_SAFE_NUMBERS_COMPILE_ASSERT_CONST_P(status == signed_overflow_status::no_error, "signed multiplication overflow"); } @@ -1826,12 +1928,13 @@ struct signed_mul_helper template struct signed_mul_helper { + template BOOST_SAFE_NUMBERS_HOST_DEVICE - [[nodiscard]] static constexpr auto apply(const signed_integer_basis lhs, - const signed_integer_basis rhs) noexcept - -> std::pair, bool> + [[nodiscard]] static constexpr auto apply(const signed_integer_basis lhs, + const signed_integer_basis rhs) noexcept + -> std::pair, bool> { - using result_type = signed_integer_basis; + using result_type = signed_integer_basis; const auto lhs_basis {static_cast(lhs)}; const auto rhs_basis {static_cast(rhs)}; @@ -1849,12 +1952,13 @@ struct signed_mul_helper template struct signed_mul_helper { + template BOOST_SAFE_NUMBERS_HOST_DEVICE - [[nodiscard]] static constexpr auto apply(const signed_integer_basis lhs, - const signed_integer_basis rhs) noexcept - -> std::optional> + [[nodiscard]] static constexpr auto apply(const signed_integer_basis lhs, + const signed_integer_basis rhs) noexcept + -> std::optional> { - using result_type = signed_integer_basis; + using result_type = signed_integer_basis; const auto lhs_basis {static_cast(lhs)}; const auto rhs_basis {static_cast(rhs)}; @@ -1874,104 +1978,107 @@ struct signed_mul_helper template struct signed_mul_helper { + template BOOST_SAFE_NUMBERS_HOST_DEVICE - [[nodiscard]] static constexpr auto apply(const signed_integer_basis lhs, - const signed_integer_basis rhs) noexcept + [[nodiscard]] static constexpr auto apply(const signed_integer_basis lhs, + const signed_integer_basis rhs) noexcept { using promoted = signed_promoted_type; static_assert(!std::is_same_v, "Widening policy with int128_t is not supported"); - using result_type = signed_integer_basis; + using result_type = signed_integer_basis; return result_type{static_cast(static_cast(static_cast(lhs)) * static_cast(rhs))}; } }; -template +template BOOST_SAFE_NUMBERS_HOST_DEVICE -[[nodiscard]] constexpr auto mul_impl(const signed_integer_basis lhs, - const signed_integer_basis rhs) - noexcept(Policy == overflow_policy::saturate || Policy == overflow_policy::overflow_tuple || - Policy == overflow_policy::checked || Policy == overflow_policy::strict || - Policy == overflow_policy::widen) +[[nodiscard]] constexpr auto mul_impl(const signed_integer_basis lhs, + const signed_integer_basis rhs) + noexcept(policy_is_nothrow_arith()) { return signed_mul_helper::apply(lhs, rhs); } } // namespace impl -template +template BOOST_SAFE_NUMBERS_HOST_DEVICE -[[nodiscard]] constexpr auto operator*(const signed_integer_basis lhs, - const signed_integer_basis rhs) -> signed_integer_basis +[[nodiscard]] constexpr auto operator*(const signed_integer_basis lhs, + const signed_integer_basis rhs) + noexcept(policy_is_nothrow_arith()) -> signed_integer_basis { #if !defined(BOOST_SAFE_NUMBERS_HAS_GPU_SUPPORT) - if (std::is_constant_evaluated()) + if constexpr (policy_equals(overflow_policy::throw_exception)) { - BasisType res {}; - const auto status {impl::signed_no_intrin_mul(static_cast(lhs), static_cast(rhs), res)}; - if (status == impl::signed_overflow_status::overflow) + if (std::is_constant_evaluated()) { - if constexpr (std::is_same_v) - { - throw std::overflow_error("Overflow detected in i8 multiplication"); // LCOV_EXCL_LINE - } - else if constexpr (std::is_same_v) + BasisType res {}; + const auto status {impl::signed_no_intrin_mul(static_cast(lhs), static_cast(rhs), res)}; + if (status == impl::signed_overflow_status::overflow) { - throw std::overflow_error("Overflow detected in i16 multiplication"); // LCOV_EXCL_LINE - } - else if constexpr (std::is_same_v) - { - throw std::overflow_error("Overflow detected in i32 multiplication"); // LCOV_EXCL_LINE - } - else if constexpr (std::is_same_v) - { - throw std::overflow_error("Overflow detected in i64 multiplication"); // LCOV_EXCL_LINE - } - else - { - throw std::overflow_error("Overflow detected in i128 multiplication"); // LCOV_EXCL_LINE - } - } - else if (status == impl::signed_overflow_status::underflow) - { - if constexpr (std::is_same_v) - { - throw std::underflow_error("Underflow detected in i8 multiplication"); // LCOV_EXCL_LINE - } - else if constexpr (std::is_same_v) - { - throw std::underflow_error("Underflow detected in i16 multiplication"); // LCOV_EXCL_LINE - } - else if constexpr (std::is_same_v) - { - throw std::underflow_error("Underflow detected in i32 multiplication"); // LCOV_EXCL_LINE - } - else if constexpr (std::is_same_v) - { - throw std::underflow_error("Underflow detected in i64 multiplication"); // LCOV_EXCL_LINE + if constexpr (std::is_same_v) + { + throw std::overflow_error("Overflow detected in i8 multiplication"); // LCOV_EXCL_LINE + } + else if constexpr (std::is_same_v) + { + throw std::overflow_error("Overflow detected in i16 multiplication"); // LCOV_EXCL_LINE + } + else if constexpr (std::is_same_v) + { + throw std::overflow_error("Overflow detected in i32 multiplication"); // LCOV_EXCL_LINE + } + else if constexpr (std::is_same_v) + { + throw std::overflow_error("Overflow detected in i64 multiplication"); // LCOV_EXCL_LINE + } + else + { + throw std::overflow_error("Overflow detected in i128 multiplication"); // LCOV_EXCL_LINE + } } - else + else if (status == impl::signed_overflow_status::underflow) { - throw std::underflow_error("Underflow detected in i128 multiplication"); // LCOV_EXCL_LINE + if constexpr (std::is_same_v) + { + throw std::underflow_error("Underflow detected in i8 multiplication"); // LCOV_EXCL_LINE + } + else if constexpr (std::is_same_v) + { + throw std::underflow_error("Underflow detected in i16 multiplication"); // LCOV_EXCL_LINE + } + else if constexpr (std::is_same_v) + { + throw std::underflow_error("Underflow detected in i32 multiplication"); // LCOV_EXCL_LINE + } + else if constexpr (std::is_same_v) + { + throw std::underflow_error("Underflow detected in i64 multiplication"); // LCOV_EXCL_LINE + } + else + { + throw std::underflow_error("Underflow detected in i128 multiplication"); // LCOV_EXCL_LINE + } } - } - return signed_integer_basis{res}; + return signed_integer_basis{res}; + } } #endif - return impl::signed_mul_helper::apply(lhs, rhs); + return impl::signed_mul_helper::apply(lhs, rhs); } BOOST_SAFE_NUMBERS_DEFINE_MIXED_SIGNED_INTEGER_OP("multiplication", operator*) -template -template +template +template BOOST_SAFE_NUMBERS_HOST_DEVICE -constexpr auto signed_integer_basis::operator*=(const signed_integer_basis rhs) - -> signed_integer_basis& +constexpr auto signed_integer_basis::operator*=(const signed_integer_basis rhs) + noexcept(policy_is_nothrow_arith()) -> signed_integer_basis& { *this = *this * rhs; return *this; @@ -2033,16 +2140,17 @@ BOOST_SAFE_NUMBERS_HOST_DEVICE constexpr auto signed_overflow_div_msg() noexcept } } -template +template struct signed_div_helper { + template BOOST_SAFE_NUMBERS_HOST_DEVICE - [[nodiscard]] static constexpr auto apply(const signed_integer_basis lhs, - const signed_integer_basis rhs) - noexcept(Policy == overflow_policy::strict) - -> signed_integer_basis + [[nodiscard]] static constexpr auto apply(const signed_integer_basis lhs, + const signed_integer_basis rhs) + noexcept(policy_is_nothrow_div()) + -> signed_integer_basis { - using result_type = signed_integer_basis; + using result_type = signed_integer_basis; const auto lhs_basis {static_cast(lhs)}; const auto rhs_basis {static_cast(rhs)}; @@ -2053,7 +2161,7 @@ struct signed_div_helper BOOST_SAFE_NUMBERS_COMPILE_ASSERT_CONST_P(rhs_basis != BasisType{0}, "signed division by zero", rhs_basis); // min / -1 overflow is an error under throw_exception and strict; saturate returns max. - if constexpr (Policy == overflow_policy::throw_exception || Policy == overflow_policy::strict) + if constexpr (policy_equals(overflow_policy::throw_exception) || policy_equals(overflow_policy::strict)) { BOOST_SAFE_NUMBERS_COMPILE_ASSERT_CONST_P(!(lhs_basis == std::numeric_limits::min() && rhs_basis == static_cast(-1)), "signed division overflow (min / -1)"); } @@ -2061,10 +2169,14 @@ struct signed_div_helper if (rhs_basis == BasisType{0}) [[unlikely]] { - if constexpr (Policy == overflow_policy::strict) + if constexpr (policy_equals(overflow_policy::strict)) { std::exit(EXIT_FAILURE); } + else if constexpr (is_user_handler_v) + { + return result_type{Policy.on_error(error_kind::divide_by_zero, lhs_basis, signed_div_by_zero_msg())}; + } else { #if !defined(BOOST_SAFE_NUMBERS_HAS_GPU_SUPPORT) @@ -2109,14 +2221,18 @@ struct signed_div_helper if (lhs_basis == std::numeric_limits::min() && rhs_basis == static_cast(-1)) [[unlikely]] { - if constexpr (Policy == overflow_policy::strict) + if constexpr (policy_equals(overflow_policy::strict)) { std::exit(EXIT_FAILURE); } - else if constexpr (Policy == overflow_policy::saturate) + else if constexpr (policy_equals(overflow_policy::saturate)) { return result_type{std::numeric_limits::max()}; } + else if constexpr (is_user_handler_v) + { + return result_type{Policy.on_error(error_kind::overflow, std::numeric_limits::min(), signed_overflow_div_msg())}; + } else { #if !defined(BOOST_SAFE_NUMBERS_HAS_GPU_SUPPORT) @@ -2159,12 +2275,13 @@ struct signed_div_helper template struct signed_div_helper { + template BOOST_SAFE_NUMBERS_HOST_DEVICE - [[nodiscard]] static constexpr auto apply(const signed_integer_basis lhs, - const signed_integer_basis rhs) - -> std::pair, bool> + [[nodiscard]] static constexpr auto apply(const signed_integer_basis lhs, + const signed_integer_basis rhs) + -> std::pair, bool> { - using result_type = signed_integer_basis; + using result_type = signed_integer_basis; const auto lhs_basis {static_cast(lhs)}; const auto rhs_basis {static_cast(rhs)}; @@ -2199,12 +2316,13 @@ struct signed_div_helper template struct signed_div_helper { + template BOOST_SAFE_NUMBERS_HOST_DEVICE - [[nodiscard]] static constexpr auto apply(const signed_integer_basis lhs, - const signed_integer_basis rhs) noexcept - -> std::optional> + [[nodiscard]] static constexpr auto apply(const signed_integer_basis lhs, + const signed_integer_basis rhs) noexcept + -> std::optional> { - using result_type = signed_integer_basis; + using result_type = signed_integer_basis; const auto lhs_basis {static_cast(lhs)}; const auto rhs_basis {static_cast(rhs)}; @@ -2229,32 +2347,33 @@ struct signed_div_helper } }; -template +template BOOST_SAFE_NUMBERS_HOST_DEVICE -[[nodiscard]] constexpr auto div_impl(const signed_integer_basis lhs, - const signed_integer_basis rhs) - noexcept(Policy == overflow_policy::checked || Policy == overflow_policy::strict) +[[nodiscard]] constexpr auto div_impl(const signed_integer_basis lhs, + const signed_integer_basis rhs) + noexcept(policy_equals(overflow_policy::checked) || policy_is_nothrow_div()) { return signed_div_helper::apply(lhs, rhs); } } // namespace impl -template +template BOOST_SAFE_NUMBERS_HOST_DEVICE -[[nodiscard]] constexpr auto operator/(const signed_integer_basis lhs, - const signed_integer_basis rhs) -> signed_integer_basis +[[nodiscard]] constexpr auto operator/(const signed_integer_basis lhs, + const signed_integer_basis rhs) + noexcept(policy_is_nothrow_div()) -> signed_integer_basis { - return impl::signed_div_helper::apply(lhs, rhs); + return impl::signed_div_helper::apply(lhs, rhs); } BOOST_SAFE_NUMBERS_DEFINE_MIXED_SIGNED_INTEGER_OP("division", operator/) -template -template +template +template BOOST_SAFE_NUMBERS_HOST_DEVICE -constexpr auto signed_integer_basis::operator/=(const signed_integer_basis rhs) - -> signed_integer_basis& +constexpr auto signed_integer_basis::operator/=(const signed_integer_basis rhs) + noexcept(policy_is_nothrow_div()) -> signed_integer_basis& { *this = *this / rhs; return *this; @@ -2316,16 +2435,17 @@ BOOST_SAFE_NUMBERS_HOST_DEVICE constexpr auto signed_overflow_mod_msg() noexcept } } -template +template struct signed_mod_helper { + template BOOST_SAFE_NUMBERS_HOST_DEVICE - [[nodiscard]] static constexpr auto apply(const signed_integer_basis lhs, - const signed_integer_basis rhs) - noexcept(Policy == overflow_policy::strict) - -> signed_integer_basis + [[nodiscard]] static constexpr auto apply(const signed_integer_basis lhs, + const signed_integer_basis rhs) + noexcept(policy_is_nothrow_div()) + -> signed_integer_basis { - using result_type = signed_integer_basis; + using result_type = signed_integer_basis; const auto lhs_basis {static_cast(lhs)}; const auto rhs_basis {static_cast(rhs)}; @@ -2336,7 +2456,7 @@ struct signed_mod_helper BOOST_SAFE_NUMBERS_COMPILE_ASSERT_CONST_P(rhs_basis != BasisType{0}, "signed modulo by zero"); // min % -1 overflow is an error under throw_exception and strict; saturate returns 0. - if constexpr (Policy == overflow_policy::throw_exception || Policy == overflow_policy::strict) + if constexpr (policy_equals(overflow_policy::throw_exception) || policy_equals(overflow_policy::strict)) { BOOST_SAFE_NUMBERS_COMPILE_ASSERT_CONST_P(!(lhs_basis == std::numeric_limits::min() && rhs_basis == static_cast(-1)), "signed modulo overflow (min % -1)"); } @@ -2344,10 +2464,14 @@ struct signed_mod_helper if (rhs_basis == BasisType{0}) [[unlikely]] { - if constexpr (Policy == overflow_policy::strict) + if constexpr (policy_equals(overflow_policy::strict)) { std::exit(EXIT_FAILURE); } + else if constexpr (is_user_handler_v) + { + return result_type{Policy.on_error(error_kind::divide_by_zero, lhs_basis, signed_mod_by_zero_msg())}; + } else { #if !defined(BOOST_SAFE_NUMBERS_HAS_GPU_SUPPORT) @@ -2394,15 +2518,20 @@ struct signed_mod_helper if (lhs_basis == std::numeric_limits::min() && rhs_basis == static_cast(-1)) [[unlikely]] { - if constexpr (Policy == overflow_policy::strict) + if constexpr (policy_equals(overflow_policy::strict)) { std::exit(EXIT_FAILURE); } - else if constexpr (Policy == overflow_policy::saturate) + else if constexpr (policy_equals(overflow_policy::saturate)) { // The mathematical result of min % -1 is 0 return result_type{BasisType{0}}; } + else if constexpr (is_user_handler_v) + { + // The mathematical result of min % -1 is 0 + return result_type{Policy.on_error(error_kind::overflow, BasisType{0}, signed_overflow_mod_msg())}; + } else { #if !defined(BOOST_SAFE_NUMBERS_HAS_GPU_SUPPORT) @@ -2445,12 +2574,13 @@ struct signed_mod_helper template struct signed_mod_helper { + template BOOST_SAFE_NUMBERS_HOST_DEVICE - [[nodiscard]] static constexpr auto apply(const signed_integer_basis lhs, - const signed_integer_basis rhs) - -> std::pair, bool> + [[nodiscard]] static constexpr auto apply(const signed_integer_basis lhs, + const signed_integer_basis rhs) + -> std::pair, bool> { - using result_type = signed_integer_basis; + using result_type = signed_integer_basis; const auto lhs_basis {static_cast(lhs)}; const auto rhs_basis {static_cast(rhs)}; @@ -2485,12 +2615,13 @@ struct signed_mod_helper template struct signed_mod_helper { + template BOOST_SAFE_NUMBERS_HOST_DEVICE - [[nodiscard]] static constexpr auto apply(const signed_integer_basis lhs, - const signed_integer_basis rhs) noexcept - -> std::optional> + [[nodiscard]] static constexpr auto apply(const signed_integer_basis lhs, + const signed_integer_basis rhs) noexcept + -> std::optional> { - using result_type = signed_integer_basis; + using result_type = signed_integer_basis; const auto lhs_basis {static_cast(lhs)}; const auto rhs_basis {static_cast(rhs)}; @@ -2515,32 +2646,33 @@ struct signed_mod_helper } }; -template +template BOOST_SAFE_NUMBERS_HOST_DEVICE -[[nodiscard]] constexpr auto mod_impl(const signed_integer_basis lhs, - const signed_integer_basis rhs) - noexcept(Policy == overflow_policy::checked || Policy == overflow_policy::strict) +[[nodiscard]] constexpr auto mod_impl(const signed_integer_basis lhs, + const signed_integer_basis rhs) + noexcept(policy_equals(overflow_policy::checked) || policy_is_nothrow_div()) { return signed_mod_helper::apply(lhs, rhs); } } // namespace impl -template +template BOOST_SAFE_NUMBERS_HOST_DEVICE -[[nodiscard]] constexpr auto operator%(const signed_integer_basis lhs, - const signed_integer_basis rhs) -> signed_integer_basis +[[nodiscard]] constexpr auto operator%(const signed_integer_basis lhs, + const signed_integer_basis rhs) + noexcept(policy_is_nothrow_div()) -> signed_integer_basis { - return impl::signed_mod_helper::apply(lhs, rhs); + return impl::signed_mod_helper::apply(lhs, rhs); } BOOST_SAFE_NUMBERS_DEFINE_MIXED_SIGNED_INTEGER_OP("modulo", operator%) -template -template +template +template BOOST_SAFE_NUMBERS_HOST_DEVICE -constexpr auto signed_integer_basis::operator%=(const signed_integer_basis rhs) - -> signed_integer_basis& +constexpr auto signed_integer_basis::operator%=(const signed_integer_basis rhs) + noexcept(policy_is_nothrow_div()) -> signed_integer_basis& { *this = *this % rhs; return *this; @@ -2604,28 +2736,61 @@ BOOST_SAFE_NUMBERS_HOST_DEVICE constexpr auto signed_underflow_dec_msg() noexcep // Pre and post increment // ------------------------------ -template +template BOOST_SAFE_NUMBERS_HOST_DEVICE -constexpr auto signed_integer_basis::operator++() - -> signed_integer_basis& +constexpr auto signed_integer_basis::operator++() + noexcept(policy_is_nothrow_arith()) -> signed_integer_basis& { if (this->basis_ == std::numeric_limits::max()) [[unlikely]] { - BOOST_SAFE_NUMBERS_THROW_EXCEPTION(std::overflow_error, signed_overflow_inc_msg()); + if constexpr (policy_equals(overflow_policy::throw_exception)) + { + BOOST_SAFE_NUMBERS_THROW_EXCEPTION(std::overflow_error, signed_overflow_inc_msg()); + } + else if constexpr (policy_equals(overflow_policy::saturate)) + { + return *this; + } + else if constexpr (policy_equals(overflow_policy::strict)) + { + std::exit(EXIT_FAILURE); + } + else + { + this->basis_ = ErrorPolicy.on_error(error_kind::overflow, std::numeric_limits::min(), signed_overflow_inc_msg()); + return *this; + } } ++this->basis_; return *this; } -template +template BOOST_SAFE_NUMBERS_HOST_DEVICE -constexpr auto signed_integer_basis::operator++(int) - -> signed_integer_basis +constexpr auto signed_integer_basis::operator++(int) + noexcept(policy_is_nothrow_arith()) -> signed_integer_basis { if (this->basis_ == std::numeric_limits::max()) [[unlikely]] { - BOOST_SAFE_NUMBERS_THROW_EXCEPTION(std::overflow_error, signed_overflow_inc_msg()); + if constexpr (policy_equals(overflow_policy::throw_exception)) + { + BOOST_SAFE_NUMBERS_THROW_EXCEPTION(std::overflow_error, signed_overflow_inc_msg()); + } + else if constexpr (policy_equals(overflow_policy::saturate)) + { + return *this; + } + else if constexpr (policy_equals(overflow_policy::strict)) + { + std::exit(EXIT_FAILURE); + } + else + { + const auto temp {*this}; + this->basis_ = ErrorPolicy.on_error(error_kind::overflow, std::numeric_limits::min(), signed_overflow_inc_msg()); + return temp; + } } const auto temp {*this}; @@ -2637,28 +2802,61 @@ constexpr auto signed_integer_basis::operator++(int) // Pre and post decrement // ------------------------------ -template +template BOOST_SAFE_NUMBERS_HOST_DEVICE -constexpr auto signed_integer_basis::operator--() - -> signed_integer_basis& +constexpr auto signed_integer_basis::operator--() + noexcept(policy_is_nothrow_arith()) -> signed_integer_basis& { if (this->basis_ == std::numeric_limits::min()) [[unlikely]] { - BOOST_SAFE_NUMBERS_THROW_EXCEPTION(std::underflow_error, signed_underflow_dec_msg()); + if constexpr (policy_equals(overflow_policy::throw_exception)) + { + BOOST_SAFE_NUMBERS_THROW_EXCEPTION(std::underflow_error, signed_underflow_dec_msg()); + } + else if constexpr (policy_equals(overflow_policy::saturate)) + { + return *this; + } + else if constexpr (policy_equals(overflow_policy::strict)) + { + std::exit(EXIT_FAILURE); + } + else + { + this->basis_ = ErrorPolicy.on_error(error_kind::underflow, std::numeric_limits::max(), signed_underflow_dec_msg()); + return *this; + } } --this->basis_; return *this; } -template +template BOOST_SAFE_NUMBERS_HOST_DEVICE -constexpr auto signed_integer_basis::operator--(int) - -> signed_integer_basis +constexpr auto signed_integer_basis::operator--(int) + noexcept(policy_is_nothrow_arith()) -> signed_integer_basis { if (this->basis_ == std::numeric_limits::min()) [[unlikely]] { - BOOST_SAFE_NUMBERS_THROW_EXCEPTION(std::underflow_error, signed_underflow_dec_msg()); + if constexpr (policy_equals(overflow_policy::throw_exception)) + { + BOOST_SAFE_NUMBERS_THROW_EXCEPTION(std::underflow_error, signed_underflow_dec_msg()); + } + else if constexpr (policy_equals(overflow_policy::saturate)) + { + return *this; + } + else if constexpr (policy_equals(overflow_policy::strict)) + { + std::exit(EXIT_FAILURE); + } + else + { + const auto temp {*this}; + this->basis_ = ErrorPolicy.on_error(error_kind::underflow, std::numeric_limits::max(), signed_underflow_dec_msg()); + return temp; + } } const auto temp {*this}; @@ -2674,55 +2872,55 @@ constexpr auto signed_integer_basis::operator--(int) namespace boost::safe_numbers { -template +template BOOST_SAFE_NUMBERS_HOST_DEVICE -[[nodiscard]] constexpr auto saturating_add(const detail::signed_integer_basis lhs, - const detail::signed_integer_basis rhs) noexcept - -> detail::signed_integer_basis +[[nodiscard]] constexpr auto saturating_add(const detail::signed_integer_basis lhs, + const detail::signed_integer_basis rhs) noexcept + -> detail::signed_integer_basis { return detail::impl::add_impl(lhs, rhs); } BOOST_SAFE_NUMBERS_DEFINE_MIXED_SIGNED_INTEGER_OP("saturating addition", saturating_add) -template +template BOOST_SAFE_NUMBERS_HOST_DEVICE -[[nodiscard]] constexpr auto saturating_sub(const detail::signed_integer_basis lhs, - const detail::signed_integer_basis rhs) noexcept - -> detail::signed_integer_basis +[[nodiscard]] constexpr auto saturating_sub(const detail::signed_integer_basis lhs, + const detail::signed_integer_basis rhs) noexcept + -> detail::signed_integer_basis { return detail::impl::sub_impl(lhs, rhs); } BOOST_SAFE_NUMBERS_DEFINE_MIXED_SIGNED_INTEGER_OP("saturating subtraction", saturating_sub) -template +template BOOST_SAFE_NUMBERS_HOST_DEVICE -[[nodiscard]] constexpr auto saturating_mul(const detail::signed_integer_basis lhs, - const detail::signed_integer_basis rhs) noexcept - -> detail::signed_integer_basis +[[nodiscard]] constexpr auto saturating_mul(const detail::signed_integer_basis lhs, + const detail::signed_integer_basis rhs) noexcept + -> detail::signed_integer_basis { return detail::impl::mul_impl(lhs, rhs); } BOOST_SAFE_NUMBERS_DEFINE_MIXED_SIGNED_INTEGER_OP("saturating multiplication", saturating_mul) -template +template BOOST_SAFE_NUMBERS_HOST_DEVICE -[[nodiscard]] constexpr auto saturating_div(const detail::signed_integer_basis lhs, - const detail::signed_integer_basis rhs) - -> detail::signed_integer_basis +[[nodiscard]] constexpr auto saturating_div(const detail::signed_integer_basis lhs, + const detail::signed_integer_basis rhs) + -> detail::signed_integer_basis { return detail::impl::div_impl(lhs, rhs); } BOOST_SAFE_NUMBERS_DEFINE_MIXED_SIGNED_INTEGER_OP("saturating division", saturating_div) -template +template BOOST_SAFE_NUMBERS_HOST_DEVICE -[[nodiscard]] constexpr auto saturating_mod(const detail::signed_integer_basis lhs, - const detail::signed_integer_basis rhs) - -> detail::signed_integer_basis +[[nodiscard]] constexpr auto saturating_mod(const detail::signed_integer_basis lhs, + const detail::signed_integer_basis rhs) + -> detail::signed_integer_basis { return detail::impl::mod_impl(lhs, rhs); } @@ -2733,55 +2931,55 @@ BOOST_SAFE_NUMBERS_DEFINE_MIXED_SIGNED_INTEGER_OP("saturating modulo", saturatin // Overflowing Math // ------------------------------ -template +template BOOST_SAFE_NUMBERS_HOST_DEVICE -[[nodiscard]] constexpr auto overflowing_add(const detail::signed_integer_basis lhs, - const detail::signed_integer_basis rhs) noexcept - -> std::pair, bool> +[[nodiscard]] constexpr auto overflowing_add(const detail::signed_integer_basis lhs, + const detail::signed_integer_basis rhs) noexcept + -> std::pair, bool> { return detail::impl::add_impl(lhs, rhs); } BOOST_SAFE_NUMBERS_DEFINE_MIXED_SIGNED_INTEGER_OP("overflowing addition", overflowing_add) -template +template BOOST_SAFE_NUMBERS_HOST_DEVICE -[[nodiscard]] constexpr auto overflowing_sub(const detail::signed_integer_basis lhs, - const detail::signed_integer_basis rhs) noexcept - -> std::pair, bool> +[[nodiscard]] constexpr auto overflowing_sub(const detail::signed_integer_basis lhs, + const detail::signed_integer_basis rhs) noexcept + -> std::pair, bool> { return detail::impl::sub_impl(lhs, rhs); } BOOST_SAFE_NUMBERS_DEFINE_MIXED_SIGNED_INTEGER_OP("overflowing subtraction", overflowing_sub) -template +template BOOST_SAFE_NUMBERS_HOST_DEVICE -[[nodiscard]] constexpr auto overflowing_mul(const detail::signed_integer_basis lhs, - const detail::signed_integer_basis rhs) noexcept - -> std::pair, bool> +[[nodiscard]] constexpr auto overflowing_mul(const detail::signed_integer_basis lhs, + const detail::signed_integer_basis rhs) noexcept + -> std::pair, bool> { return detail::impl::mul_impl(lhs, rhs); } BOOST_SAFE_NUMBERS_DEFINE_MIXED_SIGNED_INTEGER_OP("overflowing multiplication", overflowing_mul) -template +template BOOST_SAFE_NUMBERS_HOST_DEVICE -[[nodiscard]] constexpr auto overflowing_div(const detail::signed_integer_basis lhs, - const detail::signed_integer_basis rhs) - -> std::pair, bool> +[[nodiscard]] constexpr auto overflowing_div(const detail::signed_integer_basis lhs, + const detail::signed_integer_basis rhs) + -> std::pair, bool> { return detail::impl::div_impl(lhs, rhs); } BOOST_SAFE_NUMBERS_DEFINE_MIXED_SIGNED_INTEGER_OP("overflowing division", overflowing_div) -template +template BOOST_SAFE_NUMBERS_HOST_DEVICE -[[nodiscard]] constexpr auto overflowing_mod(const detail::signed_integer_basis lhs, - const detail::signed_integer_basis rhs) - -> std::pair, bool> +[[nodiscard]] constexpr auto overflowing_mod(const detail::signed_integer_basis lhs, + const detail::signed_integer_basis rhs) + -> std::pair, bool> { return detail::impl::mod_impl(lhs, rhs); } @@ -2792,55 +2990,55 @@ BOOST_SAFE_NUMBERS_DEFINE_MIXED_SIGNED_INTEGER_OP("overflowing modulo", overflow // Checked Math // ------------------------------ -template +template BOOST_SAFE_NUMBERS_HOST_DEVICE -[[nodiscard]] constexpr auto checked_add(const detail::signed_integer_basis lhs, - const detail::signed_integer_basis rhs) noexcept - -> std::optional> +[[nodiscard]] constexpr auto checked_add(const detail::signed_integer_basis lhs, + const detail::signed_integer_basis rhs) noexcept + -> std::optional> { return detail::impl::add_impl(lhs, rhs); } BOOST_SAFE_NUMBERS_DEFINE_MIXED_SIGNED_INTEGER_OP("checked addition", checked_add) -template +template BOOST_SAFE_NUMBERS_HOST_DEVICE -[[nodiscard]] constexpr auto checked_sub(const detail::signed_integer_basis lhs, - const detail::signed_integer_basis rhs) noexcept - -> std::optional> +[[nodiscard]] constexpr auto checked_sub(const detail::signed_integer_basis lhs, + const detail::signed_integer_basis rhs) noexcept + -> std::optional> { return detail::impl::sub_impl(lhs, rhs); } BOOST_SAFE_NUMBERS_DEFINE_MIXED_SIGNED_INTEGER_OP("checked subtraction", checked_sub) -template +template BOOST_SAFE_NUMBERS_HOST_DEVICE -[[nodiscard]] constexpr auto checked_mul(const detail::signed_integer_basis lhs, - const detail::signed_integer_basis rhs) noexcept - -> std::optional> +[[nodiscard]] constexpr auto checked_mul(const detail::signed_integer_basis lhs, + const detail::signed_integer_basis rhs) noexcept + -> std::optional> { return detail::impl::mul_impl(lhs, rhs); } BOOST_SAFE_NUMBERS_DEFINE_MIXED_SIGNED_INTEGER_OP("checked multiplication", checked_mul) -template +template BOOST_SAFE_NUMBERS_HOST_DEVICE -[[nodiscard]] constexpr auto checked_div(const detail::signed_integer_basis lhs, - const detail::signed_integer_basis rhs) noexcept - -> std::optional> +[[nodiscard]] constexpr auto checked_div(const detail::signed_integer_basis lhs, + const detail::signed_integer_basis rhs) noexcept + -> std::optional> { return detail::impl::div_impl(lhs, rhs); } BOOST_SAFE_NUMBERS_DEFINE_MIXED_SIGNED_INTEGER_OP("checked division", checked_div) -template +template BOOST_SAFE_NUMBERS_HOST_DEVICE -[[nodiscard]] constexpr auto checked_mod(const detail::signed_integer_basis lhs, - const detail::signed_integer_basis rhs) noexcept - -> std::optional> +[[nodiscard]] constexpr auto checked_mod(const detail::signed_integer_basis lhs, + const detail::signed_integer_basis rhs) noexcept + -> std::optional> { return detail::impl::mod_impl(lhs, rhs); } @@ -2851,50 +3049,50 @@ BOOST_SAFE_NUMBERS_DEFINE_MIXED_SIGNED_INTEGER_OP("checked modulo", checked_mod) // Strict Math // ------------------------------ -template -[[nodiscard]] constexpr auto strict_add(const detail::signed_integer_basis lhs, - const detail::signed_integer_basis rhs) noexcept - -> detail::signed_integer_basis +template +[[nodiscard]] constexpr auto strict_add(const detail::signed_integer_basis lhs, + const detail::signed_integer_basis rhs) noexcept + -> detail::signed_integer_basis { return detail::impl::add_impl(lhs, rhs); } BOOST_SAFE_NUMBERS_DEFINE_MIXED_SIGNED_INTEGER_OP("strict addition", strict_add) -template -[[nodiscard]] constexpr auto strict_sub(const detail::signed_integer_basis lhs, - const detail::signed_integer_basis rhs) noexcept - -> detail::signed_integer_basis +template +[[nodiscard]] constexpr auto strict_sub(const detail::signed_integer_basis lhs, + const detail::signed_integer_basis rhs) noexcept + -> detail::signed_integer_basis { return detail::impl::sub_impl(lhs, rhs); } BOOST_SAFE_NUMBERS_DEFINE_MIXED_SIGNED_INTEGER_OP("strict subtraction", strict_sub) -template -[[nodiscard]] constexpr auto strict_mul(const detail::signed_integer_basis lhs, - const detail::signed_integer_basis rhs) noexcept - -> detail::signed_integer_basis +template +[[nodiscard]] constexpr auto strict_mul(const detail::signed_integer_basis lhs, + const detail::signed_integer_basis rhs) noexcept + -> detail::signed_integer_basis { return detail::impl::mul_impl(lhs, rhs); } BOOST_SAFE_NUMBERS_DEFINE_MIXED_SIGNED_INTEGER_OP("strict multiplication", strict_mul) -template -[[nodiscard]] constexpr auto strict_div(const detail::signed_integer_basis lhs, - const detail::signed_integer_basis rhs) noexcept - -> detail::signed_integer_basis +template +[[nodiscard]] constexpr auto strict_div(const detail::signed_integer_basis lhs, + const detail::signed_integer_basis rhs) noexcept + -> detail::signed_integer_basis { return detail::impl::div_impl(lhs, rhs); } BOOST_SAFE_NUMBERS_DEFINE_MIXED_SIGNED_INTEGER_OP("strict division", strict_div) -template -[[nodiscard]] constexpr auto strict_mod(const detail::signed_integer_basis lhs, - const detail::signed_integer_basis rhs) noexcept - -> detail::signed_integer_basis +template +[[nodiscard]] constexpr auto strict_mod(const detail::signed_integer_basis lhs, + const detail::signed_integer_basis rhs) noexcept + -> detail::signed_integer_basis { return detail::impl::mod_impl(lhs, rhs); } @@ -2905,18 +3103,18 @@ BOOST_SAFE_NUMBERS_DEFINE_MIXED_SIGNED_INTEGER_OP("strict modulo", strict_mod) // Widening Math // ------------------------------ -template -[[nodiscard]] constexpr auto widening_add(const detail::signed_integer_basis lhs, - const detail::signed_integer_basis rhs) noexcept +template +[[nodiscard]] constexpr auto widening_add(const detail::signed_integer_basis lhs, + const detail::signed_integer_basis rhs) noexcept { return detail::impl::add_impl(lhs, rhs); } BOOST_SAFE_NUMBERS_DEFINE_MIXED_SIGNED_INTEGER_OP("widening add", widening_add) -template -[[nodiscard]] constexpr auto widening_mul(const detail::signed_integer_basis lhs, - const detail::signed_integer_basis rhs) noexcept +template +[[nodiscard]] constexpr auto widening_mul(const detail::signed_integer_basis lhs, + const detail::signed_integer_basis rhs) noexcept { return detail::impl::mul_impl(lhs, rhs); } @@ -2927,14 +3125,21 @@ BOOST_SAFE_NUMBERS_DEFINE_MIXED_SIGNED_INTEGER_OP("widening mul", widening_mul) // Generic policy-parameterized functions // ------------------------------ -template -[[nodiscard]] constexpr auto add(const detail::signed_integer_basis lhs, - const detail::signed_integer_basis rhs) +template +[[nodiscard]] constexpr auto add(const detail::signed_integer_basis lhs, + const detail::signed_integer_basis rhs) noexcept(Policy != overflow_policy::throw_exception) { if constexpr (Policy == overflow_policy::throw_exception) { - return lhs + rhs; + if constexpr (detail::policy_equals(overflow_policy::throw_exception)) + { + return lhs + rhs; + } + else + { + return detail::impl::add_impl(lhs, rhs); + } } else if constexpr (Policy == overflow_policy::saturate) { @@ -2962,14 +3167,21 @@ template } } -template -[[nodiscard]] constexpr auto sub(const detail::signed_integer_basis lhs, - const detail::signed_integer_basis rhs) +template +[[nodiscard]] constexpr auto sub(const detail::signed_integer_basis lhs, + const detail::signed_integer_basis rhs) noexcept(Policy != overflow_policy::throw_exception) { if constexpr (Policy == overflow_policy::throw_exception) { - return lhs - rhs; + if constexpr (detail::policy_equals(overflow_policy::throw_exception)) + { + return lhs - rhs; + } + else + { + return detail::impl::sub_impl(lhs, rhs); + } } else if constexpr (Policy == overflow_policy::saturate) { @@ -2993,14 +3205,21 @@ template } } -template -[[nodiscard]] constexpr auto mul(const detail::signed_integer_basis lhs, - const detail::signed_integer_basis rhs) +template +[[nodiscard]] constexpr auto mul(const detail::signed_integer_basis lhs, + const detail::signed_integer_basis rhs) noexcept(Policy != overflow_policy::throw_exception) { if constexpr (Policy == overflow_policy::throw_exception) { - return lhs * rhs; + if constexpr (detail::policy_equals(overflow_policy::throw_exception)) + { + return lhs * rhs; + } + else + { + return detail::impl::mul_impl(lhs, rhs); + } } else if constexpr (Policy == overflow_policy::saturate) { @@ -3028,14 +3247,21 @@ template } } -template -[[nodiscard]] constexpr auto div(const detail::signed_integer_basis lhs, - const detail::signed_integer_basis rhs) +template +[[nodiscard]] constexpr auto div(const detail::signed_integer_basis lhs, + const detail::signed_integer_basis rhs) noexcept(Policy == overflow_policy::checked || Policy == overflow_policy::strict) { if constexpr (Policy == overflow_policy::throw_exception) { - return lhs / rhs; + if constexpr (detail::policy_equals(overflow_policy::throw_exception)) + { + return lhs / rhs; + } + else + { + return detail::impl::div_impl(lhs, rhs); + } } else if constexpr (Policy == overflow_policy::saturate) { @@ -3059,14 +3285,21 @@ template } } -template -[[nodiscard]] constexpr auto mod(const detail::signed_integer_basis lhs, - const detail::signed_integer_basis rhs) +template +[[nodiscard]] constexpr auto mod(const detail::signed_integer_basis lhs, + const detail::signed_integer_basis rhs) noexcept(Policy == overflow_policy::checked || Policy == overflow_policy::strict) { if constexpr (Policy == overflow_policy::throw_exception) { - return lhs % rhs; + if constexpr (detail::policy_equals(overflow_policy::throw_exception)) + { + return lhs % rhs; + } + else + { + return detail::impl::mod_impl(lhs, rhs); + } } else if constexpr (Policy == overflow_policy::saturate) { @@ -3090,48 +3323,48 @@ template } } -template -constexpr auto operator~(const detail::signed_integer_basis lhs) noexcept +template +constexpr auto operator~(const detail::signed_integer_basis lhs) noexcept { static_assert(detail::dependent_false, "Bitwise NOT is deliberately disabled for signed safe integers (see Ada)"); return lhs; // LCOV_EXCL_LINE : deliberately unreachable } -template -constexpr auto operator&(const detail::signed_integer_basis lhs, - const detail::signed_integer_basis) noexcept +template +constexpr auto operator&(const detail::signed_integer_basis lhs, + const detail::signed_integer_basis) noexcept { static_assert(detail::dependent_false, "Bitwise AND is deliberately disabled for signed safe integers (see Ada)"); return lhs; // LCOV_EXCL_LINE : deliberately unreachable } -template -constexpr auto operator|(const detail::signed_integer_basis lhs, - const detail::signed_integer_basis) noexcept +template +constexpr auto operator|(const detail::signed_integer_basis lhs, + const detail::signed_integer_basis) noexcept { static_assert(detail::dependent_false, "Bitwise OR is deliberately disabled for signed safe integers (see Ada)"); return lhs; // LCOV_EXCL_LINE : deliberately unreachable } -template -constexpr auto operator^(const detail::signed_integer_basis lhs, - const detail::signed_integer_basis) noexcept +template +constexpr auto operator^(const detail::signed_integer_basis lhs, + const detail::signed_integer_basis) noexcept { static_assert(detail::dependent_false, "Bitwise XOR is deliberately disabled for signed safe integers (see Ada)"); return lhs; // LCOV_EXCL_LINE : deliberately unreachable } -template -constexpr auto operator<<(const detail::signed_integer_basis lhs, - const detail::signed_integer_basis) noexcept +template +constexpr auto operator<<(const detail::signed_integer_basis lhs, + const detail::signed_integer_basis) noexcept { static_assert(detail::dependent_false, "Left shift is deliberately disabled for signed safe integers (see Ada)"); return lhs; // LCOV_EXCL_LINE : deliberately unreachable } -template -constexpr auto operator>>(const detail::signed_integer_basis lhs, - const detail::signed_integer_basis) noexcept +template +constexpr auto operator>>(const detail::signed_integer_basis lhs, + const detail::signed_integer_basis) noexcept { static_assert(detail::dependent_false, "Right shift is deliberately disabled for signed safe integers (see Ada)"); return lhs; // LCOV_EXCL_LINE : deliberately unreachable diff --git a/include/boost/safe_numbers/detail/type_traits.hpp b/include/boost/safe_numbers/detail/type_traits.hpp index e6ff40f..df69b74 100644 --- a/include/boost/safe_numbers/detail/type_traits.hpp +++ b/include/boost/safe_numbers/detail/type_traits.hpp @@ -7,6 +7,7 @@ #include #include +#include #ifndef BOOST_SAFE_NUMBERS_BUILD_MODULE @@ -50,15 +51,164 @@ inline constexpr bool is_compatible_float_type = impl::is_compatible_float_type< template concept compatible_float_type = is_compatible_float_type; -template +// Which family of basis template a type-level error policy is validated against +enum class basis_kind +{ + unsigned_integer, + signed_integer, + floating_point +}; + +// True when the NTTP is an overflow_policy enumerator. Every comparison against +// an enumerator must sit behind this check because comparing a non-enum NTTP +// against overflow_policy would be ill-formed rather than false. +template +inline constexpr bool is_overflow_policy_v = std::is_same_v, overflow_policy>; + +// True when the NTTP is a user defined handler object rather than an +// overflow_policy enumerator. The handler interface is validated separately +// by the error_handler_for concept. +template +inline constexpr bool is_user_handler_v = std::is_class_v>; + +// A user defined handler is a stateless class whose on_error is callable on a +// const object with the error kind, a defined fallback value (the wrapped +// integer result, the dividend, or the raw IEEE 754 result), and the +// diagnostic message. Whatever it returns becomes the operation's result. +template +concept error_handler_for = std::is_empty_v && + requires(const Handler handler, const T value, const char* msg) + { + { handler.on_error(error_kind::overflow, value, msg) } -> std::same_as; + }; + +// Compares a policy NTTP against an enumerator, funneled so that user handler +// objects compare unequal instead of making the comparison ill-formed. +// consteval, so uses in runtime conditions fold to a constant. +template +consteval auto policy_equals(const overflow_policy policy) noexcept -> bool +{ + if constexpr (is_overflow_policy_v) + { + return Policy == policy; + } + else + { + return false; + } +} + +// noexcept specification for operations whose only error is overflow or +// underflow (add, sub, mul, shifts, increment, decrement, unary minus, and +// every float operator): throwing throws, saturate and strict do not, and a +// user handler propagates the noexcept of its on_error. +template +consteval auto policy_is_nothrow_arith() noexcept -> bool +{ + if constexpr (is_overflow_policy_v) + { + return Policy != overflow_policy::throw_exception; + } + else + { + return noexcept(Policy.on_error(error_kind::overflow, T{}, static_cast(nullptr))); + } +} + +// noexcept specification for integer division and modulo, where division by +// zero throws under both throw_exception and saturate. +template +consteval auto policy_is_nothrow_div() noexcept -> bool +{ + if constexpr (is_overflow_policy_v) + { + return Policy == overflow_policy::strict; + } + else + { + return noexcept(Policy.on_error(error_kind::divide_by_zero, T{}, static_cast(nullptr))); + } +} + +// True when two policy NTTPs differ, without requiring comparability between +// unrelated handler types. Handlers are stateless, so same type means equal. +template +consteval auto policies_differ() noexcept -> bool +{ + if constexpr (!std::is_same_v) + { + return true; + } + else if constexpr (is_overflow_policy_v) + { + return LHSPolicy != RHSPolicy; + } + else + { + return false; + } +} + +// Policies whose result is not the operand type (pair, optional, or a wider type) +// can never live in the type itself; they remain free functions. +template +consteval auto is_value_returning_policy() noexcept -> bool +{ + if constexpr (is_overflow_policy_v) + { + return Policy == overflow_policy::overflow_tuple || + Policy == overflow_policy::checked || + Policy == overflow_policy::widen; + } + else + { + return false; + } +} + +// The subset of overflow_policy values allowed as a type-level policy for the +// given basis kind: throw_exception and saturate everywhere, strict only for integers. +template +consteval auto is_valid_type_policy(const basis_kind kind) noexcept -> bool +{ + if constexpr (is_overflow_policy_v) + { + // One expression rather than an if on a constant condition, which MSVC rejects + // under /W4 /WX (C4127) + return Policy == overflow_policy::throw_exception || + Policy == overflow_policy::saturate || + (Policy == overflow_policy::strict && kind != basis_kind::floating_point); + } + else + { + return false; + } +} + +template class unsigned_integer_basis; -template +template class signed_integer_basis; -template +template class float_basis; +// Maps the type argument of the basic_* alias templates onto the basis NTTP: +// the tag types select the built-in enum policies and any other type is a user +// defined handler passed by value. +template +inline constexpr auto type_policy_v = ErrorHandler{}; + +template <> +inline constexpr auto type_policy_v = overflow_policy::throw_exception; + +template <> +inline constexpr auto type_policy_v = overflow_policy::saturate; + +template <> +inline constexpr auto type_policy_v = overflow_policy::strict; + // is_unsigned_library_type (base + unsigned_integer_basis specialization) namespace impl { @@ -72,14 +222,14 @@ struct is_signed_library_type : std::false_type {}; template struct is_float_library_type : std::false_type {}; -template -struct is_unsigned_library_type> : std::true_type {}; +template +struct is_unsigned_library_type> : std::true_type {}; -template -struct is_signed_library_type> : std::true_type {}; +template +struct is_signed_library_type> : std::true_type {}; -template -struct is_float_library_type> : std::true_type {}; +template +struct is_float_library_type> : std::true_type {}; } // namespace impl @@ -102,20 +252,20 @@ struct underlying using type = std::remove_cv_t>; }; -template -struct underlying> +template +struct underlying> { using type = T; }; -template -struct underlying> +template +struct underlying> { using type = T; }; -template -struct underlying> +template +struct underlying> { using type = T; }; @@ -289,14 +439,14 @@ namespace impl { template struct is_library_type : std::false_type {}; -template -struct is_library_type> : std::true_type {}; +template +struct is_library_type> : std::true_type {}; -template -struct is_library_type> : std::true_type {}; +template +struct is_library_type> : std::true_type {}; -template -struct is_library_type> : std::true_type {}; +template +struct is_library_type> : std::true_type {}; template struct is_library_type> : std::true_type {}; @@ -312,11 +462,11 @@ struct is_library_type> : std::true_type {}; template struct is_integral_library_type : std::false_type {}; -template -struct is_integral_library_type> : std::true_type {}; +template +struct is_integral_library_type> : std::true_type {}; -template -struct is_integral_library_type> : std::true_type {}; +template +struct is_integral_library_type> : std::true_type {}; template struct is_integral_library_type> : std::true_type {}; diff --git a/include/boost/safe_numbers/detail/unsigned_integer_basis.hpp b/include/boost/safe_numbers/detail/unsigned_integer_basis.hpp index 10fcaf0..d363be8 100644 --- a/include/boost/safe_numbers/detail/unsigned_integer_basis.hpp +++ b/include/boost/safe_numbers/detail/unsigned_integer_basis.hpp @@ -28,7 +28,7 @@ namespace boost::safe_numbers::detail { -template +template class unsigned_integer_basis { public: @@ -36,6 +36,22 @@ class unsigned_integer_basis // This is exposed to the user so that they can convert back to built-in using basis_type = BasisType; + static_assert(is_overflow_policy_v || error_handler_for, + "ErrorPolicy must be a boost::safe_numbers::overflow_policy enumerator or a stateless " + "handler type providing on_error(error_kind, BasisType, const char*) returning BasisType"); + + static_assert(!is_value_returning_policy(), + "overflow_tuple, checked, and widen change the result type of every operation, " + "so they can not be type-level policies: use the overflowing_*, checked_*, " + "and widening_* free functions instead"); + + static_assert(!is_overflow_policy_v || is_valid_type_policy(basis_kind::unsigned_integer), + "unsigned_integer_basis supports overflow_policy::throw_exception, " + "overflow_policy::saturate, and overflow_policy::strict"); + + // Exposed so that generic code and tests can query the type-level policy + static constexpr auto error_policy {ErrorPolicy}; + private: BasisType basis_ {0U}; @@ -61,20 +77,25 @@ class unsigned_integer_basis BOOST_SAFE_NUMBERS_HOST_DEVICE [[nodiscard]] friend constexpr auto operator<=>(unsigned_integer_basis lhs, unsigned_integer_basis rhs) noexcept -> std::strong_ordering = default; - template - BOOST_SAFE_NUMBERS_HOST_DEVICE constexpr auto operator+=(unsigned_integer_basis rhs) -> unsigned_integer_basis&; + template + BOOST_SAFE_NUMBERS_HOST_DEVICE constexpr auto operator+=(unsigned_integer_basis rhs) + noexcept(policy_is_nothrow_arith()) -> unsigned_integer_basis&; - template - BOOST_SAFE_NUMBERS_HOST_DEVICE constexpr auto operator-=(unsigned_integer_basis rhs) -> unsigned_integer_basis&; + template + BOOST_SAFE_NUMBERS_HOST_DEVICE constexpr auto operator-=(unsigned_integer_basis rhs) + noexcept(policy_is_nothrow_arith()) -> unsigned_integer_basis&; - template - BOOST_SAFE_NUMBERS_HOST_DEVICE constexpr auto operator*=(unsigned_integer_basis rhs) -> unsigned_integer_basis&; + template + BOOST_SAFE_NUMBERS_HOST_DEVICE constexpr auto operator*=(unsigned_integer_basis rhs) + noexcept(policy_is_nothrow_arith()) -> unsigned_integer_basis&; - template - BOOST_SAFE_NUMBERS_HOST_DEVICE constexpr auto operator/=(unsigned_integer_basis rhs) -> unsigned_integer_basis&; + template + BOOST_SAFE_NUMBERS_HOST_DEVICE constexpr auto operator/=(unsigned_integer_basis rhs) + noexcept(policy_is_nothrow_div()) -> unsigned_integer_basis&; - template - BOOST_SAFE_NUMBERS_HOST_DEVICE constexpr auto operator%=(unsigned_integer_basis rhs) -> unsigned_integer_basis&; + template + BOOST_SAFE_NUMBERS_HOST_DEVICE constexpr auto operator%=(unsigned_integer_basis rhs) + noexcept(policy_is_nothrow_div()) -> unsigned_integer_basis&; BOOST_SAFE_NUMBERS_HOST_DEVICE constexpr auto operator&=(unsigned_integer_basis rhs) noexcept -> unsigned_integer_basis&; @@ -82,17 +103,23 @@ class unsigned_integer_basis BOOST_SAFE_NUMBERS_HOST_DEVICE constexpr auto operator^=(unsigned_integer_basis rhs) noexcept -> unsigned_integer_basis&; - BOOST_SAFE_NUMBERS_HOST_DEVICE constexpr auto operator<<=(unsigned_integer_basis rhs) -> unsigned_integer_basis&; + BOOST_SAFE_NUMBERS_HOST_DEVICE constexpr auto operator<<=(unsigned_integer_basis rhs) + noexcept(policy_is_nothrow_arith()) -> unsigned_integer_basis&; - BOOST_SAFE_NUMBERS_HOST_DEVICE constexpr auto operator>>=(unsigned_integer_basis rhs) -> unsigned_integer_basis&; + BOOST_SAFE_NUMBERS_HOST_DEVICE constexpr auto operator>>=(unsigned_integer_basis rhs) + noexcept(policy_is_nothrow_arith()) -> unsigned_integer_basis&; - BOOST_SAFE_NUMBERS_HOST_DEVICE constexpr auto operator++() -> unsigned_integer_basis&; + BOOST_SAFE_NUMBERS_HOST_DEVICE constexpr auto operator++() + noexcept(policy_is_nothrow_arith()) -> unsigned_integer_basis&; - BOOST_SAFE_NUMBERS_HOST_DEVICE constexpr auto operator++(int) -> unsigned_integer_basis; + BOOST_SAFE_NUMBERS_HOST_DEVICE constexpr auto operator++(int) + noexcept(policy_is_nothrow_arith()) -> unsigned_integer_basis; - BOOST_SAFE_NUMBERS_HOST_DEVICE constexpr auto operator--() -> unsigned_integer_basis&; + BOOST_SAFE_NUMBERS_HOST_DEVICE constexpr auto operator--() + noexcept(policy_is_nothrow_arith()) -> unsigned_integer_basis&; - BOOST_SAFE_NUMBERS_HOST_DEVICE constexpr auto operator--(int) -> unsigned_integer_basis; + BOOST_SAFE_NUMBERS_HOST_DEVICE constexpr auto operator--(int) + noexcept(policy_is_nothrow_arith()) -> unsigned_integer_basis; BOOST_SAFE_NUMBERS_HOST_DEVICE constexpr auto operator+() const noexcept -> unsigned_integer_basis { return *this; } @@ -406,9 +433,9 @@ BOOST_SAFE_NUMBERS_HOST_DEVICE constexpr auto overflow_conversion_msg() noexcept } } -template +template template -BOOST_SAFE_NUMBERS_HOST_DEVICE constexpr unsigned_integer_basis::operator OtherBasis() const +BOOST_SAFE_NUMBERS_HOST_DEVICE constexpr unsigned_integer_basis::operator OtherBasis() const { if constexpr (sizeof(OtherBasis) < sizeof(BasisType)) { @@ -509,16 +536,17 @@ BOOST_SAFE_NUMBERS_HOST_DEVICE constexpr bool unsigned_no_intrin_add(const int12 } // namespace impl // Primary template for non-tuple policies -template +template struct add_helper { + template BOOST_SAFE_NUMBERS_HOST_DEVICE - [[nodiscard]] static constexpr auto apply(const unsigned_integer_basis lhs, - const unsigned_integer_basis rhs) - noexcept(Policy != overflow_policy::throw_exception) - -> unsigned_integer_basis + [[nodiscard]] static constexpr auto apply(const unsigned_integer_basis lhs, + const unsigned_integer_basis rhs) + noexcept(policy_is_nothrow_arith()) + -> unsigned_integer_basis { - using result_type = unsigned_integer_basis; + using result_type = unsigned_integer_basis; const auto lhs_basis {static_cast(lhs)}; const auto rhs_basis {static_cast(rhs)}; @@ -527,7 +555,9 @@ struct add_helper auto handle_overflow = [&res] { #if !defined(BOOST_SAFE_NUMBERS_HAS_GPU_SUPPORT) - if (std::is_constant_evaluated()) + // Saturation produces a defined value, so it must keep evaluating at + // constant evaluation time instead of failing the build with a throw + if (std::is_constant_evaluated() && !policy_equals(overflow_policy::saturate) && !is_user_handler_v) { if constexpr (std::is_same_v) { @@ -553,20 +583,24 @@ struct add_helper else #endif { - if constexpr (Policy == overflow_policy::throw_exception) + if constexpr (policy_equals(overflow_policy::throw_exception)) { static_cast(res); BOOST_SAFE_NUMBERS_THROW_EXCEPTION(std::overflow_error, overflow_add_msg()); } - else if constexpr (Policy == overflow_policy::saturate) + else if constexpr (policy_equals(overflow_policy::saturate)) { res = std::numeric_limits::max(); } - else if constexpr (Policy == overflow_policy::strict) + else if constexpr (policy_equals(overflow_policy::strict)) { static_cast(res); std::exit(EXIT_FAILURE); } + else if constexpr (is_user_handler_v) + { + res = Policy.on_error(error_kind::overflow, res, overflow_add_msg()); + } else { static_cast(res); @@ -590,7 +624,7 @@ struct add_helper const bool overflowed {impl::unsigned_intrin_add(lhs_basis, rhs_basis, res)}; #ifdef BOOST_SAFE_NUMBERS_ENABLE_COMPILE_ASSERT - if constexpr (Policy == overflow_policy::throw_exception || Policy == overflow_policy::strict) + if constexpr (policy_equals(overflow_policy::throw_exception) || policy_equals(overflow_policy::strict)) { BOOST_SAFE_NUMBERS_COMPILE_ASSERT_CONST_P(!overflowed, "unsigned addition overflow", lhs_basis, rhs_basis); } @@ -612,7 +646,7 @@ struct add_helper const bool overflowed {impl::unsigned_no_intrin_add(lhs_basis, rhs_basis, res)}; #ifdef BOOST_SAFE_NUMBERS_ENABLE_COMPILE_ASSERT - if constexpr (Policy == overflow_policy::throw_exception || Policy == overflow_policy::strict) + if constexpr (policy_equals(overflow_policy::throw_exception) || policy_equals(overflow_policy::strict)) { BOOST_SAFE_NUMBERS_COMPILE_ASSERT_CONST_P(!overflowed, "unsigned addition overflow", lhs_basis, rhs_basis); } @@ -631,12 +665,13 @@ struct add_helper template struct add_helper { + template BOOST_SAFE_NUMBERS_HOST_DEVICE - [[nodiscard]] static constexpr auto apply(const unsigned_integer_basis lhs, - const unsigned_integer_basis rhs) noexcept - -> std::pair, bool> + [[nodiscard]] static constexpr auto apply(const unsigned_integer_basis lhs, + const unsigned_integer_basis rhs) noexcept + -> std::pair, bool> { - using result_type = unsigned_integer_basis; + using result_type = unsigned_integer_basis; const auto lhs_basis {static_cast(lhs)}; const auto rhs_basis {static_cast(rhs)}; @@ -654,12 +689,13 @@ struct add_helper template struct add_helper { + template BOOST_SAFE_NUMBERS_HOST_DEVICE - [[nodiscard]] static constexpr auto apply(const unsigned_integer_basis lhs, - const unsigned_integer_basis rhs) noexcept - -> std::optional> + [[nodiscard]] static constexpr auto apply(const unsigned_integer_basis lhs, + const unsigned_integer_basis rhs) noexcept + -> std::optional> { - using result_type = unsigned_integer_basis; + using result_type = unsigned_integer_basis; const auto lhs_basis {static_cast(lhs)}; const auto rhs_basis {static_cast(rhs)}; @@ -677,31 +713,33 @@ struct add_helper template struct add_helper { + template BOOST_SAFE_NUMBERS_HOST_DEVICE - [[nodiscard]] static constexpr auto apply(const unsigned_integer_basis lhs, - const unsigned_integer_basis rhs) noexcept + [[nodiscard]] static constexpr auto apply(const unsigned_integer_basis lhs, + const unsigned_integer_basis rhs) noexcept { using promoted_type = promoted_type; static_assert(!std::is_same_v, "Widening policy with uint128_t is not supported"); - using result_type = unsigned_integer_basis; - return result_type{static_cast(static_cast(lhs) + rhs)}; + using result_type = unsigned_integer_basis; + return result_type{static_cast(static_cast(static_cast(lhs)) + static_cast(rhs))}; } }; -template +template BOOST_SAFE_NUMBERS_HOST_DEVICE -[[nodiscard]] constexpr auto add_impl(const unsigned_integer_basis lhs, - const unsigned_integer_basis rhs) - noexcept(Policy == overflow_policy::saturate || Policy == overflow_policy::overflow_tuple || Policy == overflow_policy::checked || Policy == overflow_policy::strict || Policy == overflow_policy::widen) +[[nodiscard]] constexpr auto add_impl(const unsigned_integer_basis lhs, + const unsigned_integer_basis rhs) + noexcept(policy_is_nothrow_arith()) { return add_helper::apply(lhs, rhs); } -template +template BOOST_SAFE_NUMBERS_HOST_DEVICE -[[nodiscard]] constexpr auto operator+(const unsigned_integer_basis lhs, - const unsigned_integer_basis rhs) -> unsigned_integer_basis +[[nodiscard]] constexpr auto operator+(const unsigned_integer_basis lhs, + const unsigned_integer_basis rhs) + noexcept(policy_is_nothrow_arith()) -> unsigned_integer_basis { // Here we do repeat some logic in the above add_impls // The reason for this is to significantly improve constexpr error messages. @@ -745,52 +783,63 @@ BOOST_SAFE_NUMBERS_HOST_DEVICE #if !defined(BOOST_SAFE_NUMBERS_HAS_GPU_SUPPORT) - if (std::is_constant_evaluated()) + // Only the throwing policy needs the duplicated diagnostics; the test is if constexpr + // so the throw is discarded where the operator is noexcept (GCC -Wterminate) + if constexpr (policy_equals(overflow_policy::throw_exception)) { - BasisType res {}; - if (impl::unsigned_no_intrin_add(static_cast(lhs), static_cast(rhs), res)) + if (std::is_constant_evaluated()) { - if constexpr (std::is_same_v) + BasisType res {}; + if (impl::unsigned_no_intrin_add(static_cast(lhs), static_cast(rhs), res)) { - throw std::overflow_error("Overflow detected in u8 addition"); // LCOV_EXCL_LINE - } - else if constexpr (std::is_same_v) - { - throw std::overflow_error("Overflow detected in u16 addition"); // LCOV_EXCL_LINE - } - else if constexpr (std::is_same_v) - { - throw std::overflow_error("Overflow detected in u32 addition"); // LCOV_EXCL_LINE - } - else if constexpr (std::is_same_v) - { - throw std::overflow_error("Overflow detected in u64 addition"); // LCOV_EXCL_LINE - } - else - { - throw std::overflow_error("Overflow detected in u128 addition"); // LCOV_EXCL_LINE + if constexpr (std::is_same_v) + { + throw std::overflow_error("Overflow detected in u8 addition"); // LCOV_EXCL_LINE + } + else if constexpr (std::is_same_v) + { + throw std::overflow_error("Overflow detected in u16 addition"); // LCOV_EXCL_LINE + } + else if constexpr (std::is_same_v) + { + throw std::overflow_error("Overflow detected in u32 addition"); // LCOV_EXCL_LINE + } + else if constexpr (std::is_same_v) + { + throw std::overflow_error("Overflow detected in u64 addition"); // LCOV_EXCL_LINE + } + else + { + throw std::overflow_error("Overflow detected in u128 addition"); // LCOV_EXCL_LINE + } } - } - return unsigned_integer_basis{res}; + return unsigned_integer_basis{res}; + } } #endif - return add_helper::apply(lhs, rhs); + return add_helper::apply(lhs, rhs); } } // namespace boost::safe_numbers::detail #define BOOST_SAFE_NUMBERS_DEFINE_MIXED_UNSIGNED_INTEGER_OP(OP_NAME, OP_SYMBOL) \ -template \ - requires (!std::is_same_v) \ +template \ + requires (!std::is_same_v || boost::safe_numbers::detail::policies_differ()) \ BOOST_SAFE_NUMBERS_HOST_DEVICE \ -constexpr auto OP_SYMBOL(const boost::safe_numbers::detail::unsigned_integer_basis, \ - const boost::safe_numbers::detail::unsigned_integer_basis) \ +constexpr auto OP_SYMBOL(const boost::safe_numbers::detail::unsigned_integer_basis, \ + const boost::safe_numbers::detail::unsigned_integer_basis) \ { \ - if constexpr (std::is_same_v) \ + if constexpr (std::is_same_v) \ + { \ + static_assert(boost::safe_numbers::detail::dependent_false, \ + "Can not perform " OP_NAME " between same width types with different overflow policies " \ + "(e.g. u8 and sat_u8): convert explicitly through basis_type first"); \ + } \ + else if constexpr (std::is_same_v) \ { \ if constexpr (std::is_same_v) \ { \ @@ -910,7 +959,7 @@ constexpr auto OP_SYMBOL(const boost::safe_numbers::detail::unsigned_integer_bas static_assert(boost::safe_numbers::detail::dependent_false, "Can not perform " OP_NAME " on mixed width unsigned integer types"); \ } \ \ - return boost::safe_numbers::detail::unsigned_integer_basis(0); \ + return boost::safe_numbers::detail::unsigned_integer_basis(0); \ } namespace boost::safe_numbers::detail { @@ -920,11 +969,11 @@ BOOST_SAFE_NUMBERS_DEFINE_MIXED_UNSIGNED_INTEGER_OP("equality", operator==) BOOST_SAFE_NUMBERS_DEFINE_MIXED_UNSIGNED_INTEGER_OP("addition", operator+) -template -template +template +template BOOST_SAFE_NUMBERS_HOST_DEVICE -constexpr auto unsigned_integer_basis::operator+=(const unsigned_integer_basis rhs) - -> unsigned_integer_basis& +constexpr auto unsigned_integer_basis::operator+=(const unsigned_integer_basis rhs) + noexcept(policy_is_nothrow_arith()) -> unsigned_integer_basis& { *this = *this + rhs; return *this; @@ -1019,16 +1068,17 @@ BOOST_SAFE_NUMBERS_HOST_DEVICE constexpr bool unsigned_no_intrin_sub(const int12 } // namespace impl // Primary template for non-tuple policies -template +template struct sub_helper { + template BOOST_SAFE_NUMBERS_HOST_DEVICE - [[nodiscard]] static constexpr auto apply(const unsigned_integer_basis lhs, - const unsigned_integer_basis rhs) - noexcept(Policy != overflow_policy::throw_exception) - -> unsigned_integer_basis + [[nodiscard]] static constexpr auto apply(const unsigned_integer_basis lhs, + const unsigned_integer_basis rhs) + noexcept(policy_is_nothrow_arith()) + -> unsigned_integer_basis { - using result_type = unsigned_integer_basis; + using result_type = unsigned_integer_basis; const auto lhs_basis {static_cast(lhs)}; const auto rhs_basis {static_cast(rhs)}; @@ -1038,7 +1088,9 @@ struct sub_helper { #if !defined(BOOST_SAFE_NUMBERS_HAS_GPU_SUPPORT) - if (std::is_constant_evaluated()) + // Saturation produces a defined value, so it must keep evaluating at + // constant evaluation time instead of failing the build with a throw + if (std::is_constant_evaluated() && !policy_equals(overflow_policy::saturate) && !is_user_handler_v) { if constexpr (std::is_same_v) { @@ -1064,20 +1116,24 @@ struct sub_helper else #endif { - if constexpr (Policy == overflow_policy::throw_exception) + if constexpr (policy_equals(overflow_policy::throw_exception)) { static_cast(res); BOOST_SAFE_NUMBERS_THROW_EXCEPTION(std::underflow_error, underflow_sub_msg()); } - else if constexpr (Policy == overflow_policy::saturate) + else if constexpr (policy_equals(overflow_policy::saturate)) { res = std::numeric_limits::min(); } - else if constexpr (Policy == overflow_policy::strict) + else if constexpr (policy_equals(overflow_policy::strict)) { static_cast(res); std::exit(EXIT_FAILURE); } + else if constexpr (is_user_handler_v) + { + res = Policy.on_error(error_kind::underflow, res, underflow_sub_msg()); + } else { static_cast(res); @@ -1097,7 +1153,7 @@ struct sub_helper // A constant subtraction that underflows is a build error under the error // policies (throw_exception, strict); value policies are excluded. #ifdef BOOST_SAFE_NUMBERS_ENABLE_COMPILE_ASSERT - if constexpr (Policy == overflow_policy::throw_exception || Policy == overflow_policy::strict) + if constexpr (policy_equals(overflow_policy::throw_exception) || policy_equals(overflow_policy::strict)) { BOOST_SAFE_NUMBERS_COMPILE_ASSERT_CONST_P(!underflowed, "unsigned subtraction underflow"); } @@ -1117,7 +1173,7 @@ struct sub_helper const bool underflowed {impl::unsigned_no_intrin_sub(lhs_basis, rhs_basis, res)}; #ifdef BOOST_SAFE_NUMBERS_ENABLE_COMPILE_ASSERT - if constexpr (Policy == overflow_policy::throw_exception || Policy == overflow_policy::strict) + if constexpr (policy_equals(overflow_policy::throw_exception) || policy_equals(overflow_policy::strict)) { BOOST_SAFE_NUMBERS_COMPILE_ASSERT_CONST_P(!underflowed, "unsigned subtraction underflow"); } @@ -1136,12 +1192,13 @@ struct sub_helper template struct sub_helper { + template BOOST_SAFE_NUMBERS_HOST_DEVICE - [[nodiscard]] static constexpr auto apply(const unsigned_integer_basis lhs, - const unsigned_integer_basis rhs) noexcept - -> std::pair, bool> + [[nodiscard]] static constexpr auto apply(const unsigned_integer_basis lhs, + const unsigned_integer_basis rhs) noexcept + -> std::pair, bool> { - using result_type = unsigned_integer_basis; + using result_type = unsigned_integer_basis; const auto lhs_basis {static_cast(lhs)}; const auto rhs_basis {static_cast(rhs)}; @@ -1159,12 +1216,13 @@ struct sub_helper template struct sub_helper { + template BOOST_SAFE_NUMBERS_HOST_DEVICE - [[nodiscard]] static constexpr auto apply(const unsigned_integer_basis lhs, - const unsigned_integer_basis rhs) noexcept - -> std::optional> + [[nodiscard]] static constexpr auto apply(const unsigned_integer_basis lhs, + const unsigned_integer_basis rhs) noexcept + -> std::optional> { - using result_type = unsigned_integer_basis; + using result_type = unsigned_integer_basis; const auto lhs_basis {static_cast(lhs)}; const auto rhs_basis {static_cast(rhs)}; @@ -1178,64 +1236,68 @@ struct sub_helper } }; -template +template BOOST_SAFE_NUMBERS_HOST_DEVICE -[[nodiscard]] constexpr auto sub_impl(const unsigned_integer_basis lhs, - const unsigned_integer_basis rhs) - noexcept(Policy == overflow_policy::saturate || Policy == overflow_policy::overflow_tuple || Policy == overflow_policy::checked || Policy == overflow_policy::strict) +[[nodiscard]] constexpr auto sub_impl(const unsigned_integer_basis lhs, + const unsigned_integer_basis rhs) + noexcept(policy_is_nothrow_arith()) { return sub_helper::apply(lhs, rhs); } -template +template BOOST_SAFE_NUMBERS_HOST_DEVICE -[[nodiscard]] constexpr auto operator-(const unsigned_integer_basis lhs, - const unsigned_integer_basis rhs) -> unsigned_integer_basis +[[nodiscard]] constexpr auto operator-(const unsigned_integer_basis lhs, + const unsigned_integer_basis rhs) + noexcept(policy_is_nothrow_arith()) -> unsigned_integer_basis { #if !defined(BOOST_SAFE_NUMBERS_HAS_GPU_SUPPORT) - if (std::is_constant_evaluated()) + if constexpr (policy_equals(overflow_policy::throw_exception)) { - BasisType res {}; - if (impl::unsigned_no_intrin_sub(static_cast(lhs), static_cast(rhs), res)) + if (std::is_constant_evaluated()) { - if constexpr (std::is_same_v) + BasisType res {}; + if (impl::unsigned_no_intrin_sub(static_cast(lhs), static_cast(rhs), res)) { - throw std::underflow_error("Underflow detected in u8 subtraction"); // LCOV_EXCL_LINE - } - else if constexpr (std::is_same_v) - { - throw std::underflow_error("Underflow detected in u16 subtraction"); // LCOV_EXCL_LINE - } - else if constexpr (std::is_same_v) - { - throw std::underflow_error("Underflow detected in u32 subtraction"); // LCOV_EXCL_LINE - } - else if constexpr (std::is_same_v) - { - throw std::underflow_error("Underflow detected in u64 subtraction"); // LCOV_EXCL_LINE - } - else - { - throw std::underflow_error("Underflow detected in u128 subtraction"); // LCOV_EXCL_LINE + if constexpr (std::is_same_v) + { + throw std::underflow_error("Underflow detected in u8 subtraction"); // LCOV_EXCL_LINE + } + else if constexpr (std::is_same_v) + { + throw std::underflow_error("Underflow detected in u16 subtraction"); // LCOV_EXCL_LINE + } + else if constexpr (std::is_same_v) + { + throw std::underflow_error("Underflow detected in u32 subtraction"); // LCOV_EXCL_LINE + } + else if constexpr (std::is_same_v) + { + throw std::underflow_error("Underflow detected in u64 subtraction"); // LCOV_EXCL_LINE + } + else + { + throw std::underflow_error("Underflow detected in u128 subtraction"); // LCOV_EXCL_LINE + } } - } - return unsigned_integer_basis{res}; + return unsigned_integer_basis{res}; + } } #endif - return sub_helper::apply(lhs, rhs); + return sub_helper::apply(lhs, rhs); } BOOST_SAFE_NUMBERS_DEFINE_MIXED_UNSIGNED_INTEGER_OP("subtraction", operator-) -template -template +template +template BOOST_SAFE_NUMBERS_HOST_DEVICE -constexpr auto unsigned_integer_basis::operator-=(const unsigned_integer_basis rhs) - -> unsigned_integer_basis& +constexpr auto unsigned_integer_basis::operator-=(const unsigned_integer_basis rhs) + noexcept(policy_is_nothrow_arith()) -> unsigned_integer_basis& { *this = *this - rhs; return *this; @@ -1339,16 +1401,17 @@ constexpr bool no_intrin_mul(const int128::uint128_t& lhs, const int128::uint128 } // namespace impl // Primary template for non-tuple policies -template +template struct mul_helper { + template BOOST_SAFE_NUMBERS_HOST_DEVICE - [[nodiscard]] static constexpr auto apply(const unsigned_integer_basis lhs, - const unsigned_integer_basis rhs) - noexcept(Policy == overflow_policy::saturate || Policy == overflow_policy::strict) - -> unsigned_integer_basis + [[nodiscard]] static constexpr auto apply(const unsigned_integer_basis lhs, + const unsigned_integer_basis rhs) + noexcept(policy_is_nothrow_arith()) + -> unsigned_integer_basis { - using result_type = unsigned_integer_basis; + using result_type = unsigned_integer_basis; const auto lhs_basis {static_cast(lhs)}; const auto rhs_basis {static_cast(rhs)}; @@ -1357,7 +1420,9 @@ struct mul_helper auto handle_overflow = [&res] { #if !defined(BOOST_SAFE_NUMBERS_HAS_GPU_SUPPORT) - if (std::is_constant_evaluated()) + // Saturation produces a defined value, so it must keep evaluating at + // constant evaluation time instead of failing the build with a throw + if (std::is_constant_evaluated() && !policy_equals(overflow_policy::saturate) && !is_user_handler_v) { if constexpr (std::is_same_v) { @@ -1383,20 +1448,24 @@ struct mul_helper else #endif { - if constexpr (Policy == overflow_policy::throw_exception) + if constexpr (policy_equals(overflow_policy::throw_exception)) { static_cast(res); BOOST_SAFE_NUMBERS_THROW_EXCEPTION(std::overflow_error, overflow_mul_msg()); } - else if constexpr (Policy == overflow_policy::saturate) + else if constexpr (policy_equals(overflow_policy::saturate)) { res = std::numeric_limits::max(); } - else if constexpr (Policy == overflow_policy::strict) + else if constexpr (policy_equals(overflow_policy::strict)) { static_cast(res); std::exit(EXIT_FAILURE); } + else if constexpr (is_user_handler_v) + { + res = Policy.on_error(error_kind::overflow, res, overflow_mul_msg()); + } else { static_cast(res); @@ -1419,7 +1488,7 @@ struct mul_helper // A constant multiplication that overflows is a build error under the error // policies (throw_exception, strict); value policies are excluded. #ifdef BOOST_SAFE_NUMBERS_ENABLE_COMPILE_ASSERT - if constexpr (Policy == overflow_policy::throw_exception || Policy == overflow_policy::strict) + if constexpr (policy_equals(overflow_policy::throw_exception) || policy_equals(overflow_policy::strict)) { BOOST_SAFE_NUMBERS_COMPILE_ASSERT_CONST_P(!overflowed, "unsigned multiplication overflow"); } @@ -1439,7 +1508,7 @@ struct mul_helper const bool overflowed {impl::no_intrin_mul(lhs_basis, rhs_basis, res)}; #ifdef BOOST_SAFE_NUMBERS_ENABLE_COMPILE_ASSERT - if constexpr (Policy == overflow_policy::throw_exception || Policy == overflow_policy::strict) + if constexpr (policy_equals(overflow_policy::throw_exception) || policy_equals(overflow_policy::strict)) { BOOST_SAFE_NUMBERS_COMPILE_ASSERT_CONST_P(!overflowed, "unsigned multiplication overflow"); } @@ -1458,12 +1527,13 @@ struct mul_helper template struct mul_helper { + template BOOST_SAFE_NUMBERS_HOST_DEVICE - [[nodiscard]] static constexpr auto apply(const unsigned_integer_basis lhs, - const unsigned_integer_basis rhs) noexcept - -> std::pair, bool> + [[nodiscard]] static constexpr auto apply(const unsigned_integer_basis lhs, + const unsigned_integer_basis rhs) noexcept + -> std::pair, bool> { - using result_type = unsigned_integer_basis; + using result_type = unsigned_integer_basis; const auto lhs_basis {static_cast(lhs)}; const auto rhs_basis {static_cast(rhs)}; @@ -1481,12 +1551,13 @@ struct mul_helper template struct mul_helper { + template BOOST_SAFE_NUMBERS_HOST_DEVICE - [[nodiscard]] static constexpr auto apply(const unsigned_integer_basis lhs, - const unsigned_integer_basis rhs) noexcept - -> std::optional> + [[nodiscard]] static constexpr auto apply(const unsigned_integer_basis lhs, + const unsigned_integer_basis rhs) noexcept + -> std::optional> { - using result_type = unsigned_integer_basis; + using result_type = unsigned_integer_basis; const auto lhs_basis {static_cast(lhs)}; const auto rhs_basis {static_cast(rhs)}; @@ -1504,76 +1575,81 @@ struct mul_helper template struct mul_helper { + template BOOST_SAFE_NUMBERS_HOST_DEVICE - [[nodiscard]] static constexpr auto apply(const unsigned_integer_basis lhs, - const unsigned_integer_basis rhs) noexcept + [[nodiscard]] static constexpr auto apply(const unsigned_integer_basis lhs, + const unsigned_integer_basis rhs) noexcept { using promoted_type = promoted_type; static_assert(!std::is_same_v, "Widening policy with uint128_t is not supported"); - using result_type = unsigned_integer_basis; - return result_type{static_cast(static_cast(lhs) * rhs)}; + using result_type = unsigned_integer_basis; + return result_type{static_cast(static_cast(static_cast(lhs)) * static_cast(rhs))}; } }; -template +template BOOST_SAFE_NUMBERS_HOST_DEVICE -[[nodiscard]] constexpr auto mul_impl(const unsigned_integer_basis lhs, - const unsigned_integer_basis rhs) - noexcept(Policy == overflow_policy::saturate || Policy == overflow_policy::overflow_tuple || Policy == overflow_policy::checked || Policy == overflow_policy::strict || Policy == overflow_policy::widen) +[[nodiscard]] constexpr auto mul_impl(const unsigned_integer_basis lhs, + const unsigned_integer_basis rhs) + noexcept(policy_is_nothrow_arith()) { return mul_helper::apply(lhs, rhs); } -template +template BOOST_SAFE_NUMBERS_HOST_DEVICE -[[nodiscard]] constexpr auto operator*(const unsigned_integer_basis lhs, - const unsigned_integer_basis rhs) -> unsigned_integer_basis +[[nodiscard]] constexpr auto operator*(const unsigned_integer_basis lhs, + const unsigned_integer_basis rhs) + noexcept(policy_is_nothrow_arith()) -> unsigned_integer_basis { #if !defined(BOOST_SAFE_NUMBERS_HAS_GPU_SUPPORT) - if (std::is_constant_evaluated()) + if constexpr (policy_equals(overflow_policy::throw_exception)) { - BasisType res {}; - if (impl::no_intrin_mul(static_cast(lhs), static_cast(rhs), res)) + if (std::is_constant_evaluated()) { - if constexpr (std::is_same_v) - { - throw std::overflow_error("Overflow detected in u8 multiplication"); // LCOV_EXCL_LINE - } - else if constexpr (std::is_same_v) + BasisType res {}; + if (impl::no_intrin_mul(static_cast(lhs), static_cast(rhs), res)) { - throw std::overflow_error("Overflow detected in u16 multiplication"); // LCOV_EXCL_LINE - } - else if constexpr (std::is_same_v) - { - throw std::overflow_error("Overflow detected in u32 multiplication"); // LCOV_EXCL_LINE - } - else if constexpr (std::is_same_v) - { - throw std::overflow_error("Overflow detected in u64 multiplication"); // LCOV_EXCL_LINE - } - else - { - throw std::overflow_error("Overflow detected in u128 multiplication"); // LCOV_EXCL_LINE + if constexpr (std::is_same_v) + { + throw std::overflow_error("Overflow detected in u8 multiplication"); // LCOV_EXCL_LINE + } + else if constexpr (std::is_same_v) + { + throw std::overflow_error("Overflow detected in u16 multiplication"); // LCOV_EXCL_LINE + } + else if constexpr (std::is_same_v) + { + throw std::overflow_error("Overflow detected in u32 multiplication"); // LCOV_EXCL_LINE + } + else if constexpr (std::is_same_v) + { + throw std::overflow_error("Overflow detected in u64 multiplication"); // LCOV_EXCL_LINE + } + else + { + throw std::overflow_error("Overflow detected in u128 multiplication"); // LCOV_EXCL_LINE + } } - } - return unsigned_integer_basis{res}; + return unsigned_integer_basis{res}; + } } #endif - return mul_helper::apply(lhs, rhs); + return mul_helper::apply(lhs, rhs); } BOOST_SAFE_NUMBERS_DEFINE_MIXED_UNSIGNED_INTEGER_OP("multiplication", operator*) -template -template +template +template BOOST_SAFE_NUMBERS_HOST_DEVICE -constexpr auto unsigned_integer_basis::operator*=(const unsigned_integer_basis rhs) - -> unsigned_integer_basis& +constexpr auto unsigned_integer_basis::operator*=(const unsigned_integer_basis rhs) + noexcept(policy_is_nothrow_arith()) -> unsigned_integer_basis& { *this = *this * rhs; return *this; @@ -1584,39 +1660,47 @@ constexpr auto unsigned_integer_basis::operator*=(const unsigned_inte // ------------------------------ // Primary template for non-tuple policies -template +template struct div_helper { + template BOOST_SAFE_NUMBERS_HOST_DEVICE - [[nodiscard]] static constexpr auto apply(const unsigned_integer_basis lhs, - const unsigned_integer_basis rhs) - noexcept(Policy == overflow_policy::strict) - -> unsigned_integer_basis + [[nodiscard]] static constexpr auto apply(const unsigned_integer_basis lhs, + const unsigned_integer_basis rhs) + noexcept(policy_is_nothrow_div()) + -> unsigned_integer_basis { - using result_type = unsigned_integer_basis; + using result_type = unsigned_integer_basis; const auto divisor {static_cast(rhs)}; // Divide-by-zero throws or exits under every policy that reaches this template // (throw_exception, saturate, strict), so a constant zero divisor is always a bug. #ifdef BOOST_SAFE_NUMBERS_ENABLE_COMPILE_ASSERT - BOOST_SAFE_NUMBERS_COMPILE_ASSERT_CONST_P(divisor != 0U, "unsigned division by zero"); + if constexpr (!is_user_handler_v) + { + BOOST_SAFE_NUMBERS_COMPILE_ASSERT_CONST_P(divisor != 0U, "unsigned division by zero"); + } #endif if (divisor == 0U) [[unlikely]] { - if constexpr (Policy == overflow_policy::throw_exception) + if constexpr (policy_equals(overflow_policy::throw_exception)) { BOOST_SAFE_NUMBERS_THROW_EXCEPTION(std::domain_error, div_by_zero_msg()); } - else if constexpr (Policy == overflow_policy::saturate) + else if constexpr (policy_equals(overflow_policy::saturate)) { BOOST_SAFE_NUMBERS_THROW_EXCEPTION(std::domain_error, div_by_zero_msg()); } - else if constexpr (Policy == overflow_policy::strict) + else if constexpr (policy_equals(overflow_policy::strict)) { std::exit(EXIT_FAILURE); } + else if constexpr (is_user_handler_v) + { + return result_type{Policy.on_error(error_kind::divide_by_zero, static_cast(lhs), div_by_zero_msg())}; + } else { BOOST_SAFE_NUMBERS_UNREACHABLE; @@ -1638,12 +1722,13 @@ struct div_helper template struct div_helper { + template BOOST_SAFE_NUMBERS_HOST_DEVICE - [[nodiscard]] static constexpr auto apply(const unsigned_integer_basis lhs, - const unsigned_integer_basis rhs) - -> std::pair, bool> + [[nodiscard]] static constexpr auto apply(const unsigned_integer_basis lhs, + const unsigned_integer_basis rhs) + -> std::pair, bool> { - using result_type = unsigned_integer_basis; + using result_type = unsigned_integer_basis; const auto divisor {static_cast(rhs)}; @@ -1672,12 +1757,13 @@ struct div_helper template struct div_helper { + template BOOST_SAFE_NUMBERS_HOST_DEVICE - [[nodiscard]] static constexpr auto apply(const unsigned_integer_basis lhs, - const unsigned_integer_basis rhs) noexcept - -> std::optional> + [[nodiscard]] static constexpr auto apply(const unsigned_integer_basis lhs, + const unsigned_integer_basis rhs) noexcept + -> std::optional> { - using result_type = unsigned_integer_basis; + using result_type = unsigned_integer_basis; const auto divisor {static_cast(rhs)}; if (divisor == 0U) [[unlikely]] @@ -1696,52 +1782,58 @@ struct div_helper } }; -template +template BOOST_SAFE_NUMBERS_HOST_DEVICE -[[nodiscard]] constexpr auto div_impl(const unsigned_integer_basis lhs, - const unsigned_integer_basis rhs) - noexcept(Policy == overflow_policy::checked || Policy == overflow_policy::strict) +[[nodiscard]] constexpr auto div_impl(const unsigned_integer_basis lhs, + const unsigned_integer_basis rhs) + noexcept(policy_equals(overflow_policy::checked) || policy_is_nothrow_div()) { return div_helper::apply(lhs, rhs); } -template +template BOOST_SAFE_NUMBERS_HOST_DEVICE -[[nodiscard]] constexpr auto operator/(const unsigned_integer_basis lhs, - const unsigned_integer_basis rhs) -> unsigned_integer_basis +[[nodiscard]] constexpr auto operator/(const unsigned_integer_basis lhs, + const unsigned_integer_basis rhs) + noexcept(policy_is_nothrow_div()) -> unsigned_integer_basis { #if !defined(BOOST_SAFE_NUMBERS_HAS_GPU_SUPPORT) - if (std::is_constant_evaluated()) + // Divide-by-zero throws under both throw_exception and saturate, + // so both keep the clean constant evaluation diagnostic + if constexpr (policy_equals(overflow_policy::throw_exception) || policy_equals(overflow_policy::saturate)) { - const auto divisor {static_cast(rhs)}; - if (divisor == 0U) [[unlikely]] + if (std::is_constant_evaluated()) { - throw std::domain_error("Unsigned division by zero"); // LCOV_EXCL_LINE - } + const auto divisor {static_cast(rhs)}; + if (divisor == 0U) [[unlikely]] + { + throw std::domain_error("Unsigned division by zero"); // LCOV_EXCL_LINE + } - if constexpr (std::is_same_v || std::is_same_v) - { - return unsigned_integer_basis{static_cast(static_cast(lhs) / divisor)}; - } - else - { - return unsigned_integer_basis{static_cast(lhs) / divisor}; + if constexpr (std::is_same_v || std::is_same_v) + { + return unsigned_integer_basis{static_cast(static_cast(lhs) / divisor)}; + } + else + { + return unsigned_integer_basis{static_cast(lhs) / divisor}; + } } } #endif - return div_helper::apply(lhs, rhs); + return div_helper::apply(lhs, rhs); } BOOST_SAFE_NUMBERS_DEFINE_MIXED_UNSIGNED_INTEGER_OP("division", operator/) -template -template +template +template BOOST_SAFE_NUMBERS_HOST_DEVICE -constexpr auto unsigned_integer_basis::operator/=(const unsigned_integer_basis rhs) - -> unsigned_integer_basis& +constexpr auto unsigned_integer_basis::operator/=(const unsigned_integer_basis rhs) + noexcept(policy_is_nothrow_div()) -> unsigned_integer_basis& { *this = *this / rhs; return *this; @@ -1752,39 +1844,47 @@ constexpr auto unsigned_integer_basis::operator/=(const unsigned_inte // ------------------------------ // Primary template for non-tuple policies -template +template struct mod_helper { + template BOOST_SAFE_NUMBERS_HOST_DEVICE - [[nodiscard]] static constexpr auto apply(const unsigned_integer_basis lhs, - const unsigned_integer_basis rhs) - noexcept(Policy == overflow_policy::strict) - -> unsigned_integer_basis + [[nodiscard]] static constexpr auto apply(const unsigned_integer_basis lhs, + const unsigned_integer_basis rhs) + noexcept(policy_is_nothrow_div()) + -> unsigned_integer_basis { - using result_type = unsigned_integer_basis; + using result_type = unsigned_integer_basis; const auto divisor {static_cast(rhs)}; // Modulo-by-zero throws or exits under every policy that reaches this template // (throw_exception, saturate, strict), so a constant zero divisor is always a bug. #ifdef BOOST_SAFE_NUMBERS_ENABLE_COMPILE_ASSERT - BOOST_SAFE_NUMBERS_COMPILE_ASSERT_CONST_P(divisor != 0U, "unsigned modulo by zero"); + if constexpr (!is_user_handler_v) + { + BOOST_SAFE_NUMBERS_COMPILE_ASSERT_CONST_P(divisor != 0U, "unsigned modulo by zero"); + } #endif if (divisor == 0U) [[unlikely]] { - if constexpr (Policy == overflow_policy::throw_exception) + if constexpr (policy_equals(overflow_policy::throw_exception)) { BOOST_SAFE_NUMBERS_THROW_EXCEPTION(std::domain_error, mod_by_zero_msg()); } - else if constexpr (Policy == overflow_policy::saturate) + else if constexpr (policy_equals(overflow_policy::saturate)) { BOOST_SAFE_NUMBERS_THROW_EXCEPTION(std::domain_error, mod_by_zero_msg()); } - else if constexpr (Policy == overflow_policy::strict) + else if constexpr (policy_equals(overflow_policy::strict)) { std::exit(EXIT_FAILURE); } + else if constexpr (is_user_handler_v) + { + return result_type{Policy.on_error(error_kind::divide_by_zero, static_cast(lhs), mod_by_zero_msg())}; + } else { BOOST_SAFE_NUMBERS_UNREACHABLE; @@ -1806,12 +1906,13 @@ struct mod_helper template struct mod_helper { + template BOOST_SAFE_NUMBERS_HOST_DEVICE - [[nodiscard]] static constexpr auto apply(const unsigned_integer_basis lhs, - const unsigned_integer_basis rhs) - -> std::pair, bool> + [[nodiscard]] static constexpr auto apply(const unsigned_integer_basis lhs, + const unsigned_integer_basis rhs) + -> std::pair, bool> { - using result_type = unsigned_integer_basis; + using result_type = unsigned_integer_basis; const auto divisor {static_cast(rhs)}; @@ -1840,12 +1941,13 @@ struct mod_helper template struct mod_helper { + template BOOST_SAFE_NUMBERS_HOST_DEVICE - [[nodiscard]] static constexpr auto apply(const unsigned_integer_basis lhs, - const unsigned_integer_basis rhs) noexcept - -> std::optional> + [[nodiscard]] static constexpr auto apply(const unsigned_integer_basis lhs, + const unsigned_integer_basis rhs) noexcept + -> std::optional> { - using result_type = unsigned_integer_basis; + using result_type = unsigned_integer_basis; const auto divisor {static_cast(rhs)}; if (divisor == 0U) [[unlikely]] @@ -1864,52 +1966,58 @@ struct mod_helper } }; -template +template BOOST_SAFE_NUMBERS_HOST_DEVICE -[[nodiscard]] constexpr auto mod_impl(const unsigned_integer_basis lhs, - const unsigned_integer_basis rhs) - noexcept(Policy == overflow_policy::checked || Policy == overflow_policy::strict) +[[nodiscard]] constexpr auto mod_impl(const unsigned_integer_basis lhs, + const unsigned_integer_basis rhs) + noexcept(policy_equals(overflow_policy::checked) || policy_is_nothrow_div()) { return mod_helper::apply(lhs, rhs); } -template +template BOOST_SAFE_NUMBERS_HOST_DEVICE -[[nodiscard]] constexpr auto operator%(const unsigned_integer_basis lhs, - const unsigned_integer_basis rhs) -> unsigned_integer_basis +[[nodiscard]] constexpr auto operator%(const unsigned_integer_basis lhs, + const unsigned_integer_basis rhs) + noexcept(policy_is_nothrow_div()) -> unsigned_integer_basis { #if !defined(BOOST_SAFE_NUMBERS_HAS_GPU_SUPPORT) - if (std::is_constant_evaluated()) + // Modulo-by-zero throws under both throw_exception and saturate, + // so both keep the clean constant evaluation diagnostic + if constexpr (policy_equals(overflow_policy::throw_exception) || policy_equals(overflow_policy::saturate)) { - const auto divisor {static_cast(rhs)}; - if (divisor == 0U) [[unlikely]] + if (std::is_constant_evaluated()) { - throw std::domain_error("Unsigned modulo by zero"); // LCOV_EXCL_LINE - } + const auto divisor {static_cast(rhs)}; + if (divisor == 0U) [[unlikely]] + { + throw std::domain_error("Unsigned modulo by zero"); // LCOV_EXCL_LINE + } - if constexpr (std::is_same_v || std::is_same_v) - { - return unsigned_integer_basis{static_cast(static_cast(lhs) % divisor)}; - } - else - { - return unsigned_integer_basis{static_cast(lhs) % divisor}; + if constexpr (std::is_same_v || std::is_same_v) + { + return unsigned_integer_basis{static_cast(static_cast(lhs) % divisor)}; + } + else + { + return unsigned_integer_basis{static_cast(lhs) % divisor}; + } } } #endif - return mod_helper::apply(lhs, rhs); + return mod_helper::apply(lhs, rhs); } BOOST_SAFE_NUMBERS_DEFINE_MIXED_UNSIGNED_INTEGER_OP("modulo", operator%) -template -template +template +template BOOST_SAFE_NUMBERS_HOST_DEVICE -constexpr auto unsigned_integer_basis::operator%=(const unsigned_integer_basis rhs) - -> unsigned_integer_basis& +constexpr auto unsigned_integer_basis::operator%=(const unsigned_integer_basis rhs) + noexcept(policy_is_nothrow_div()) -> unsigned_integer_basis& { *this = *this % rhs; return *this; @@ -1919,28 +2027,61 @@ constexpr auto unsigned_integer_basis::operator%=(const unsigned_inte // Pre and post increment // ------------------------------ -template +template BOOST_SAFE_NUMBERS_HOST_DEVICE -constexpr auto unsigned_integer_basis::operator++() - -> unsigned_integer_basis& +constexpr auto unsigned_integer_basis::operator++() + noexcept(policy_is_nothrow_arith()) -> unsigned_integer_basis& { if (this->basis_ == std::numeric_limits::max()) [[unlikely]] { - BOOST_SAFE_NUMBERS_THROW_EXCEPTION(std::overflow_error, overflow_inc_msg()); + if constexpr (policy_equals(overflow_policy::throw_exception)) + { + BOOST_SAFE_NUMBERS_THROW_EXCEPTION(std::overflow_error, overflow_inc_msg()); + } + else if constexpr (policy_equals(overflow_policy::saturate)) + { + return *this; + } + else if constexpr (policy_equals(overflow_policy::strict)) + { + std::exit(EXIT_FAILURE); + } + else + { + this->basis_ = ErrorPolicy.on_error(error_kind::overflow, std::numeric_limits::min(), overflow_inc_msg()); + return *this; + } } ++this->basis_; return *this; } -template +template BOOST_SAFE_NUMBERS_HOST_DEVICE -constexpr auto unsigned_integer_basis::operator++(int) - -> unsigned_integer_basis +constexpr auto unsigned_integer_basis::operator++(int) + noexcept(policy_is_nothrow_arith()) -> unsigned_integer_basis { if (this->basis_ == std::numeric_limits::max()) [[unlikely]] { - BOOST_SAFE_NUMBERS_THROW_EXCEPTION(std::overflow_error, overflow_inc_msg()); + if constexpr (policy_equals(overflow_policy::throw_exception)) + { + BOOST_SAFE_NUMBERS_THROW_EXCEPTION(std::overflow_error, overflow_inc_msg()); + } + else if constexpr (policy_equals(overflow_policy::saturate)) + { + return *this; + } + else if constexpr (policy_equals(overflow_policy::strict)) + { + std::exit(EXIT_FAILURE); + } + else + { + const auto temp {*this}; + this->basis_ = ErrorPolicy.on_error(error_kind::overflow, std::numeric_limits::min(), overflow_inc_msg()); + return temp; + } } const auto temp {*this}; @@ -1952,28 +2093,61 @@ constexpr auto unsigned_integer_basis::operator++(int) // Pre and post decrement // ------------------------------ -template +template BOOST_SAFE_NUMBERS_HOST_DEVICE -constexpr auto unsigned_integer_basis::operator--() - -> unsigned_integer_basis& +constexpr auto unsigned_integer_basis::operator--() + noexcept(policy_is_nothrow_arith()) -> unsigned_integer_basis& { if (this->basis_ == 0U) [[unlikely]] { - BOOST_SAFE_NUMBERS_THROW_EXCEPTION(std::underflow_error, underflow_dec_msg()); + if constexpr (policy_equals(overflow_policy::throw_exception)) + { + BOOST_SAFE_NUMBERS_THROW_EXCEPTION(std::underflow_error, underflow_dec_msg()); + } + else if constexpr (policy_equals(overflow_policy::saturate)) + { + return *this; + } + else if constexpr (policy_equals(overflow_policy::strict)) + { + std::exit(EXIT_FAILURE); + } + else + { + this->basis_ = ErrorPolicy.on_error(error_kind::underflow, std::numeric_limits::max(), underflow_dec_msg()); + return *this; + } } --this->basis_; return *this; } -template +template BOOST_SAFE_NUMBERS_HOST_DEVICE -constexpr auto unsigned_integer_basis::operator--(int) - -> unsigned_integer_basis +constexpr auto unsigned_integer_basis::operator--(int) + noexcept(policy_is_nothrow_arith()) -> unsigned_integer_basis { if (this->basis_ == 0U) [[unlikely]] { - BOOST_SAFE_NUMBERS_THROW_EXCEPTION(std::underflow_error, underflow_dec_msg()); + if constexpr (policy_equals(overflow_policy::throw_exception)) + { + BOOST_SAFE_NUMBERS_THROW_EXCEPTION(std::underflow_error, underflow_dec_msg()); + } + else if constexpr (policy_equals(overflow_policy::saturate)) + { + return *this; + } + else if constexpr (policy_equals(overflow_policy::strict)) + { + std::exit(EXIT_FAILURE); + } + else + { + const auto temp {*this}; + this->basis_ = ErrorPolicy.on_error(error_kind::underflow, std::numeric_limits::max(), underflow_dec_msg()); + return temp; + } } const auto temp {*this}; @@ -1986,16 +2160,17 @@ constexpr auto unsigned_integer_basis::operator--(int) // ------------------------------ // Primary template for non-tuple policies -template +template struct shl_helper { + template BOOST_SAFE_NUMBERS_HOST_DEVICE - [[nodiscard]] static constexpr auto apply(const unsigned_integer_basis lhs, - const unsigned_integer_basis rhs) - noexcept(Policy != overflow_policy::throw_exception) - -> unsigned_integer_basis + [[nodiscard]] static constexpr auto apply(const unsigned_integer_basis lhs, + const unsigned_integer_basis rhs) + noexcept(policy_is_nothrow_arith()) + -> unsigned_integer_basis { - using result_type = unsigned_integer_basis; + using result_type = unsigned_integer_basis; using core::bit_width; const auto raw_lhs {static_cast(lhs)}; @@ -2006,7 +2181,7 @@ struct shl_helper // Left shift past the type width is an error under throw_exception and strict; // saturate/checked/overflow_tuple return a defined value, so they are excluded. #ifdef BOOST_SAFE_NUMBERS_ENABLE_COMPILE_ASSERT - if constexpr (Policy == overflow_policy::throw_exception || Policy == overflow_policy::strict) + if constexpr (policy_equals(overflow_policy::throw_exception) || policy_equals(overflow_policy::strict)) { BOOST_SAFE_NUMBERS_COMPILE_ASSERT_CONST_P(!overflowed, "unsigned left shift past type width"); } @@ -2014,18 +2189,23 @@ struct shl_helper if (overflowed) { - if constexpr (Policy == overflow_policy::throw_exception) + if constexpr (policy_equals(overflow_policy::throw_exception)) { BOOST_SAFE_NUMBERS_THROW_EXCEPTION(std::overflow_error, left_shift_overflow_msg()); } - else if constexpr (Policy == overflow_policy::saturate) + else if constexpr (policy_equals(overflow_policy::saturate)) { return result_type{std::numeric_limits::max()}; } - else if constexpr (Policy == overflow_policy::strict) + else if constexpr (policy_equals(overflow_policy::strict)) { std::exit(EXIT_FAILURE); } + else if constexpr (is_user_handler_v) + { + const auto wrapped {static_cast(raw_lhs << (raw_rhs % static_cast(std::numeric_limits::digits)))}; + return result_type{Policy.on_error(error_kind::overflow, wrapped, left_shift_overflow_msg())}; + } else { BOOST_SAFE_NUMBERS_UNREACHABLE; @@ -2040,12 +2220,13 @@ struct shl_helper template struct shl_helper { + template BOOST_SAFE_NUMBERS_HOST_DEVICE - [[nodiscard]] static constexpr auto apply(const unsigned_integer_basis lhs, - const unsigned_integer_basis rhs) noexcept - -> std::pair, bool> + [[nodiscard]] static constexpr auto apply(const unsigned_integer_basis lhs, + const unsigned_integer_basis rhs) noexcept + -> std::pair, bool> { - using result_type = unsigned_integer_basis; + using result_type = unsigned_integer_basis; using core::bit_width; const auto raw_lhs {static_cast(lhs)}; @@ -2067,12 +2248,13 @@ struct shl_helper template struct shl_helper { + template BOOST_SAFE_NUMBERS_HOST_DEVICE - [[nodiscard]] static constexpr auto apply(const unsigned_integer_basis lhs, - const unsigned_integer_basis rhs) noexcept - -> std::optional> + [[nodiscard]] static constexpr auto apply(const unsigned_integer_basis lhs, + const unsigned_integer_basis rhs) noexcept + -> std::optional> { - using result_type = unsigned_integer_basis; + using result_type = unsigned_integer_basis; using core::bit_width; const auto raw_lhs {static_cast(lhs)}; @@ -2084,11 +2266,11 @@ struct shl_helper } }; -template +template BOOST_SAFE_NUMBERS_HOST_DEVICE -[[nodiscard]] constexpr auto shl_impl(const unsigned_integer_basis lhs, - const unsigned_integer_basis rhs) - noexcept(Policy == overflow_policy::saturate || Policy == overflow_policy::overflow_tuple || Policy == overflow_policy::checked || Policy == overflow_policy::strict) +[[nodiscard]] constexpr auto shl_impl(const unsigned_integer_basis lhs, + const unsigned_integer_basis rhs) + noexcept(policy_is_nothrow_arith()) { return shl_helper::apply(lhs, rhs); } @@ -2098,16 +2280,17 @@ BOOST_SAFE_NUMBERS_HOST_DEVICE // ------------------------------ // Primary template for non-tuple policies -template +template struct shr_helper { + template BOOST_SAFE_NUMBERS_HOST_DEVICE - [[nodiscard]] static constexpr auto apply(const unsigned_integer_basis lhs, - const unsigned_integer_basis rhs) - noexcept(Policy != overflow_policy::throw_exception) - -> unsigned_integer_basis + [[nodiscard]] static constexpr auto apply(const unsigned_integer_basis lhs, + const unsigned_integer_basis rhs) + noexcept(policy_is_nothrow_arith()) + -> unsigned_integer_basis { - using result_type = unsigned_integer_basis; + using result_type = unsigned_integer_basis; const auto raw_lhs {static_cast(lhs)}; const auto raw_rhs {static_cast(rhs)}; @@ -2116,7 +2299,7 @@ struct shr_helper // Right shift past the type width is an error under throw_exception and strict; // saturate/checked/overflow_tuple return a defined value, so they are excluded. #ifdef BOOST_SAFE_NUMBERS_ENABLE_COMPILE_ASSERT - if constexpr (Policy == overflow_policy::throw_exception || Policy == overflow_policy::strict) + if constexpr (policy_equals(overflow_policy::throw_exception) || policy_equals(overflow_policy::strict)) { BOOST_SAFE_NUMBERS_COMPILE_ASSERT_CONST_P(!overflowed, "unsigned right shift past type width"); } @@ -2124,18 +2307,23 @@ struct shr_helper if (overflowed) { - if constexpr (Policy == overflow_policy::throw_exception) + if constexpr (policy_equals(overflow_policy::throw_exception)) { BOOST_SAFE_NUMBERS_THROW_EXCEPTION(std::overflow_error, right_shift_overflow_msg()); } - else if constexpr (Policy == overflow_policy::saturate) + else if constexpr (policy_equals(overflow_policy::saturate)) { return result_type{0U}; } - else if constexpr (Policy == overflow_policy::strict) + else if constexpr (policy_equals(overflow_policy::strict)) { std::exit(EXIT_FAILURE); } + else if constexpr (is_user_handler_v) + { + const auto wrapped {static_cast(raw_lhs >> (raw_rhs % static_cast(std::numeric_limits::digits)))}; + return result_type{Policy.on_error(error_kind::overflow, wrapped, right_shift_overflow_msg())}; + } else { BOOST_SAFE_NUMBERS_UNREACHABLE; @@ -2150,12 +2338,13 @@ struct shr_helper template struct shr_helper { + template BOOST_SAFE_NUMBERS_HOST_DEVICE - [[nodiscard]] static constexpr auto apply(const unsigned_integer_basis lhs, - const unsigned_integer_basis rhs) noexcept - -> std::pair, bool> + [[nodiscard]] static constexpr auto apply(const unsigned_integer_basis lhs, + const unsigned_integer_basis rhs) noexcept + -> std::pair, bool> { - using result_type = unsigned_integer_basis; + using result_type = unsigned_integer_basis; const auto raw_lhs {static_cast(lhs)}; const auto raw_rhs {static_cast(rhs)}; @@ -2174,12 +2363,13 @@ struct shr_helper template struct shr_helper { + template BOOST_SAFE_NUMBERS_HOST_DEVICE - [[nodiscard]] static constexpr auto apply(const unsigned_integer_basis lhs, - const unsigned_integer_basis rhs) noexcept - -> std::optional> + [[nodiscard]] static constexpr auto apply(const unsigned_integer_basis lhs, + const unsigned_integer_basis rhs) noexcept + -> std::optional> { - using result_type = unsigned_integer_basis; + using result_type = unsigned_integer_basis; const auto raw_lhs {static_cast(lhs)}; const auto raw_rhs {static_cast(rhs)}; @@ -2189,11 +2379,11 @@ struct shr_helper } }; -template +template BOOST_SAFE_NUMBERS_HOST_DEVICE -[[nodiscard]] constexpr auto shr_impl(const unsigned_integer_basis lhs, - const unsigned_integer_basis rhs) - noexcept(Policy == overflow_policy::saturate || Policy == overflow_policy::overflow_tuple || Policy == overflow_policy::checked || Policy == overflow_policy::strict) +[[nodiscard]] constexpr auto shr_impl(const unsigned_integer_basis lhs, + const unsigned_integer_basis rhs) + noexcept(policy_is_nothrow_arith()) { return shr_helper::apply(lhs, rhs); } @@ -2206,233 +2396,233 @@ BOOST_SAFE_NUMBERS_HOST_DEVICE namespace boost::safe_numbers { -template +template BOOST_SAFE_NUMBERS_HOST_DEVICE -[[nodiscard]] constexpr auto saturating_add(const detail::unsigned_integer_basis lhs, - const detail::unsigned_integer_basis rhs) noexcept - -> detail::unsigned_integer_basis +[[nodiscard]] constexpr auto saturating_add(const detail::unsigned_integer_basis lhs, + const detail::unsigned_integer_basis rhs) noexcept + -> detail::unsigned_integer_basis { return detail::add_impl(lhs, rhs); } BOOST_SAFE_NUMBERS_DEFINE_MIXED_UNSIGNED_INTEGER_OP("saturating addition", saturating_add) -template +template BOOST_SAFE_NUMBERS_HOST_DEVICE -[[nodiscard]] constexpr auto saturating_sub(const detail::unsigned_integer_basis lhs, - const detail::unsigned_integer_basis rhs) noexcept - -> detail::unsigned_integer_basis +[[nodiscard]] constexpr auto saturating_sub(const detail::unsigned_integer_basis lhs, + const detail::unsigned_integer_basis rhs) noexcept + -> detail::unsigned_integer_basis { return detail::sub_impl(lhs, rhs); } BOOST_SAFE_NUMBERS_DEFINE_MIXED_UNSIGNED_INTEGER_OP("saturating subtraction", saturating_sub) -template +template BOOST_SAFE_NUMBERS_HOST_DEVICE -[[nodiscard]] constexpr auto saturating_mul(const detail::unsigned_integer_basis lhs, - const detail::unsigned_integer_basis rhs) noexcept - -> detail::unsigned_integer_basis +[[nodiscard]] constexpr auto saturating_mul(const detail::unsigned_integer_basis lhs, + const detail::unsigned_integer_basis rhs) noexcept + -> detail::unsigned_integer_basis { return detail::mul_impl(lhs, rhs); } BOOST_SAFE_NUMBERS_DEFINE_MIXED_UNSIGNED_INTEGER_OP("saturating multiplication", saturating_mul) -template +template BOOST_SAFE_NUMBERS_HOST_DEVICE -[[nodiscard]] constexpr auto saturating_div(const detail::unsigned_integer_basis lhs, - const detail::unsigned_integer_basis rhs) - -> detail::unsigned_integer_basis +[[nodiscard]] constexpr auto saturating_div(const detail::unsigned_integer_basis lhs, + const detail::unsigned_integer_basis rhs) + -> detail::unsigned_integer_basis { return detail::div_impl(lhs, rhs); } BOOST_SAFE_NUMBERS_DEFINE_MIXED_UNSIGNED_INTEGER_OP("saturating division", saturating_div) -template +template BOOST_SAFE_NUMBERS_HOST_DEVICE -[[nodiscard]] constexpr auto saturating_mod(const detail::unsigned_integer_basis lhs, - const detail::unsigned_integer_basis rhs) - -> detail::unsigned_integer_basis +[[nodiscard]] constexpr auto saturating_mod(const detail::unsigned_integer_basis lhs, + const detail::unsigned_integer_basis rhs) + -> detail::unsigned_integer_basis { return detail::mod_impl(lhs, rhs); } BOOST_SAFE_NUMBERS_DEFINE_MIXED_UNSIGNED_INTEGER_OP("saturating modulo", saturating_mod) -template +template BOOST_SAFE_NUMBERS_HOST_DEVICE -[[nodiscard]] constexpr auto overflowing_add(const detail::unsigned_integer_basis lhs, - const detail::unsigned_integer_basis rhs) noexcept - -> std::pair, bool> +[[nodiscard]] constexpr auto overflowing_add(const detail::unsigned_integer_basis lhs, + const detail::unsigned_integer_basis rhs) noexcept + -> std::pair, bool> { return detail::add_impl(lhs, rhs); } BOOST_SAFE_NUMBERS_DEFINE_MIXED_UNSIGNED_INTEGER_OP("overflowing addition", overflowing_add) -template +template BOOST_SAFE_NUMBERS_HOST_DEVICE -[[nodiscard]] constexpr auto overflowing_sub(const detail::unsigned_integer_basis lhs, - const detail::unsigned_integer_basis rhs) noexcept - -> std::pair, bool> +[[nodiscard]] constexpr auto overflowing_sub(const detail::unsigned_integer_basis lhs, + const detail::unsigned_integer_basis rhs) noexcept + -> std::pair, bool> { return detail::sub_impl(lhs, rhs); } BOOST_SAFE_NUMBERS_DEFINE_MIXED_UNSIGNED_INTEGER_OP("overflowing subtraction", overflowing_sub) -template +template BOOST_SAFE_NUMBERS_HOST_DEVICE -[[nodiscard]] constexpr auto overflowing_mul(const detail::unsigned_integer_basis lhs, - const detail::unsigned_integer_basis rhs) noexcept - -> std::pair, bool> +[[nodiscard]] constexpr auto overflowing_mul(const detail::unsigned_integer_basis lhs, + const detail::unsigned_integer_basis rhs) noexcept + -> std::pair, bool> { return detail::mul_impl(lhs, rhs); } BOOST_SAFE_NUMBERS_DEFINE_MIXED_UNSIGNED_INTEGER_OP("overflowing multiplication", overflowing_mul) -template +template BOOST_SAFE_NUMBERS_HOST_DEVICE -[[nodiscard]] constexpr auto overflowing_div(const detail::unsigned_integer_basis lhs, - const detail::unsigned_integer_basis rhs) - -> std::pair, bool> +[[nodiscard]] constexpr auto overflowing_div(const detail::unsigned_integer_basis lhs, + const detail::unsigned_integer_basis rhs) + -> std::pair, bool> { return detail::div_impl(lhs, rhs); } BOOST_SAFE_NUMBERS_DEFINE_MIXED_UNSIGNED_INTEGER_OP("overflowing division", overflowing_div) -template +template BOOST_SAFE_NUMBERS_HOST_DEVICE -[[nodiscard]] constexpr auto overflowing_mod(const detail::unsigned_integer_basis lhs, - const detail::unsigned_integer_basis rhs) - -> std::pair, bool> +[[nodiscard]] constexpr auto overflowing_mod(const detail::unsigned_integer_basis lhs, + const detail::unsigned_integer_basis rhs) + -> std::pair, bool> { return detail::mod_impl(lhs, rhs); } BOOST_SAFE_NUMBERS_DEFINE_MIXED_UNSIGNED_INTEGER_OP("overflowing modulo", overflowing_mod) -template +template BOOST_SAFE_NUMBERS_HOST_DEVICE -[[nodiscard]] constexpr auto checked_add(const detail::unsigned_integer_basis lhs, - const detail::unsigned_integer_basis rhs) noexcept - -> std::optional> +[[nodiscard]] constexpr auto checked_add(const detail::unsigned_integer_basis lhs, + const detail::unsigned_integer_basis rhs) noexcept + -> std::optional> { return detail::add_impl(lhs, rhs); } BOOST_SAFE_NUMBERS_DEFINE_MIXED_UNSIGNED_INTEGER_OP("checked addition", checked_add) -template +template BOOST_SAFE_NUMBERS_HOST_DEVICE -[[nodiscard]] constexpr auto checked_sub(const detail::unsigned_integer_basis lhs, - const detail::unsigned_integer_basis rhs) noexcept - -> std::optional> +[[nodiscard]] constexpr auto checked_sub(const detail::unsigned_integer_basis lhs, + const detail::unsigned_integer_basis rhs) noexcept + -> std::optional> { return detail::sub_impl(lhs, rhs); } BOOST_SAFE_NUMBERS_DEFINE_MIXED_UNSIGNED_INTEGER_OP("checked subtraction", checked_sub) -template +template BOOST_SAFE_NUMBERS_HOST_DEVICE -[[nodiscard]] constexpr auto checked_mul(const detail::unsigned_integer_basis lhs, - const detail::unsigned_integer_basis rhs) noexcept - -> std::optional> +[[nodiscard]] constexpr auto checked_mul(const detail::unsigned_integer_basis lhs, + const detail::unsigned_integer_basis rhs) noexcept + -> std::optional> { return detail::mul_impl(lhs, rhs); } BOOST_SAFE_NUMBERS_DEFINE_MIXED_UNSIGNED_INTEGER_OP("checked multiplication", checked_mul) -template +template BOOST_SAFE_NUMBERS_HOST_DEVICE -[[nodiscard]] constexpr auto checked_div(const detail::unsigned_integer_basis lhs, - const detail::unsigned_integer_basis rhs) noexcept - -> std::optional> +[[nodiscard]] constexpr auto checked_div(const detail::unsigned_integer_basis lhs, + const detail::unsigned_integer_basis rhs) noexcept + -> std::optional> { return detail::div_impl(lhs, rhs); } BOOST_SAFE_NUMBERS_DEFINE_MIXED_UNSIGNED_INTEGER_OP("checked division", checked_div) -template +template BOOST_SAFE_NUMBERS_HOST_DEVICE -[[nodiscard]] constexpr auto checked_mod(const detail::unsigned_integer_basis lhs, - const detail::unsigned_integer_basis rhs) noexcept - -> std::optional> +[[nodiscard]] constexpr auto checked_mod(const detail::unsigned_integer_basis lhs, + const detail::unsigned_integer_basis rhs) noexcept + -> std::optional> { return detail::mod_impl(lhs, rhs); } BOOST_SAFE_NUMBERS_DEFINE_MIXED_UNSIGNED_INTEGER_OP("checked modulo", checked_mod) -template -[[nodiscard]] constexpr auto strict_add(const detail::unsigned_integer_basis lhs, - const detail::unsigned_integer_basis rhs) noexcept - -> detail::unsigned_integer_basis +template +[[nodiscard]] constexpr auto strict_add(const detail::unsigned_integer_basis lhs, + const detail::unsigned_integer_basis rhs) noexcept + -> detail::unsigned_integer_basis { return detail::add_impl(lhs, rhs); } BOOST_SAFE_NUMBERS_DEFINE_MIXED_UNSIGNED_INTEGER_OP("strict addition", strict_add) -template -[[nodiscard]] constexpr auto strict_sub(const detail::unsigned_integer_basis lhs, - const detail::unsigned_integer_basis rhs) noexcept - -> detail::unsigned_integer_basis +template +[[nodiscard]] constexpr auto strict_sub(const detail::unsigned_integer_basis lhs, + const detail::unsigned_integer_basis rhs) noexcept + -> detail::unsigned_integer_basis { return detail::sub_impl(lhs, rhs); } BOOST_SAFE_NUMBERS_DEFINE_MIXED_UNSIGNED_INTEGER_OP("strict subtraction", strict_sub) -template -[[nodiscard]] constexpr auto strict_mul(const detail::unsigned_integer_basis lhs, - const detail::unsigned_integer_basis rhs) noexcept - -> detail::unsigned_integer_basis +template +[[nodiscard]] constexpr auto strict_mul(const detail::unsigned_integer_basis lhs, + const detail::unsigned_integer_basis rhs) noexcept + -> detail::unsigned_integer_basis { return detail::mul_impl(lhs, rhs); } BOOST_SAFE_NUMBERS_DEFINE_MIXED_UNSIGNED_INTEGER_OP("strict multiplication", strict_mul) -template -[[nodiscard]] constexpr auto strict_div(const detail::unsigned_integer_basis lhs, - const detail::unsigned_integer_basis rhs) noexcept - -> detail::unsigned_integer_basis +template +[[nodiscard]] constexpr auto strict_div(const detail::unsigned_integer_basis lhs, + const detail::unsigned_integer_basis rhs) noexcept + -> detail::unsigned_integer_basis { return detail::div_impl(lhs, rhs); } BOOST_SAFE_NUMBERS_DEFINE_MIXED_UNSIGNED_INTEGER_OP("strict division", strict_div) -template -[[nodiscard]] constexpr auto strict_mod(const detail::unsigned_integer_basis lhs, - const detail::unsigned_integer_basis rhs) noexcept - -> detail::unsigned_integer_basis +template +[[nodiscard]] constexpr auto strict_mod(const detail::unsigned_integer_basis lhs, + const detail::unsigned_integer_basis rhs) noexcept + -> detail::unsigned_integer_basis { return detail::mod_impl(lhs, rhs); } BOOST_SAFE_NUMBERS_DEFINE_MIXED_UNSIGNED_INTEGER_OP("strict modulo", strict_mod) -template -[[nodiscard]] constexpr auto widening_add(const detail::unsigned_integer_basis lhs, - const detail::unsigned_integer_basis rhs) noexcept +template +[[nodiscard]] constexpr auto widening_add(const detail::unsigned_integer_basis lhs, + const detail::unsigned_integer_basis rhs) noexcept { return detail::add_impl(lhs, rhs); } BOOST_SAFE_NUMBERS_DEFINE_MIXED_UNSIGNED_INTEGER_OP("widening add", widening_add) -template -[[nodiscard]] constexpr auto widening_mul(const detail::unsigned_integer_basis lhs, - const detail::unsigned_integer_basis rhs) noexcept +template +[[nodiscard]] constexpr auto widening_mul(const detail::unsigned_integer_basis lhs, + const detail::unsigned_integer_basis rhs) noexcept { return detail::mul_impl(lhs, rhs); } @@ -2443,22 +2633,22 @@ BOOST_SAFE_NUMBERS_DEFINE_MIXED_UNSIGNED_INTEGER_OP("widening mul", widening_mul // Saturating Shift // ------------------------------ -template +template BOOST_SAFE_NUMBERS_HOST_DEVICE -[[nodiscard]] constexpr auto saturating_shl(const detail::unsigned_integer_basis lhs, - const detail::unsigned_integer_basis rhs) noexcept - -> detail::unsigned_integer_basis +[[nodiscard]] constexpr auto saturating_shl(const detail::unsigned_integer_basis lhs, + const detail::unsigned_integer_basis rhs) noexcept + -> detail::unsigned_integer_basis { return detail::shl_impl(lhs, rhs); } BOOST_SAFE_NUMBERS_DEFINE_MIXED_UNSIGNED_INTEGER_OP("saturating left shift", saturating_shl) -template +template BOOST_SAFE_NUMBERS_HOST_DEVICE -[[nodiscard]] constexpr auto saturating_shr(const detail::unsigned_integer_basis lhs, - const detail::unsigned_integer_basis rhs) noexcept - -> detail::unsigned_integer_basis +[[nodiscard]] constexpr auto saturating_shr(const detail::unsigned_integer_basis lhs, + const detail::unsigned_integer_basis rhs) noexcept + -> detail::unsigned_integer_basis { return detail::shr_impl(lhs, rhs); } @@ -2469,22 +2659,22 @@ BOOST_SAFE_NUMBERS_DEFINE_MIXED_UNSIGNED_INTEGER_OP("saturating right shift", sa // Overflowing Shift // ------------------------------ -template +template BOOST_SAFE_NUMBERS_HOST_DEVICE -[[nodiscard]] constexpr auto overflowing_shl(const detail::unsigned_integer_basis lhs, - const detail::unsigned_integer_basis rhs) noexcept - -> std::pair, bool> +[[nodiscard]] constexpr auto overflowing_shl(const detail::unsigned_integer_basis lhs, + const detail::unsigned_integer_basis rhs) noexcept + -> std::pair, bool> { return detail::shl_impl(lhs, rhs); } BOOST_SAFE_NUMBERS_DEFINE_MIXED_UNSIGNED_INTEGER_OP("overflowing left shift", overflowing_shl) -template +template BOOST_SAFE_NUMBERS_HOST_DEVICE -[[nodiscard]] constexpr auto overflowing_shr(const detail::unsigned_integer_basis lhs, - const detail::unsigned_integer_basis rhs) noexcept - -> std::pair, bool> +[[nodiscard]] constexpr auto overflowing_shr(const detail::unsigned_integer_basis lhs, + const detail::unsigned_integer_basis rhs) noexcept + -> std::pair, bool> { return detail::shr_impl(lhs, rhs); } @@ -2495,22 +2685,22 @@ BOOST_SAFE_NUMBERS_DEFINE_MIXED_UNSIGNED_INTEGER_OP("overflowing right shift", o // Checked Shift // ------------------------------ -template +template BOOST_SAFE_NUMBERS_HOST_DEVICE -[[nodiscard]] constexpr auto checked_shl(const detail::unsigned_integer_basis lhs, - const detail::unsigned_integer_basis rhs) noexcept - -> std::optional> +[[nodiscard]] constexpr auto checked_shl(const detail::unsigned_integer_basis lhs, + const detail::unsigned_integer_basis rhs) noexcept + -> std::optional> { return detail::shl_impl(lhs, rhs); } BOOST_SAFE_NUMBERS_DEFINE_MIXED_UNSIGNED_INTEGER_OP("checked left shift", checked_shl) -template +template BOOST_SAFE_NUMBERS_HOST_DEVICE -[[nodiscard]] constexpr auto checked_shr(const detail::unsigned_integer_basis lhs, - const detail::unsigned_integer_basis rhs) noexcept - -> std::optional> +[[nodiscard]] constexpr auto checked_shr(const detail::unsigned_integer_basis lhs, + const detail::unsigned_integer_basis rhs) noexcept + -> std::optional> { return detail::shr_impl(lhs, rhs); } @@ -2521,20 +2711,20 @@ BOOST_SAFE_NUMBERS_DEFINE_MIXED_UNSIGNED_INTEGER_OP("checked right shift", check // Strict Shift // ------------------------------ -template -[[nodiscard]] constexpr auto strict_shl(const detail::unsigned_integer_basis lhs, - const detail::unsigned_integer_basis rhs) noexcept - -> detail::unsigned_integer_basis +template +[[nodiscard]] constexpr auto strict_shl(const detail::unsigned_integer_basis lhs, + const detail::unsigned_integer_basis rhs) noexcept + -> detail::unsigned_integer_basis { return detail::shl_impl(lhs, rhs); } BOOST_SAFE_NUMBERS_DEFINE_MIXED_UNSIGNED_INTEGER_OP("strict left shift", strict_shl) -template -[[nodiscard]] constexpr auto strict_shr(const detail::unsigned_integer_basis lhs, - const detail::unsigned_integer_basis rhs) noexcept - -> detail::unsigned_integer_basis +template +[[nodiscard]] constexpr auto strict_shr(const detail::unsigned_integer_basis lhs, + const detail::unsigned_integer_basis rhs) noexcept + -> detail::unsigned_integer_basis { return detail::shr_impl(lhs, rhs); } @@ -2545,14 +2735,23 @@ BOOST_SAFE_NUMBERS_DEFINE_MIXED_UNSIGNED_INTEGER_OP("strict right shift", strict // Generic policy-parameterized functions // ------------------------------ -template -[[nodiscard]] constexpr auto add(const detail::unsigned_integer_basis lhs, - const detail::unsigned_integer_basis rhs) +template +[[nodiscard]] constexpr auto add(const detail::unsigned_integer_basis lhs, + const detail::unsigned_integer_basis rhs) noexcept(Policy != overflow_policy::throw_exception) { if constexpr (Policy == overflow_policy::throw_exception) { - return lhs + rhs; + // Operand types with a non-throwing type-level policy must not fall back to + // their own operator, so route them straight to the throwing implementation + if constexpr (detail::policy_equals(overflow_policy::throw_exception)) + { + return lhs + rhs; + } + else + { + return detail::add_impl(lhs, rhs); + } } else if constexpr (Policy == overflow_policy::saturate) { @@ -2580,14 +2779,21 @@ template -[[nodiscard]] constexpr auto sub(const detail::unsigned_integer_basis lhs, - const detail::unsigned_integer_basis rhs) +template +[[nodiscard]] constexpr auto sub(const detail::unsigned_integer_basis lhs, + const detail::unsigned_integer_basis rhs) noexcept(Policy != overflow_policy::throw_exception) { if constexpr (Policy == overflow_policy::throw_exception) { - return lhs - rhs; + if constexpr (detail::policy_equals(overflow_policy::throw_exception)) + { + return lhs - rhs; + } + else + { + return detail::sub_impl(lhs, rhs); + } } else if constexpr (Policy == overflow_policy::saturate) { @@ -2611,14 +2817,21 @@ template -[[nodiscard]] constexpr auto mul(const detail::unsigned_integer_basis lhs, - const detail::unsigned_integer_basis rhs) +template +[[nodiscard]] constexpr auto mul(const detail::unsigned_integer_basis lhs, + const detail::unsigned_integer_basis rhs) noexcept(Policy != overflow_policy::throw_exception) { if constexpr (Policy == overflow_policy::throw_exception) { - return lhs * rhs; + if constexpr (detail::policy_equals(overflow_policy::throw_exception)) + { + return lhs * rhs; + } + else + { + return detail::mul_impl(lhs, rhs); + } } else if constexpr (Policy == overflow_policy::saturate) { @@ -2646,14 +2859,21 @@ template -[[nodiscard]] constexpr auto div(const detail::unsigned_integer_basis lhs, - const detail::unsigned_integer_basis rhs) +template +[[nodiscard]] constexpr auto div(const detail::unsigned_integer_basis lhs, + const detail::unsigned_integer_basis rhs) noexcept(Policy == overflow_policy::checked || Policy == overflow_policy::strict) { if constexpr (Policy == overflow_policy::throw_exception) { - return lhs / rhs; + if constexpr (detail::policy_equals(overflow_policy::throw_exception)) + { + return lhs / rhs; + } + else + { + return detail::div_impl(lhs, rhs); + } } else if constexpr (Policy == overflow_policy::saturate) { @@ -2677,14 +2897,21 @@ template -[[nodiscard]] constexpr auto mod(const detail::unsigned_integer_basis lhs, - const detail::unsigned_integer_basis rhs) +template +[[nodiscard]] constexpr auto mod(const detail::unsigned_integer_basis lhs, + const detail::unsigned_integer_basis rhs) noexcept(Policy == overflow_policy::checked || Policy == overflow_policy::strict) { if constexpr (Policy == overflow_policy::throw_exception) { - return lhs % rhs; + if constexpr (detail::policy_equals(overflow_policy::throw_exception)) + { + return lhs % rhs; + } + else + { + return detail::mod_impl(lhs, rhs); + } } else if constexpr (Policy == overflow_policy::saturate) { @@ -2708,106 +2935,108 @@ template -[[nodiscard]] constexpr auto shl(const detail::unsigned_integer_basis lhs, - const detail::unsigned_integer_basis rhs) +template +[[nodiscard]] constexpr auto shl(const detail::unsigned_integer_basis lhs, + const detail::unsigned_integer_basis rhs) noexcept(Policy != overflow_policy::throw_exception) { return detail::shl_impl(lhs, rhs); } -template -[[nodiscard]] constexpr auto shr(const detail::unsigned_integer_basis lhs, - const detail::unsigned_integer_basis rhs) +template +[[nodiscard]] constexpr auto shr(const detail::unsigned_integer_basis lhs, + const detail::unsigned_integer_basis rhs) noexcept(Policy != overflow_policy::throw_exception) { return detail::shr_impl(lhs, rhs); } -template -constexpr auto operator~(const detail::unsigned_integer_basis lhs) noexcept +template +constexpr auto operator~(const detail::unsigned_integer_basis lhs) noexcept { - using return_type = detail::unsigned_integer_basis; + using return_type = detail::unsigned_integer_basis; return return_type{static_cast(~detail::raw_value(lhs))}; } -template -constexpr auto operator&(const detail::unsigned_integer_basis lhs, - const detail::unsigned_integer_basis rhs) noexcept +template +constexpr auto operator&(const detail::unsigned_integer_basis lhs, + const detail::unsigned_integer_basis rhs) noexcept { - using return_type = detail::unsigned_integer_basis; + using return_type = detail::unsigned_integer_basis; return return_type{static_cast(detail::raw_value(lhs) & detail::raw_value(rhs))}; } -template -constexpr auto operator|(const detail::unsigned_integer_basis lhs, - const detail::unsigned_integer_basis rhs) noexcept +template +constexpr auto operator|(const detail::unsigned_integer_basis lhs, + const detail::unsigned_integer_basis rhs) noexcept { - using return_type = detail::unsigned_integer_basis; + using return_type = detail::unsigned_integer_basis; return return_type{static_cast(detail::raw_value(lhs) | detail::raw_value(rhs))}; } -template -constexpr auto operator^(const detail::unsigned_integer_basis lhs, - const detail::unsigned_integer_basis rhs) noexcept +template +constexpr auto operator^(const detail::unsigned_integer_basis lhs, + const detail::unsigned_integer_basis rhs) noexcept { - using return_type = detail::unsigned_integer_basis; + using return_type = detail::unsigned_integer_basis; return return_type{static_cast(detail::raw_value(lhs) ^ detail::raw_value(rhs))}; } -template -constexpr auto operator<<(const detail::unsigned_integer_basis lhs, - const detail::unsigned_integer_basis rhs) +template +constexpr auto operator<<(const detail::unsigned_integer_basis lhs, + const detail::unsigned_integer_basis rhs) + noexcept(detail::policy_is_nothrow_arith()) { - return detail::shl_impl(lhs, rhs); + return detail::shl_impl(lhs, rhs); } -template -constexpr auto operator>>(const detail::unsigned_integer_basis lhs, - const detail::unsigned_integer_basis rhs) +template +constexpr auto operator>>(const detail::unsigned_integer_basis lhs, + const detail::unsigned_integer_basis rhs) + noexcept(detail::policy_is_nothrow_arith()) { - return detail::shr_impl(lhs, rhs); + return detail::shr_impl(lhs, rhs); } // ------------------------------ // Compound bitwise operators // ------------------------------ -template -constexpr auto detail::unsigned_integer_basis::operator&=(const unsigned_integer_basis rhs) noexcept +template +constexpr auto detail::unsigned_integer_basis::operator&=(const unsigned_integer_basis rhs) noexcept -> unsigned_integer_basis& { *this = boost::safe_numbers::operator&(*this, rhs); return *this; } -template -constexpr auto detail::unsigned_integer_basis::operator|=(const unsigned_integer_basis rhs) noexcept +template +constexpr auto detail::unsigned_integer_basis::operator|=(const unsigned_integer_basis rhs) noexcept -> unsigned_integer_basis& { *this = boost::safe_numbers::operator|(*this, rhs); return *this; } -template -constexpr auto detail::unsigned_integer_basis::operator^=(const unsigned_integer_basis rhs) noexcept +template +constexpr auto detail::unsigned_integer_basis::operator^=(const unsigned_integer_basis rhs) noexcept -> unsigned_integer_basis& { *this = boost::safe_numbers::operator^(*this, rhs); return *this; } -template -constexpr auto detail::unsigned_integer_basis::operator<<=(const unsigned_integer_basis rhs) - -> unsigned_integer_basis& +template +constexpr auto detail::unsigned_integer_basis::operator<<=(const unsigned_integer_basis rhs) + noexcept(policy_is_nothrow_arith()) -> unsigned_integer_basis& { *this = boost::safe_numbers::operator<<(*this, rhs); return *this; } -template -constexpr auto detail::unsigned_integer_basis::operator>>=(const unsigned_integer_basis rhs) - -> unsigned_integer_basis& +template +constexpr auto detail::unsigned_integer_basis::operator>>=(const unsigned_integer_basis rhs) + noexcept(policy_is_nothrow_arith()) -> unsigned_integer_basis& { *this = boost::safe_numbers::operator>>(*this, rhs); return *this; diff --git a/include/boost/safe_numbers/floats.hpp b/include/boost/safe_numbers/floats.hpp index 2102d97..3809a1e 100644 --- a/include/boost/safe_numbers/floats.hpp +++ b/include/boost/safe_numbers/floats.hpp @@ -5,12 +5,32 @@ #ifndef BOOST_SAFE_NUMBERS_FLOATS_HPP #define BOOST_SAFE_NUMBERS_FLOATS_HPP +#include #include namespace boost::safe_numbers { -using f32 = detail::float_basis; -using f64 = detail::float_basis; +BOOST_SAFE_NUMBERS_EXPORT using f32 = detail::float_basis; + +BOOST_SAFE_NUMBERS_EXPORT using f64 = detail::float_basis; + +// Saturating counterparts with raw IEEE 754 semantics: overflow saturates to +// infinity, NaN propagates, and division by zero yields infinity. No checks run. +// basic_f32/basic_f64 below select the policy by type, including user handlers. + +BOOST_SAFE_NUMBERS_EXPORT using sat_f32 = detail::float_basis; + +BOOST_SAFE_NUMBERS_EXPORT using sat_f64 = detail::float_basis; + +// Width-named alias templates selecting the policy by type: basic_f32<> is f32, +// basic_f32 is sat_f32, and basic_f32 carries a user +// defined handler (see the error_handler_for concept). + +BOOST_SAFE_NUMBERS_EXPORT template +using basic_f32 = detail::float_basis>; + +BOOST_SAFE_NUMBERS_EXPORT template +using basic_f64 = detail::float_basis>; } // namespace boost::safe_numbers diff --git a/include/boost/safe_numbers/limits.hpp b/include/boost/safe_numbers/limits.hpp index 8670e42..358e8ee 100644 --- a/include/boost/safe_numbers/limits.hpp +++ b/include/boost/safe_numbers/limits.hpp @@ -74,53 +74,17 @@ namespace std { # pragma clang diagnostic ignored "-Wmismatched-tags" #endif -template <> -class numeric_limits : - public boost::safe_numbers::detail::numeric_limits_impl {}; +template +class numeric_limits> : + public boost::safe_numbers::detail::numeric_limits_impl> {}; -template <> -class numeric_limits : - public boost::safe_numbers::detail::numeric_limits_impl {}; +template +class numeric_limits> : + public boost::safe_numbers::detail::numeric_limits_impl> {}; -template <> -class numeric_limits : - public boost::safe_numbers::detail::numeric_limits_impl {}; - -template <> -class numeric_limits : - public boost::safe_numbers::detail::numeric_limits_impl {}; - -template <> -class numeric_limits : - public boost::safe_numbers::detail::numeric_limits_impl {}; - -template <> -class numeric_limits : - public boost::safe_numbers::detail::numeric_limits_impl {}; - -template <> -class numeric_limits : - public boost::safe_numbers::detail::numeric_limits_impl {}; - -template <> -class numeric_limits : - public boost::safe_numbers::detail::numeric_limits_impl {}; - -template <> -class numeric_limits : - public boost::safe_numbers::detail::numeric_limits_impl {}; - -template <> -class numeric_limits : - public boost::safe_numbers::detail::numeric_limits_impl {}; - -template <> -class numeric_limits : - public boost::safe_numbers::detail::numeric_limits_impl {}; - -template <> -class numeric_limits : - public boost::safe_numbers::detail::numeric_limits_impl {}; +template +class numeric_limits> : + public boost::safe_numbers::detail::numeric_limits_impl> {}; template class numeric_limits> diff --git a/include/boost/safe_numbers/overflow_policy.hpp b/include/boost/safe_numbers/overflow_policy.hpp index 59c7c94..60c1722 100644 --- a/include/boost/safe_numbers/overflow_policy.hpp +++ b/include/boost/safe_numbers/overflow_policy.hpp @@ -5,9 +5,11 @@ #ifndef BOOST_SAFE_NUMBERS_OVERFLOW_POLICY_HPP #define BOOST_SAFE_NUMBERS_OVERFLOW_POLICY_HPP +#include + namespace boost::safe_numbers { -enum class overflow_policy +BOOST_SAFE_NUMBERS_EXPORT enum class overflow_policy { throw_exception, saturate, @@ -17,6 +19,28 @@ enum class overflow_policy widen, }; +// The kind of error being reported to a user defined handler. +// divide_by_zero covers modulo by zero as well; the message distinguishes them. +// invalid_operation and nan_operation are produced by the floating point types only. +BOOST_SAFE_NUMBERS_EXPORT enum class error_kind +{ + overflow, + underflow, + divide_by_zero, + invalid_operation, + nan_operation, +}; + +// Tag types selecting the built-in policies through the basic_* alias templates, +// e.g. basic_u8. A user defined handler type is used the same way: +// basic_u8. + +BOOST_SAFE_NUMBERS_EXPORT struct throwing {}; + +BOOST_SAFE_NUMBERS_EXPORT struct saturating {}; + +BOOST_SAFE_NUMBERS_EXPORT struct strict {}; + } // namespace boost::safe_numbers #endif // BOOST_SAFE_NUMBERS_OVERFLOW_POLICY_HPP diff --git a/include/boost/safe_numbers/signed_integers.hpp b/include/boost/safe_numbers/signed_integers.hpp index c7fa21e..20544b8 100644 --- a/include/boost/safe_numbers/signed_integers.hpp +++ b/include/boost/safe_numbers/signed_integers.hpp @@ -28,6 +28,51 @@ BOOST_SAFE_NUMBERS_EXPORT using i64 = detail::signed_integer_basis BOOST_SAFE_NUMBERS_EXPORT using i128 = detail::signed_integer_basis; +// Saturating counterparts: overflow and underflow clamp to the numeric limits +// instead of throwing (including MIN / -1 and negation of MIN). +// Division and modulo by zero still throw std::domain_error. + +BOOST_SAFE_NUMBERS_EXPORT using sat_i8 = detail::signed_integer_basis; + +BOOST_SAFE_NUMBERS_EXPORT using sat_i16 = detail::signed_integer_basis; + +BOOST_SAFE_NUMBERS_EXPORT using sat_i32 = detail::signed_integer_basis; + +BOOST_SAFE_NUMBERS_EXPORT using sat_i64 = detail::signed_integer_basis; + +BOOST_SAFE_NUMBERS_EXPORT using sat_i128 = detail::signed_integer_basis; + +// Strict counterparts: any error terminates via std::exit(EXIT_FAILURE). Host only. + +BOOST_SAFE_NUMBERS_EXPORT using strict_i8 = detail::signed_integer_basis; + +BOOST_SAFE_NUMBERS_EXPORT using strict_i16 = detail::signed_integer_basis; + +BOOST_SAFE_NUMBERS_EXPORT using strict_i32 = detail::signed_integer_basis; + +BOOST_SAFE_NUMBERS_EXPORT using strict_i64 = detail::signed_integer_basis; + +BOOST_SAFE_NUMBERS_EXPORT using strict_i128 = detail::signed_integer_basis; + +// Width-named alias templates selecting the policy by type: basic_i8<> is i8, +// basic_i8 is sat_i8, and basic_i8 carries a user +// defined handler (see the error_handler_for concept). + +BOOST_SAFE_NUMBERS_EXPORT template +using basic_i8 = detail::signed_integer_basis>; + +BOOST_SAFE_NUMBERS_EXPORT template +using basic_i16 = detail::signed_integer_basis>; + +BOOST_SAFE_NUMBERS_EXPORT template +using basic_i32 = detail::signed_integer_basis>; + +BOOST_SAFE_NUMBERS_EXPORT template +using basic_i64 = detail::signed_integer_basis>; + +BOOST_SAFE_NUMBERS_EXPORT template +using basic_i128 = detail::signed_integer_basis>; + } // namespace boost::safe_numbers #endif // BOOST_SAFE_NUMBERS_SIGNED_INTEGERS_HPP diff --git a/include/boost/safe_numbers/unsigned_integers.hpp b/include/boost/safe_numbers/unsigned_integers.hpp index 99e3735..fe35a49 100644 --- a/include/boost/safe_numbers/unsigned_integers.hpp +++ b/include/boost/safe_numbers/unsigned_integers.hpp @@ -27,6 +27,50 @@ BOOST_SAFE_NUMBERS_EXPORT using u64 = detail::unsigned_integer_basis; +// Saturating counterparts: overflow clamps to the numeric limits instead of throwing. +// Division and modulo by zero still throw std::domain_error. + +BOOST_SAFE_NUMBERS_EXPORT using sat_u8 = detail::unsigned_integer_basis; + +BOOST_SAFE_NUMBERS_EXPORT using sat_u16 = detail::unsigned_integer_basis; + +BOOST_SAFE_NUMBERS_EXPORT using sat_u32 = detail::unsigned_integer_basis; + +BOOST_SAFE_NUMBERS_EXPORT using sat_u64 = detail::unsigned_integer_basis; + +BOOST_SAFE_NUMBERS_EXPORT using sat_u128 = detail::unsigned_integer_basis; + +// Strict counterparts: any error terminates via std::exit(EXIT_FAILURE). Host only. + +BOOST_SAFE_NUMBERS_EXPORT using strict_u8 = detail::unsigned_integer_basis; + +BOOST_SAFE_NUMBERS_EXPORT using strict_u16 = detail::unsigned_integer_basis; + +BOOST_SAFE_NUMBERS_EXPORT using strict_u32 = detail::unsigned_integer_basis; + +BOOST_SAFE_NUMBERS_EXPORT using strict_u64 = detail::unsigned_integer_basis; + +BOOST_SAFE_NUMBERS_EXPORT using strict_u128 = detail::unsigned_integer_basis; + +// Width-named alias templates selecting the policy by type: basic_u8<> is u8, +// basic_u8 is sat_u8, and basic_u8 carries a user +// defined handler (see the error_handler_for concept). + +BOOST_SAFE_NUMBERS_EXPORT template +using basic_u8 = detail::unsigned_integer_basis>; + +BOOST_SAFE_NUMBERS_EXPORT template +using basic_u16 = detail::unsigned_integer_basis>; + +BOOST_SAFE_NUMBERS_EXPORT template +using basic_u32 = detail::unsigned_integer_basis>; + +BOOST_SAFE_NUMBERS_EXPORT template +using basic_u64 = detail::unsigned_integer_basis>; + +BOOST_SAFE_NUMBERS_EXPORT template +using basic_u128 = detail::unsigned_integer_basis>; + } // namespace boost::safe_numbers #endif // BOOST_SAFE_NUMBERS_UNSIGNED_INTEGERS_HPP diff --git a/test/Jamfile b/test/Jamfile index 3529aa4..0049e03 100644 --- a/test/Jamfile +++ b/test/Jamfile @@ -200,6 +200,28 @@ run-fail test_signed_strict_multiplication.cpp ; run-fail test_signed_strict_division.cpp ; run-fail test_signed_strict_mod.cpp ; +# Type-level policy tests (second template parameter on the basis templates) +run test_unsigned_sat_type_arithmetic.cpp ; +run test_signed_sat_type_arithmetic.cpp ; +run test_float_sat_type_arithmetic.cpp ; +run test_policy_type_traits.cpp ; +run-fail test_unsigned_strict_type_addition.cpp ; +run-fail test_unsigned_strict_type_division.cpp ; +run-fail test_signed_strict_type_addition.cpp ; +run-fail test_signed_strict_type_division.cpp ; +run test_user_defined_handler.cpp ; +compile-fail compile_fail_handler_stateful.cpp ; +compile-fail compile_fail_handler_bad_signature.cpp ; +compile-fail compile_fail_handler_mixed_policy.cpp ; +compile-fail compile_fail_handler_tag_nttp.cpp ; +compile-fail compile_fail_mixed_policy_ops.cpp ; +compile-fail compile_fail_mixed_policy_compare.cpp ; +compile-fail compile_fail_policy_checked_type.cpp ; +compile-fail compile_fail_policy_overflow_tuple_type.cpp ; +compile-fail compile_fail_policy_widen_type.cpp ; +compile-fail compile_fail_strict_float_type.cpp ; +compile-fail compile_fail_policy_wrong_kind.cpp ; + # Exhaustive verification tests run test_exhaustive_u8_arithmetic.cpp ; run test_boundary_arithmetic.cpp ; @@ -310,6 +332,8 @@ run ../examples/strict_arithmetic.cpp ; run ../examples/generic_arithmetic.cpp ; run ../examples/literals.cpp ; run ../examples/fmt_format.cpp ; +run ../examples/policy_types.cpp ; +run ../examples/user_defined_handler.cpp ; run ../examples/iostream.cpp ; run ../examples/charconv.cpp ; run ../examples/bit.cpp ; diff --git a/test/benchmarks/benchmark_float_operations.cpp b/test/benchmarks/benchmark_float_operations.cpp index 1a771e9..9c37121 100644 --- a/test/benchmarks/benchmark_float_operations.cpp +++ b/test/benchmarks/benchmark_float_operations.cpp @@ -40,8 +40,8 @@ using namespace boost::safe_numbers; template struct underlying_for_bench { using type = T; }; -template -struct underlying_for_bench> { using type = T; }; +template +struct underlying_for_bench> { using type = T; }; template using underlying_for_bench_t = typename underlying_for_bench::type; diff --git a/test/benchmarks/benchmark_signed_operations.cpp b/test/benchmarks/benchmark_signed_operations.cpp index 3995c53..3e2e65b 100644 --- a/test/benchmarks/benchmark_signed_operations.cpp +++ b/test/benchmarks/benchmark_signed_operations.cpp @@ -79,8 +79,8 @@ using namespace boost::safe_numbers; template struct underlying_for_bench { using type = T; }; -template -struct underlying_for_bench> { using type = T; }; +template +struct underlying_for_bench> { using type = T; }; template struct underlying_for_bench> { using type = T; }; diff --git a/test/benchmarks/benchmark_unsigned_operations.cpp b/test/benchmarks/benchmark_unsigned_operations.cpp index b366c0b..21958a5 100644 --- a/test/benchmarks/benchmark_unsigned_operations.cpp +++ b/test/benchmarks/benchmark_unsigned_operations.cpp @@ -79,8 +79,8 @@ using namespace boost::safe_numbers; template struct underlying_for_bench { using type = T; }; -template -struct underlying_for_bench> { using type = T; }; +template +struct underlying_for_bench> { using type = T; }; template struct underlying_for_bench> { using type = T; }; diff --git a/test/compile_fail_handler_bad_signature.cpp b/test/compile_fail_handler_bad_signature.cpp new file mode 100644 index 0000000..f2926f7 --- /dev/null +++ b/test/compile_fail_handler_bad_signature.cpp @@ -0,0 +1,34 @@ +// Copyright 2026 Matt Borland +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt + +// A user defined handler must provide +// on_error(error_kind, BasisType, const char*) returning BasisType + +#ifdef BOOST_SAFE_NUMBERS_BUILD_MODULE + +import boost.safe_numbers; + +#else + +#include + +#endif + +using namespace boost::safe_numbers; + +struct bad_signature +{ + template + constexpr auto on_error(const T value) const noexcept -> T + { + return value; + } +}; + +int main() +{ + const basic_u8 bad {1U}; + + return static_cast(static_cast(bad)); +} diff --git a/test/compile_fail_handler_mixed_policy.cpp b/test/compile_fail_handler_mixed_policy.cpp new file mode 100644 index 0000000..939119e --- /dev/null +++ b/test/compile_fail_handler_mixed_policy.cpp @@ -0,0 +1,34 @@ +// Copyright 2026 Matt Borland +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt + +// Handler-typed values do not mix with other policies, exactly like the +// built-in policies do not mix with each other + +#ifdef BOOST_SAFE_NUMBERS_BUILD_MODULE + +import boost.safe_numbers; + +#else + +#include + +#endif + +using namespace boost::safe_numbers; + +struct wrap_handler +{ + template + constexpr auto on_error(const error_kind, const T value, const char*) const noexcept -> T + { + return value; + } +}; + +int main() +{ + const auto res {basic_u8{1U} + sat_u8{1U}}; + + return static_cast(static_cast(res)); +} diff --git a/test/compile_fail_handler_stateful.cpp b/test/compile_fail_handler_stateful.cpp new file mode 100644 index 0000000..f33ac03 --- /dev/null +++ b/test/compile_fail_handler_stateful.cpp @@ -0,0 +1,36 @@ +// Copyright 2026 Matt Borland +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt + +// A user defined handler must be stateless: the policy is part of the type, +// so per-value state would silently be lost + +#ifdef BOOST_SAFE_NUMBERS_BUILD_MODULE + +import boost.safe_numbers; + +#else + +#include + +#endif + +using namespace boost::safe_numbers; + +struct stateful_handler +{ + int error_count {0}; + + template + constexpr auto on_error(const error_kind, const T value, const char*) const noexcept -> T + { + return value; + } +}; + +int main() +{ + const basic_u8 bad {1U}; + + return static_cast(static_cast(bad)); +} diff --git a/test/compile_fail_handler_tag_nttp.cpp b/test/compile_fail_handler_tag_nttp.cpp new file mode 100644 index 0000000..ed0992e --- /dev/null +++ b/test/compile_fail_handler_tag_nttp.cpp @@ -0,0 +1,26 @@ +// Copyright 2026 Matt Borland +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt + +// The tag types select built-in policies through the basic_* alias templates +// only; they are not handlers and can not be used as the basis NTTP directly + +#ifdef BOOST_SAFE_NUMBERS_BUILD_MODULE + +import boost.safe_numbers; + +#else + +#include +#include + +#endif + +using namespace boost::safe_numbers; + +int main() +{ + const detail::unsigned_integer_basis bad {1U}; + + return static_cast(static_cast(bad)); +} diff --git a/test/compile_fail_mixed_policy_compare.cpp b/test/compile_fail_mixed_policy_compare.cpp new file mode 100644 index 0000000..0d57222 --- /dev/null +++ b/test/compile_fail_mixed_policy_compare.cpp @@ -0,0 +1,25 @@ +// Copyright 2026 Matt Borland +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt + +// Comparison between the same width with different overflow policies must not compile + +#ifdef BOOST_SAFE_NUMBERS_BUILD_MODULE + +import boost.safe_numbers; + +#else + +#include + +#endif + +using namespace boost::safe_numbers; + +int main() +{ + const sat_u8 lhs {1U}; + const u8 rhs {1U}; + + return (lhs == rhs) ? 0 : 1; +} diff --git a/test/compile_fail_mixed_policy_ops.cpp b/test/compile_fail_mixed_policy_ops.cpp new file mode 100644 index 0000000..e2428ad --- /dev/null +++ b/test/compile_fail_mixed_policy_ops.cpp @@ -0,0 +1,28 @@ +// Copyright 2026 Matt Borland +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt + +// Arithmetic between the same width with different overflow policies must not compile + +#ifdef BOOST_SAFE_NUMBERS_BUILD_MODULE + +import boost.safe_numbers; + +#else + +#include +#include + +#endif + +using namespace boost::safe_numbers; + +int main() +{ + const sat_u8 lhs {1U}; + const u8 rhs {1U}; + + const auto res {lhs + rhs}; + + return static_cast(static_cast(res)); +} diff --git a/test/compile_fail_policy_checked_type.cpp b/test/compile_fail_policy_checked_type.cpp new file mode 100644 index 0000000..8586215 --- /dev/null +++ b/test/compile_fail_policy_checked_type.cpp @@ -0,0 +1,25 @@ +// Copyright 2026 Matt Borland +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt + +// checked returns std::optional so it can not be a type-level policy + +#ifdef BOOST_SAFE_NUMBERS_BUILD_MODULE + +import boost.safe_numbers; + +#else + +#include +#include + +#endif + +using namespace boost::safe_numbers; + +int main() +{ + const detail::unsigned_integer_basis bad {1U}; + + return static_cast(static_cast(bad)); +} diff --git a/test/compile_fail_policy_overflow_tuple_type.cpp b/test/compile_fail_policy_overflow_tuple_type.cpp new file mode 100644 index 0000000..bb926b8 --- /dev/null +++ b/test/compile_fail_policy_overflow_tuple_type.cpp @@ -0,0 +1,25 @@ +// Copyright 2026 Matt Borland +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt + +// overflow_tuple returns std::pair so it can not be a type-level policy + +#ifdef BOOST_SAFE_NUMBERS_BUILD_MODULE + +import boost.safe_numbers; + +#else + +#include +#include + +#endif + +using namespace boost::safe_numbers; + +int main() +{ + const detail::signed_integer_basis bad {std::int8_t{1}}; + + return static_cast(static_cast(bad)); +} diff --git a/test/compile_fail_policy_widen_type.cpp b/test/compile_fail_policy_widen_type.cpp new file mode 100644 index 0000000..66e9769 --- /dev/null +++ b/test/compile_fail_policy_widen_type.cpp @@ -0,0 +1,25 @@ +// Copyright 2026 Matt Borland +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt + +// widen changes the result width so it can not be a type-level policy + +#ifdef BOOST_SAFE_NUMBERS_BUILD_MODULE + +import boost.safe_numbers; + +#else + +#include +#include + +#endif + +using namespace boost::safe_numbers; + +int main() +{ + const detail::unsigned_integer_basis bad {1U}; + + return static_cast(static_cast(bad)); +} diff --git a/test/compile_fail_policy_wrong_kind.cpp b/test/compile_fail_policy_wrong_kind.cpp new file mode 100644 index 0000000..fcc8b73 --- /dev/null +++ b/test/compile_fail_policy_wrong_kind.cpp @@ -0,0 +1,25 @@ +// Copyright 2026 Matt Borland +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt + +// The policy parameter must be an overflow_policy enumerator, not an arbitrary NTTP + +#ifdef BOOST_SAFE_NUMBERS_BUILD_MODULE + +import boost.safe_numbers; + +#else + +#include +#include + +#endif + +using namespace boost::safe_numbers; + +int main() +{ + const detail::unsigned_integer_basis bad {1U}; + + return static_cast(static_cast(bad)); +} diff --git a/test/compile_fail_strict_float_type.cpp b/test/compile_fail_strict_float_type.cpp new file mode 100644 index 0000000..465505f --- /dev/null +++ b/test/compile_fail_strict_float_type.cpp @@ -0,0 +1,25 @@ +// Copyright 2026 Matt Borland +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt + +// strict is an integer only policy: std::exit has no defined meaning for the +// IEEE 754 error taxonomy the float types use + +#ifdef BOOST_SAFE_NUMBERS_BUILD_MODULE + +import boost.safe_numbers; + +#else + +#include + +#endif + +using namespace boost::safe_numbers; + +int main() +{ + const detail::float_basis bad {1.0F}; + + return static_cast(static_cast(bad)); +} diff --git a/test/cuda_jamfile b/test/cuda_jamfile index f3a5973..73acf2b 100644 --- a/test/cuda_jamfile +++ b/test/cuda_jamfile @@ -35,6 +35,9 @@ run test_cuda_u32_mul.cu ; run test_cuda_u32_div.cu ; run test_cuda_u32_mod.cu ; +# Type-level policy smoke (saturating types on device) +run test_cuda_sat_type_add.cu ; + # u64 tests run test_cuda_u64_add.cu ; run test_cuda_u64_sub.cu ; diff --git a/test/sycl_jamfile b/test/sycl_jamfile index e3496f1..e6fe3be 100644 --- a/test/sycl_jamfile +++ b/test/sycl_jamfile @@ -19,6 +19,9 @@ run test_unsigned_mul_sycl.cpp ; run test_unsigned_div_sycl.cpp ; run test_unsigned_mod_sycl.cpp ; +# Type-level policy smoke (saturating types on device) +run test_sat_type_sycl.cpp ; + # Arithmetic - signed (i8, i16, i32, i64, i128) run test_signed_add_sycl.cpp ; run test_signed_sub_sycl.cpp ; diff --git a/test/test_cuda_sat_type_add.cu b/test/test_cuda_sat_type_add.cu new file mode 100644 index 0000000..9fd31d0 --- /dev/null +++ b/test/test_cuda_sat_type_add.cu @@ -0,0 +1,82 @@ +// Copyright Matt Borland 2026. +// Use, modification and distribution are subject to the +// Boost Software License, Version 1.0. (See accompanying file +// LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) + +#include +#include +#include +#include +#include +#include +#include +#include "cuda_managed_ptr.hpp" +#include "stopwatch.hpp" + +#include + +using test_type = boost::safe_numbers::sat_u32; +using basis_type = test_type::basis_type; + +__global__ void cuda_test(const test_type *in, test_type *out, int numElements) +{ + int i = blockDim.x * blockIdx.x + threadIdx.x; + + if (i < numElements) + { + out[i] = in[i] + in[i]; + } +} + +int main(void) +{ + std::mt19937_64 rng{42}; + + int numElements = 50000; + std::cout << "[Vector operation on " << numElements << " elements]" << std::endl; + + cuda_managed_ptr input_vector(numElements); + cuda_managed_ptr output_vector(numElements); + + // Full range so roughly half of the additions saturate on device + std::uniform_int_distribution dist{basis_type{0}, (std::numeric_limits::max)()}; + for (int i = 0; i < numElements; ++i) + { + input_vector[i] = test_type{dist(rng)}; + } + + int threadsPerBlock = 256; + int blocksPerGrid = (numElements + threadsPerBlock - 1) / threadsPerBlock; + std::cout << "CUDA kernel launch with " << blocksPerGrid << " blocks of " << threadsPerBlock << " threads" << std::endl; + + boost::safe_numbers::device_error_context ctx; + watch w; + + cuda_test<<>>(input_vector.get(), output_vector.get(), numElements); + ctx.synchronize(); + + std::cout << "CUDA kernel done in: " << w.elapsed() << "s" << std::endl; + + std::vector results; + results.reserve(numElements); + w.reset(); + for (int i = 0; i < numElements; ++i) + { + results.push_back(input_vector[i] + input_vector[i]); + } + double t = w.elapsed(); + + for (int i = 0; i < numElements; ++i) + { + if (output_vector[i] != results[i]) + { + std::cerr << "Result verification failed at element " << i << "!" << std::endl; + return EXIT_FAILURE; + } + } + + std::cout << "Test PASSED, normal calculation time: " << t << "s" << std::endl; + std::cout << "Done\n"; + + return 0; +} diff --git a/test/test_float_sat_type_arithmetic.cpp b/test/test_float_sat_type_arithmetic.cpp new file mode 100644 index 0000000..63cf8e6 --- /dev/null +++ b/test/test_float_sat_type_arithmetic.cpp @@ -0,0 +1,181 @@ +// Copyright 2026 Matt Borland +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt + +// Verifies that the saturating float policy is raw IEEE 754 arithmetic: +// bit for bit identical to the value component of the overflowing_* functions + +#include + +// The saturating results compared here are exact IEEE 754 values +#if defined(__clang__) +# pragma clang diagnostic push +# pragma clang diagnostic ignored "-Wfloat-equal" +#elif defined(__GNUC__) +# pragma GCC diagnostic push +# pragma GCC diagnostic ignored "-Wfloat-equal" +#elif defined(_MSC_VER) +# pragma warning (push) +# pragma warning (disable: 4723) // Potential divide by 0. The saturating policy is raw IEEE 754 +#endif + +#ifdef BOOST_SAFE_NUMBERS_BUILD_MODULE + +import boost.safe_numbers; + +#else + +#include +#include +#include +#include +#include +#include + +#endif + +using namespace boost::safe_numbers; + +// Excess precision on 32-bit x87 makes value comparison of float results +// unreliable, so compare the stored bit patterns instead +template +auto same_bits(const T lhs, const T rhs) noexcept -> bool +{ + if constexpr (sizeof(T) == sizeof(std::uint32_t)) + { + return std::bit_cast(lhs) == std::bit_cast(rhs); + } + else + { + return std::bit_cast(lhs) == std::bit_cast(rhs); + } +} + +template +void test_equivalence_with_overflowing() +{ + using basis_type = typename SatType::basis_type; + + constexpr basis_type interesting[] { + basis_type{0}, + basis_type{-0.0}, + basis_type{1.5}, + basis_type{-2.5}, + std::numeric_limits::max(), + std::numeric_limits::lowest(), + std::numeric_limits::min(), + std::numeric_limits::denorm_min(), + std::numeric_limits::infinity(), + -std::numeric_limits::infinity(), + std::numeric_limits::quiet_NaN(), + basis_type{3} * (std::numeric_limits::max() / basis_type{4}), + }; + + for (const auto a : interesting) + { + for (const auto b : interesting) + { + const SatType sa {a}; + const SatType sb {b}; + const DefaultType da {a}; + const DefaultType db {b}; + + BOOST_TEST(same_bits(static_cast(sa + sb), + static_cast(overflowing_add(da, db).first))); + BOOST_TEST(same_bits(static_cast(sa - sb), + static_cast(overflowing_sub(da, db).first))); + BOOST_TEST(same_bits(static_cast(sa * sb), + static_cast(overflowing_mul(da, db).first))); + BOOST_TEST(same_bits(static_cast(sa / sb), + static_cast(overflowing_div(da, db).first))); + } + } +} + +void test_ieee_semantics() +{ + constexpr auto fmax {std::numeric_limits::max()}; + + // Overflow saturates to infinity + const auto over {sat_f32{fmax} + sat_f32{fmax}}; + BOOST_TEST(std::isinf(static_cast(over))); + + // Division by zero yields infinity + const auto div_zero {sat_f32{1.0F} / sat_f32{0.0F}}; + BOOST_TEST(std::isinf(static_cast(div_zero))); + + // NaN propagates + const auto nan_res {sat_f32{std::numeric_limits::quiet_NaN()} + sat_f32{1.0F}}; + BOOST_TEST(std::isnan(static_cast(nan_res))); + + // inf - inf is NaN + constexpr auto inf {std::numeric_limits::infinity()}; + const auto invalid {sat_f32{inf} - sat_f32{inf}}; + BOOST_TEST(std::isnan(static_cast(invalid))); +} + +void test_comparisons() +{ + const sat_f32 one {1.0F}; + const sat_f32 two {2.0F}; + const sat_f32 nan_val {std::numeric_limits::quiet_NaN()}; + + BOOST_TEST(one < two); + BOOST_TEST(one == one); + BOOST_TEST((one <=> nan_val) == std::partial_ordering::unordered); + BOOST_TEST(!(nan_val == nan_val)); +} + +void test_constexpr_saturate() +{ + constexpr sat_f32 finite {sat_f32{1.5F} + sat_f32{2.0F}}; + static_assert(static_cast(finite) == 3.5F); + + constexpr sat_f64 product {sat_f64{3.0} * sat_f64{0.5}}; + static_assert(static_cast(product) == 1.5); + + // Overflow to infinity at constant evaluation is accepted by Clang but + // rejected by GCC, so it is exercised at runtime in test_ieee_semantics only + + BOOST_TEST(static_cast(finite) == 3.5F); +} + +void test_noexcept_matrix() +{ + const sat_f32 s {1.0F}; + const f32 d {1.0F}; + + static_assert(noexcept(s + s)); + static_assert(noexcept(s - s)); + static_assert(noexcept(s * s)); + static_assert(noexcept(s / s)); + + static_assert(!noexcept(d + d)); + static_assert(!noexcept(d - d)); + static_assert(!noexcept(d * d)); + static_assert(!noexcept(d / d)); + + static_cast(s); + static_cast(d); +} + +int main() +{ + test_equivalence_with_overflowing(); + test_equivalence_with_overflowing(); + + test_ieee_semantics(); + test_comparisons(); + test_constexpr_saturate(); + test_noexcept_matrix(); + + return boost::report_errors(); +} + +#if defined(__clang__) +# pragma clang diagnostic pop +#elif defined(__GNUC__) +# pragma GCC diagnostic pop +#elif defined(_MSC_VER) +# pragma warning (pop) +#endif diff --git a/test/test_policy_type_traits.cpp b/test/test_policy_type_traits.cpp new file mode 100644 index 0000000..89a45ff --- /dev/null +++ b/test/test_policy_type_traits.cpp @@ -0,0 +1,214 @@ +// Copyright 2026 Matt Borland +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt + +// Verifies that the policy-typed aliases integrate with the trait machinery, +// numeric_limits, hashing, streaming, the policy family free functions, +// and the generic policy-parameterized functions + +#include + +#ifdef BOOST_SAFE_NUMBERS_BUILD_MODULE + +import boost.safe_numbers; + +#else + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#endif + +using namespace boost::safe_numbers; + +void test_traits() +{ + static_assert(detail::is_unsigned_library_type_v); + static_assert(detail::is_unsigned_library_type_v); + static_assert(detail::is_signed_library_type_v); + static_assert(detail::is_signed_library_type_v); + static_assert(detail::is_float_library_type_v); + static_assert(detail::is_float_library_type_v); + + static_assert(detail::is_library_type_v); + static_assert(detail::is_library_type_v); + static_assert(detail::is_integral_library_type_v); + static_assert(!detail::is_integral_library_type_v); + + static_assert(std::is_same_v, std::uint32_t>); + static_assert(std::is_same_v, std::int64_t>); + static_assert(std::is_same_v, double>); + + static_assert(std::is_same_v); + + // The type-level policy is queryable + static_assert(u8::error_policy == overflow_policy::throw_exception); + static_assert(sat_u8::error_policy == overflow_policy::saturate); + static_assert(strict_u8::error_policy == overflow_policy::strict); + static_assert(sat_f32::error_policy == overflow_policy::saturate); + + // Layout is unchanged by the policy parameter + static_assert(sizeof(sat_u8) == sizeof(std::uint8_t)); + static_assert(sizeof(sat_i64) == sizeof(std::int64_t)); + static_assert(std::is_trivially_copyable_v); + static_assert(std::is_trivially_copyable_v); + static_assert(std::has_unique_object_representations_v); + + BOOST_TEST(true); +} + +void test_numeric_limits() +{ + static_assert(std::numeric_limits::is_specialized); + static_assert(std::numeric_limits::is_specialized); + static_assert(std::numeric_limits::is_specialized); + + BOOST_TEST_EQ(static_cast(std::numeric_limits::max()), + static_cast(std::numeric_limits::max())); + BOOST_TEST_EQ(static_cast(std::numeric_limits::min()), + static_cast(std::numeric_limits::min())); + + static_assert(std::numeric_limits::digits == std::numeric_limits::digits); + static_assert(std::numeric_limits::is_signed); + static_assert(std::numeric_limits::has_infinity); + static_assert(!std::numeric_limits::is_signed); +} + +void test_hash_and_stream() +{ + const auto h1 {std::hash{}(sat_u8{42U})}; + const auto h2 {std::hash{}(u8{42U})}; + BOOST_TEST_EQ(h1, h2); + + std::ostringstream os; + os << sat_u16{1000U} << " " << strict_i16{std::int16_t{-5}} << " " << sat_f32{1.5F}; + BOOST_TEST_EQ(os.str(), "1000 -5 1.5"); + + std::istringstream is {"77"}; + sat_u8 parsed {}; + is >> parsed; + BOOST_TEST_EQ(static_cast(parsed), 77U); +} + +// The named families accept policy-typed operands and preserve the policy +void test_free_functions_on_policy_types() +{ + const sat_u8 sa {200U}; + const sat_u8 sb {100U}; + + const auto sat_res {saturating_add(sa, sb)}; + static_assert(std::is_same_v); + BOOST_TEST_EQ(static_cast(sat_res), 255U); + + const auto ovf {overflowing_add(sa, sb)}; + static_assert(std::is_same_v>); + BOOST_TEST(ovf.second); + BOOST_TEST_EQ(static_cast(ovf.first), static_cast(44U)); + + const auto chk {checked_add(sa, sb)}; + static_assert(std::is_same_v>); + BOOST_TEST(!chk.has_value()); + + const strict_u8 ta {10U}; + const strict_u8 tb {20U}; + const auto chk2 {checked_add(ta, tb)}; + static_assert(std::is_same_v>); + BOOST_TEST(chk2.has_value()); + + // Widening preserves the operand policy in the wider result + const auto wide {widening_add(sa, sb)}; + static_assert(std::is_same_v>); + BOOST_TEST_EQ(static_cast(wide), 300U); + + const auto fovf {overflowing_add(sat_f32{1.0F}, sat_f32{2.0F})}; + static_assert(std::is_same_v>); + BOOST_TEST(!fovf.second); +} + +// A generic call requesting throw_exception must throw even when the operand +// type itself carries a non-throwing policy +void test_generic_functions_on_policy_types() +{ + const sat_u8 sa {255U}; + const sat_u8 sb {1U}; + + bool caught {false}; + try + { + const auto res {add(sa, sb)}; + static_cast(res); + } + catch (const std::overflow_error&) + { + caught = true; + } + BOOST_TEST(caught); + + const auto sat_res {add(sa, sb)}; + BOOST_TEST_EQ(static_cast(sat_res), 255U); + + const auto tup {add(sa, sb)}; + BOOST_TEST(tup.second); + + // Same check for the throwing default types, which keep throwing + caught = false; + try + { + const auto res {add(u8{255U}, u8{1U})}; + static_cast(res); + } + catch (const std::overflow_error&) + { + caught = true; + } + BOOST_TEST(caught); +} + +// Strict types behave like the default on the success path +void test_strict_success_paths() +{ + const strict_u8 a {10U}; + const strict_u8 b {20U}; + BOOST_TEST_EQ(static_cast(a + b), 30U); + BOOST_TEST_EQ(static_cast(b - a), 10U); + BOOST_TEST_EQ(static_cast(a * b), 200U); + BOOST_TEST_EQ(static_cast(b / a), 2U); + BOOST_TEST_EQ(static_cast(b % a), 0U); + + const strict_i8 c {std::int8_t{-5}}; + BOOST_TEST_EQ(static_cast(-c), std::int8_t{5}); + + static_assert(noexcept(a + b)); + static_assert(noexcept(b / a)); + static_assert(strict_u8::error_policy == overflow_policy::strict); +} + +// Comparison operators work within one policy +void test_comparisons() +{ + BOOST_TEST(sat_u8{1U} < sat_u8{2U}); + BOOST_TEST(sat_u8{2U} == sat_u8{2U}); + BOOST_TEST(strict_i8{std::int8_t{-1}} < strict_i8{std::int8_t{1}}); + static_assert(noexcept(sat_u8{1U} == sat_u8{2U})); +} + +int main() +{ + test_traits(); + test_numeric_limits(); + test_hash_and_stream(); + test_free_functions_on_policy_types(); + test_generic_functions_on_policy_types(); + test_strict_success_paths(); + test_comparisons(); + + return boost::report_errors(); +} diff --git a/test/test_sat_type_sycl.cpp b/test/test_sat_type_sycl.cpp new file mode 100644 index 0000000..3ead458 --- /dev/null +++ b/test/test_sat_type_sycl.cpp @@ -0,0 +1,31 @@ +// Copyright 2026 Matt Borland +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt + +// Smoke test for the saturating type-level policy on SYCL devices: full-range +// operands so a large fraction of the additions actually saturate on device + +#include "sycl_test.hpp" + +int main() +{ + using boost::safe_numbers::sat_u32; + using boost::safe_numbers::sat_i32; + using boost::safe_numbers::sat_f32; + + int rc {EXIT_SUCCESS}; + + rc |= sn_sycl_test::run_binary("sat_u32 add", &sn_sycl_test::draw_full, + [](auto a, auto b) { return a + b; }); + rc |= sn_sycl_test::run_binary("sat_u32 mul", &sn_sycl_test::draw_full, + [](auto a, auto b) { return a * b; }); + rc |= sn_sycl_test::run_binary("sat_i32 add", &sn_sycl_test::draw_full, + [](auto a, auto b) { return a + b; }); + rc |= sn_sycl_test::run_binary("sat_i32 sub", &sn_sycl_test::draw_full, + [](auto a, auto b) { return a - b; }); + rc |= sn_sycl_test::run_binary("sat_f32 add", + [](std::mt19937_64& rng) { return sn_sycl_test::draw_real(rng, -1.0e30, 1.0e30); }, + [](auto a, auto b) { return a + b; }); + + return rc; +} diff --git a/test/test_signed_sat_type_arithmetic.cpp b/test/test_signed_sat_type_arithmetic.cpp new file mode 100644 index 0000000..24d53fc --- /dev/null +++ b/test/test_signed_sat_type_arithmetic.cpp @@ -0,0 +1,200 @@ +// Copyright 2026 Matt Borland +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt + +// Verifies that the saturating type-level policy behaves exactly like the +// saturating_* free functions on the default (throwing) signed types + +#include + +#ifdef BOOST_SAFE_NUMBERS_BUILD_MODULE + +import boost.safe_numbers; + +#else + +#include +#include +#include +#include +#include + +#endif + +using namespace boost::safe_numbers; + +void test_exhaustive_i8() +{ + for (std::int32_t i {-128}; i <= 127; ++i) + { + for (std::int32_t j {-128}; j <= 127; ++j) + { + const auto a {static_cast(i)}; + const auto b {static_cast(j)}; + + const sat_i8 sa {a}; + const sat_i8 sb {b}; + const i8 ia {a}; + const i8 ib {b}; + + BOOST_TEST_EQ(static_cast(sa + sb), static_cast(saturating_add(ia, ib))); + BOOST_TEST_EQ(static_cast(sa - sb), static_cast(saturating_sub(ia, ib))); + BOOST_TEST_EQ(static_cast(sa * sb), static_cast(saturating_mul(ia, ib))); + + if (b != 0) + { + BOOST_TEST_EQ(static_cast(sa / sb), static_cast(saturating_div(ia, ib))); + BOOST_TEST_EQ(static_cast(sa % sb), static_cast(saturating_mod(ia, ib))); + } + } + } +} + +template +void test_spot_checks() +{ + using basis_type = typename SatType::basis_type; + + constexpr auto max_val {std::numeric_limits::max()}; + constexpr auto min_val {std::numeric_limits::min()}; + + const SatType smax {max_val}; + const SatType smin {min_val}; + const SatType stwo {basis_type{2}}; + const DefaultType dmax {max_val}; + const DefaultType dmin {min_val}; + const DefaultType dtwo {basis_type{2}}; + + BOOST_TEST(static_cast(smax + stwo) == static_cast(saturating_add(dmax, dtwo))); + BOOST_TEST(static_cast(smin - stwo) == static_cast(saturating_sub(dmin, dtwo))); + BOOST_TEST(static_cast(smax * stwo) == static_cast(saturating_mul(dmax, dtwo))); + + // MIN / -1 saturates to max instead of throwing + const SatType sneg_one {basis_type{-1}}; + const DefaultType dneg_one {basis_type{-1}}; + BOOST_TEST(static_cast(smin / sneg_one) == static_cast(saturating_div(dmin, dneg_one))); + BOOST_TEST(static_cast(smin / sneg_one) == max_val); + BOOST_TEST(static_cast(smin % sneg_one) == static_cast(saturating_mod(dmin, dneg_one))); +} + +// Negating the minimum saturates to max, matching saturating_sub(0, min) +void test_unary_minus() +{ + const sat_i8 smin {std::int8_t{-128}}; + BOOST_TEST_EQ(static_cast(-smin), std::int8_t{127}); + BOOST_TEST_EQ(static_cast(-smin), + static_cast(saturating_sub(i8{std::int8_t{0}}, i8{std::int8_t{-128}}))); + + const sat_i8 pos {std::int8_t{5}}; + BOOST_TEST_EQ(static_cast(-pos), std::int8_t{-5}); + BOOST_TEST_EQ(static_cast(+pos), std::int8_t{5}); + + static_assert(noexcept(-sat_i8{std::int8_t{1}})); + static_assert(!noexcept(-i8{std::int8_t{1}})); +} + +void test_increment_decrement_bounds() +{ + sat_i8 at_max {std::int8_t{127}}; + ++at_max; + BOOST_TEST_EQ(static_cast(at_max), std::int8_t{127}); + + const auto old_max {at_max++}; + BOOST_TEST_EQ(static_cast(old_max), std::int8_t{127}); + BOOST_TEST_EQ(static_cast(at_max), std::int8_t{127}); + + sat_i8 at_min {std::int8_t{-128}}; + --at_min; + BOOST_TEST_EQ(static_cast(at_min), std::int8_t{-128}); + + const auto old_min {at_min--}; + BOOST_TEST_EQ(static_cast(old_min), std::int8_t{-128}); + BOOST_TEST_EQ(static_cast(at_min), std::int8_t{-128}); +} + +void test_divide_by_zero_still_throws() +{ + const sat_i8 val {std::int8_t{1}}; + const sat_i8 zero {std::int8_t{0}}; + + bool caught {false}; + try + { + const auto res {val / zero}; + static_cast(res); + } + catch (const std::domain_error&) + { + caught = true; + } + BOOST_TEST(caught); + + caught = false; + try + { + const auto res {val % zero}; + static_cast(res); + } + catch (const std::domain_error&) + { + caught = true; + } + BOOST_TEST(caught); +} + +void test_constexpr_saturation() +{ + constexpr sat_i8 over {sat_i8{std::int8_t{127}} + sat_i8{std::int8_t{1}}}; + static_assert(static_cast(over) == std::int8_t{127}); + + constexpr sat_i8 under {sat_i8{std::int8_t{-128}} - sat_i8{std::int8_t{1}}}; + static_assert(static_cast(under) == std::int8_t{-128}); + + constexpr sat_i8 mul_under {sat_i8{std::int8_t{-128}} * sat_i8{std::int8_t{2}}}; + static_assert(static_cast(mul_under) == std::int8_t{-128}); + + constexpr i8 free_over {saturating_add(i8{std::int8_t{127}}, i8{std::int8_t{1}})}; + static_assert(static_cast(free_over) == std::int8_t{127}); + + BOOST_TEST(static_cast(over) == std::int8_t{127}); +} + +void test_noexcept_matrix() +{ + const sat_i8 s {std::int8_t{1}}; + const i8 d {std::int8_t{1}}; + + static_assert(noexcept(s + s)); + static_assert(noexcept(s - s)); + static_assert(noexcept(s * s)); + static_assert(!noexcept(s / s)); + static_assert(!noexcept(s % s)); + static_assert(noexcept(++sat_i8{std::int8_t{0}})); + static_assert(noexcept(--sat_i8{std::int8_t{0}})); + + static_assert(!noexcept(d + d)); + static_assert(!noexcept(d - d)); + static_assert(!noexcept(d * d)); + static_assert(!noexcept(d / d)); + + static_cast(s); + static_cast(d); +} + +int main() +{ + test_exhaustive_i8(); + + test_spot_checks(); + test_spot_checks(); + test_spot_checks(); + test_spot_checks(); + + test_unary_minus(); + test_increment_decrement_bounds(); + test_divide_by_zero_still_throws(); + test_constexpr_saturation(); + test_noexcept_matrix(); + + return boost::report_errors(); +} diff --git a/test/test_signed_strict_type_addition.cpp b/test/test_signed_strict_type_addition.cpp new file mode 100644 index 0000000..db285d1 --- /dev/null +++ b/test/test_signed_strict_type_addition.cpp @@ -0,0 +1,32 @@ +// Copyright 2026 Matt Borland +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt + +// This test verifies that strict_i32 addition 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 +#include + +#endif + +using namespace boost::safe_numbers; + +int main() +{ + const strict_i32 max_val {std::numeric_limits::max()}; + const strict_i32 one {1}; + + // This should call std::exit(EXIT_FAILURE) + const auto result {max_val + one}; + + // Should never reach here + static_cast(result); + return 0; // LCOV_EXCL_LINE +} diff --git a/test/test_signed_strict_type_division.cpp b/test/test_signed_strict_type_division.cpp new file mode 100644 index 0000000..992261c --- /dev/null +++ b/test/test_signed_strict_type_division.cpp @@ -0,0 +1,31 @@ +// Copyright 2026 Matt Borland +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt + +// This test verifies that strict_i32 division by zero calls std::exit(EXIT_FAILURE) +// It is marked as run-fail in the Jamfile + +#ifdef BOOST_SAFE_NUMBERS_BUILD_MODULE + +import boost.safe_numbers; + +#else + +#include + +#endif + +using namespace boost::safe_numbers; + +int main() +{ + const strict_i32 val {1}; + const strict_i32 zero {0}; + + // This should call std::exit(EXIT_FAILURE) + const auto result {val / zero}; + + // Should never reach here + static_cast(result); + return 0; // LCOV_EXCL_LINE +} diff --git a/test/test_unsigned_sat_type_arithmetic.cpp b/test/test_unsigned_sat_type_arithmetic.cpp new file mode 100644 index 0000000..6887d58 --- /dev/null +++ b/test/test_unsigned_sat_type_arithmetic.cpp @@ -0,0 +1,234 @@ +// Copyright 2026 Matt Borland +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt + +// Verifies that the saturating type-level policy behaves exactly like the +// saturating_* free functions on the default (throwing) types + +#include + +#ifdef BOOST_SAFE_NUMBERS_BUILD_MODULE + +import boost.safe_numbers; + +#else + +#include +#include +#include +#include +#include + +#endif + +using namespace boost::safe_numbers; + +// Every operator result must match the equivalent saturating free function +// applied to the default policy type carrying the same values +void test_exhaustive_u8() +{ + for (std::uint32_t i {0U}; i <= 255U; ++i) + { + for (std::uint32_t j {0U}; j <= 255U; ++j) + { + const auto a {static_cast(i)}; + const auto b {static_cast(j)}; + + const sat_u8 sa {a}; + const sat_u8 sb {b}; + const u8 ua {a}; + const u8 ub {b}; + + BOOST_TEST_EQ(static_cast(sa + sb), static_cast(saturating_add(ua, ub))); + BOOST_TEST_EQ(static_cast(sa - sb), static_cast(saturating_sub(ua, ub))); + BOOST_TEST_EQ(static_cast(sa * sb), static_cast(saturating_mul(ua, ub))); + + if (b != 0U) + { + BOOST_TEST_EQ(static_cast(sa / sb), static_cast(saturating_div(ua, ub))); + BOOST_TEST_EQ(static_cast(sa % sb), static_cast(saturating_mod(ua, ub))); + } + } + } +} + +void test_shifts_u8() +{ + for (std::uint32_t i {0U}; i <= 255U; ++i) + { + for (std::uint32_t j {0U}; j <= 9U; ++j) + { + const auto a {static_cast(i)}; + const auto b {static_cast(j)}; + + const sat_u8 sa {a}; + const sat_u8 sb {b}; + const u8 ua {a}; + const u8 ub {b}; + + BOOST_TEST_EQ(static_cast(sa << sb), static_cast(saturating_shl(ua, ub))); + BOOST_TEST_EQ(static_cast(sa >> sb), static_cast(saturating_shr(ua, ub))); + } + } +} + +template +void test_spot_checks() +{ + using basis_type = typename SatType::basis_type; + + constexpr auto max_val {std::numeric_limits::max()}; + + const SatType sa {max_val}; + const SatType sb {basis_type{2U}}; + const DefaultType ua {max_val}; + const DefaultType ub {basis_type{2U}}; + + BOOST_TEST(static_cast(sa + sb) == static_cast(saturating_add(ua, ub))); + BOOST_TEST(static_cast(sb - sa) == static_cast(saturating_sub(ub, ua))); + BOOST_TEST(static_cast(sa * sb) == static_cast(saturating_mul(ua, ub))); + BOOST_TEST(static_cast(sa / sb) == static_cast(saturating_div(ua, ub))); + BOOST_TEST(static_cast(sa % sb) == static_cast(saturating_mod(ua, ub))); +} + +void test_increment_decrement_bounds() +{ + sat_u8 at_max {255U}; + ++at_max; + BOOST_TEST_EQ(static_cast(at_max), 255U); + + const auto old_max {at_max++}; + BOOST_TEST_EQ(static_cast(old_max), 255U); + BOOST_TEST_EQ(static_cast(at_max), 255U); + + sat_u8 at_min {0U}; + --at_min; + BOOST_TEST_EQ(static_cast(at_min), 0U); + + const auto old_min {at_min--}; + BOOST_TEST_EQ(static_cast(old_min), 0U); + BOOST_TEST_EQ(static_cast(at_min), 0U); +} + +void test_compound_assignment() +{ + sat_u8 val {250U}; + val += sat_u8{10U}; + BOOST_TEST_EQ(static_cast(val), 255U); + + val -= sat_u8{5U}; + BOOST_TEST_EQ(static_cast(val), 250U); + + val *= sat_u8{2U}; + BOOST_TEST_EQ(static_cast(val), 255U); + + val /= sat_u8{5U}; + BOOST_TEST_EQ(static_cast(val), 51U); + + val %= sat_u8{10U}; + BOOST_TEST_EQ(static_cast(val), 1U); + + val <<= sat_u8{200U}; + BOOST_TEST_EQ(static_cast(val), 255U); + + val >>= sat_u8{200U}; + BOOST_TEST_EQ(static_cast(val), 0U); +} + +// Division and modulo by zero still throw under the saturating policy, +// exactly like saturating_div and saturating_mod +void test_divide_by_zero_still_throws() +{ + const sat_u8 val {1U}; + const sat_u8 zero {0U}; + + bool caught {false}; + try + { + const auto res {val / zero}; + static_cast(res); + } + catch (const std::domain_error&) + { + caught = true; + } + BOOST_TEST(caught); + + caught = false; + try + { + const auto res {val % zero}; + static_cast(res); + } + catch (const std::domain_error&) + { + caught = true; + } + BOOST_TEST(caught); +} + +// Saturating overflow is a defined value, so it must be usable at constant evaluation +void test_constexpr_saturation() +{ + constexpr sat_u8 over {sat_u8{255U} + sat_u8{1U}}; + static_assert(static_cast(over) == 255U); + + constexpr sat_u8 under {sat_u8{0U} - sat_u8{1U}}; + static_assert(static_cast(under) == 0U); + + constexpr sat_u8 mul_over {sat_u8{16U} * sat_u8{16U}}; + static_assert(static_cast(mul_over) == 255U); + + constexpr sat_u8 shl_over {sat_u8{1U} << sat_u8{8U}}; + static_assert(static_cast(shl_over) == 255U); + + // The generalized free functions saturate at constant evaluation too + constexpr u8 free_over {saturating_add(u8{255U}, u8{1U})}; + static_assert(static_cast(free_over) == 255U); + + BOOST_TEST(static_cast(over) == 255U); +} + +void test_noexcept_matrix() +{ + const sat_u8 s {1U}; + const u8 d {1U}; + + static_assert(noexcept(s + s)); + static_assert(noexcept(s - s)); + static_assert(noexcept(s * s)); + static_assert(!noexcept(s / s)); + static_assert(!noexcept(s % s)); + static_assert(noexcept(s << s)); + static_assert(noexcept(s >> s)); + static_assert(noexcept(++sat_u8{0U})); + static_assert(noexcept(--sat_u8{1U})); + + static_assert(!noexcept(d + d)); + static_assert(!noexcept(d - d)); + static_assert(!noexcept(d * d)); + static_assert(!noexcept(d / d)); + static_assert(!noexcept(d << d)); + + static_cast(s); + static_cast(d); +} + +int main() +{ + test_exhaustive_u8(); + test_shifts_u8(); + + test_spot_checks(); + test_spot_checks(); + test_spot_checks(); + test_spot_checks(); + + test_increment_decrement_bounds(); + test_compound_assignment(); + test_divide_by_zero_still_throws(); + test_constexpr_saturation(); + test_noexcept_matrix(); + + return boost::report_errors(); +} diff --git a/test/test_unsigned_strict_type_addition.cpp b/test/test_unsigned_strict_type_addition.cpp new file mode 100644 index 0000000..3a004c0 --- /dev/null +++ b/test/test_unsigned_strict_type_addition.cpp @@ -0,0 +1,32 @@ +// Copyright 2026 Matt Borland +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt + +// This test verifies that strict_u32 addition 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 +#include + +#endif + +using namespace boost::safe_numbers; + +int main() +{ + const strict_u32 max_val {std::numeric_limits::max()}; + const strict_u32 one {1U}; + + // This should call std::exit(EXIT_FAILURE) + const auto result {max_val + one}; + + // Should never reach here + static_cast(result); + return 0; // LCOV_EXCL_LINE +} diff --git a/test/test_unsigned_strict_type_division.cpp b/test/test_unsigned_strict_type_division.cpp new file mode 100644 index 0000000..a35084c --- /dev/null +++ b/test/test_unsigned_strict_type_division.cpp @@ -0,0 +1,31 @@ +// Copyright 2026 Matt Borland +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt + +// This test verifies that strict_u32 division by zero calls std::exit(EXIT_FAILURE) +// It is marked as run-fail in the Jamfile + +#ifdef BOOST_SAFE_NUMBERS_BUILD_MODULE + +import boost.safe_numbers; + +#else + +#include + +#endif + +using namespace boost::safe_numbers; + +int main() +{ + const strict_u32 val {1U}; + const strict_u32 zero {0U}; + + // This should call std::exit(EXIT_FAILURE) + const auto result {val / zero}; + + // Should never reach here + static_cast(result); + return 0; // LCOV_EXCL_LINE +} diff --git a/test/test_user_defined_handler.cpp b/test/test_user_defined_handler.cpp new file mode 100644 index 0000000..cf391a3 --- /dev/null +++ b/test/test_user_defined_handler.cpp @@ -0,0 +1,302 @@ +// Copyright 2026 Matt Borland +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt + +// Verifies user defined error handlers passed through the basic_* alias +// templates: dispatch for every error kind, value semantics, noexcept +// propagation, constexpr usability, and identity of the built-in tags + +// The float checks compare exact sentinel values (0.0F) produced by the +// handler, so the equality is intentional +#ifdef __clang__ +# pragma clang diagnostic push +# pragma clang diagnostic ignored "-Wfloat-equal" +#elif defined(__GNUC__) +# pragma GCC diagnostic push +# pragma GCC diagnostic ignored "-Wfloat-equal" +#elif defined(_MSC_VER) +# pragma warning (push) +# pragma warning (disable: 4702) // Unreachable code. The throwing handler never returns a value +# pragma warning (disable: 4723) // Potential divide by 0. We need to test this +#endif + +#include + +#ifdef BOOST_SAFE_NUMBERS_BUILD_MODULE + +import boost.safe_numbers; + +#else + +#include +#include +#include +#include +#include +#include + +#endif + +using namespace boost::safe_numbers; + +// Returns the defined fallback value for every error: wrapping semantics for +// the integers and raw IEEE 754 results for the floats +struct wrapping_handler +{ + template + constexpr auto on_error(const error_kind, const T value, const char*) const noexcept -> T + { + return value; + } +}; + +// Clamps like the saturate policy, driven by the reported kind +struct clamping_handler +{ + template + constexpr auto on_error(const error_kind kind, const T value, const char*) const noexcept -> T + { + if (kind == error_kind::overflow) + { + return std::numeric_limits::max(); + } + if (kind == error_kind::underflow) + { + return std::numeric_limits::min(); + } + return value; + } +}; + +// Throws a user defined exception type carrying the library's message +struct my_error : std::runtime_error +{ + using std::runtime_error::runtime_error; +}; + +struct custom_throw_handler +{ + template + auto on_error(const error_kind, const T, const char* msg) const -> T + { + throw my_error{msg}; + } +}; + +// Replaces every exceptional float result with zero +struct zeroing_handler +{ + template + constexpr auto on_error(const error_kind, const T, const char*) const noexcept -> T + { + return T{0}; + } +}; + +using wrap_u8 = basic_u8; +using wrap_i8 = basic_i8; +using clamp_u8 = basic_u8; +using throw_u8 = basic_u8; + +void test_alias_identity() +{ + static_assert(std::is_same_v, u8>); + static_assert(std::is_same_v, u8>); + static_assert(std::is_same_v, sat_u8>); + static_assert(std::is_same_v, strict_u8>); + static_assert(std::is_same_v, sat_u128>); + static_assert(std::is_same_v, strict_i64>); + static_assert(std::is_same_v, f32>); + static_assert(std::is_same_v, sat_f64>); + + static_assert(std::is_same_v, wrapping_handler>); + static_assert(sizeof(wrap_u8) == sizeof(std::uint8_t)); + static_assert(std::is_trivially_copyable_v); + + BOOST_TEST(true); +} + +// The wrapping handler must match the value component of overflowing_* everywhere +void test_wrapping_equivalence_u8() +{ + for (std::uint32_t i {0U}; i <= 255U; ++i) + { + for (std::uint32_t j {0U}; j <= 255U; ++j) + { + const auto a {static_cast(i)}; + const auto b {static_cast(j)}; + + const wrap_u8 wa {a}; + const wrap_u8 wb {b}; + const u8 ua {a}; + const u8 ub {b}; + + BOOST_TEST_EQ(static_cast(wa + wb), static_cast(overflowing_add(ua, ub).first)); + BOOST_TEST_EQ(static_cast(wa - wb), static_cast(overflowing_sub(ua, ub).first)); + BOOST_TEST_EQ(static_cast(wa * wb), static_cast(overflowing_mul(ua, ub).first)); + + if (b != 0U) + { + BOOST_TEST_EQ(static_cast(wa / wb), static_cast(overflowing_div(ua, ub).first)); + BOOST_TEST_EQ(static_cast(wa % wb), static_cast(overflowing_mod(ua, ub).first)); + } + } + } +} + +// The clamping handler must match the saturate policy for add/sub/mul +void test_clamping_equivalence_u8() +{ + for (std::uint32_t i {0U}; i <= 255U; ++i) + { + for (std::uint32_t j {0U}; j <= 255U; ++j) + { + const auto a {static_cast(i)}; + const auto b {static_cast(j)}; + + const clamp_u8 ca {a}; + const clamp_u8 cb {b}; + const sat_u8 sa {a}; + const sat_u8 sb {b}; + + BOOST_TEST_EQ(static_cast(ca + cb), static_cast(sa + sb)); + BOOST_TEST_EQ(static_cast(ca - cb), static_cast(sa - sb)); + BOOST_TEST_EQ(static_cast(ca * cb), static_cast(sa * sb)); + } + } +} + +void test_signed_edge_cases() +{ + constexpr auto min_val {std::numeric_limits::min()}; + + // Two's complement wrapping at the boundaries + constexpr wrap_i8 over {wrap_i8{std::int8_t{127}} + wrap_i8{std::int8_t{1}}}; + static_assert(static_cast(over) == min_val); + + // MIN / -1 hands the handler the wrapped value (MIN) + constexpr wrap_i8 mdiv {wrap_i8{min_val} / wrap_i8{std::int8_t{-1}}}; + static_assert(static_cast(mdiv) == min_val); + + // MIN % -1 hands the handler the mathematical result (0) + constexpr wrap_i8 mmod {wrap_i8{min_val} % wrap_i8{std::int8_t{-1}}}; + static_assert(static_cast(mmod) == std::int8_t{0}); + + // Division by zero hands the handler the dividend + constexpr wrap_i8 dz {wrap_i8{std::int8_t{9}} / wrap_i8{std::int8_t{0}}}; + static_assert(static_cast(dz) == std::int8_t{9}); + + // Negating MIN hands the handler the wrapped value (MIN) + constexpr wrap_i8 neg {-wrap_i8{min_val}}; + static_assert(static_cast(neg) == min_val); + + BOOST_TEST(static_cast(over) == min_val); +} + +void test_increment_decrement_and_shifts() +{ + wrap_u8 inc {255U}; + ++inc; + BOOST_TEST_EQ(static_cast(inc), 0U); + + wrap_u8 dec {0U}; + const auto old_dec {dec--}; + BOOST_TEST_EQ(static_cast(old_dec), 0U); + BOOST_TEST_EQ(static_cast(dec), 255U); + + // Shift past the width hands the handler the mask-shifted value + constexpr wrap_u8 shl {wrap_u8{1U} << wrap_u8{9U}}; + static_assert(static_cast(shl) == 2U); + + BOOST_TEST(true); +} + +void test_custom_exception() +{ + bool caught {false}; + try + { + const auto res {throw_u8{255U} + throw_u8{1U}}; + static_cast(res); + } + catch (const my_error& e) + { + caught = true; + BOOST_TEST_CSTR_EQ(e.what(), "Overflow detected in u8 addition"); + } + BOOST_TEST(caught); + + static_assert(!noexcept(throw_u8{1U} + throw_u8{1U})); +} + +void test_float_handlers() +{ + using zero_f32 = basic_f32; + + const zero_f32 big {std::numeric_limits::max()}; + BOOST_TEST_EQ(static_cast(big + big), 0.0F); + BOOST_TEST_EQ(static_cast(zero_f32{1.0F} / zero_f32{0.0F}), 0.0F); + BOOST_TEST_EQ(static_cast(zero_f32{std::numeric_limits::quiet_NaN()} + zero_f32{1.0F}), 0.0F); + + // The wrapping handler on floats reproduces the raw IEEE result + using ieee_f32 = basic_f32; + const ieee_f32 ibig {std::numeric_limits::max()}; + BOOST_TEST(std::isinf(static_cast(ibig + ibig))); + + // Normal math never reaches the handler + constexpr zero_f32 fine {zero_f32{1.5F} + zero_f32{2.0F}}; + static_assert(static_cast(fine) == 3.5F); + + static_assert(noexcept(big + big)); +} + +void test_noexcept_and_free_functions() +{ + const wrap_u8 w {1U}; + + static_assert(noexcept(w + w)); + static_assert(noexcept(w / w)); + static_assert(noexcept(w << w)); + static_assert(noexcept(++wrap_u8{0U})); + + // The named families accept handler types and preserve them + const auto sat_res {saturating_add(wrap_u8{255U}, wrap_u8{1U})}; + static_assert(std::is_same_v); + BOOST_TEST_EQ(static_cast(sat_res), 255U); + + const auto ovf {overflowing_add(w, w)}; + static_assert(std::is_same_v); + + // A generic throw_exception call throws even on handler-typed operands + bool caught {false}; + try + { + const auto res {add(wrap_u8{255U}, wrap_u8{1U})}; + static_cast(res); + } + catch (const std::overflow_error&) + { + caught = true; + } + BOOST_TEST(caught); + + // Traits and numeric_limits admit handler types + static_assert(detail::is_unsigned_library_type_v); + static_assert(detail::is_library_type_v); + static_assert(std::numeric_limits::is_specialized); + static_assert(static_cast(std::numeric_limits::max()) == 255U); +} + +int main() +{ + test_alias_identity(); + test_wrapping_equivalence_u8(); + test_clamping_equivalence_u8(); + test_signed_edge_cases(); + test_increment_decrement_and_shifts(); + test_custom_exception(); + test_float_handlers(); + test_noexcept_and_free_functions(); + + return boost::report_errors(); +}