This module defines facilities for efficient checking of integral operations against overflow, casting with loss of precision, unexpected change of sign, etc. The checking (and possibly correction) can be done at operation level, for example opChecked!"+"(x, y, overflow) adds two integrals x and y and sets overflow to true if an overflow occurred. The flag overflow (a bool passed by reference) is not touched if the operation succeeded, so the same flag can be reused for a sequence of operations and tested at the end.

Issuing individual checked operations is flexible and efficient but often tedious. The Checked facility offers encapsulated integral wrappers that do all checking internally and have configurable behavior upon erroneous results. For example, Checked!int is a type that behaves like int but aborts execution immediately whenever involved in an operation that produces the arithmetically wrong result. The accompanying convenience function checked uses type deduction to convert a value x of integral type T to Checked!T by means of checked(x). For example:

void main()
    import std.checkedint, std.stdio;
    writeln((checked(5) + 7).get); // 12
    writeln((checked(10) * 1000 * 1000 * 1000).get); // Overflow

Similarly, checked(-1) > uint(0) aborts execution (even though the built-in comparison int(-1) > uint(0) is surprisingly true due to language's conversion rules modeled after C). Thus, Checked!int is a virtually drop-in replacement for int useable in debug builds, to be replaced by int in release mode if efficiency demands it.

Checked has customizable behavior with the help of a second type parameter, Hook. Depending on what methods Hook defines, core operations on the underlying integral may be verified for overflow or completely redefined. If Hook defines no method at all and carries no state, there is no change in behavior, i.e. Checked!(int, void) is a wrapper around int that adds no customization at all.

This module provides a few predefined hooks (below) that add useful behavior to Checked:

Abortfails every incorrect operation with a message to std.stdio.stderr followed by a call to assert(0). It is the default second parameter, i.e. Checked!short is the same as Checked!(short, Abort).
Throwfails every incorrect operation by throwing an exception.
Warnprints incorrect operations to std.stdio.stderr but otherwise preserves the built-in behavior.
ProperComparefixes the comparison operators ==, !=, <, <=, >, and >= to return correct results in all circumstances, at a slight cost in efficiency. For example, Checked!(uint, ProperCompare)(1) > -1 is true, which is not the case for the built-in comparison. Also, comparing numbers for equality with floating-point numbers only passes if the integral can be converted to the floating-point number precisely, so as to preserve transitivity of equality.
WithNaNreserves a special "Not a Number" (NaN) value akin to the homonym value reserved for floating-point values. Once a Checked!(X, WithNaN) gets this special value, it preserves and propagates it until reassigned. isNaN can be used to query whether the object is not a number.
Saturateimplements saturating arithmetic, i.e. Checked!(int, Saturate) "stops" at int.max for all operations that would cause an int to overflow toward infinity, and at int.min for all operations that would correspondingly overflow toward negative infinity.



Checked!(T, Hook) checked(T value)

Convenience function that turns an integral into the corresponding Checked instance by using template argument deduction. The hook type may be specified (by default Abort).

bool isNaN(Checked!(T, WithNaN) x)

Queries whether a Checked!(T, WithNaN) object is not a number (NaN).

typeof(mixin (x == "cmp" ? "0" : ("L() " ~ x ~ " R()"))) opChecked(L lhs, R rhs, bool overflow)

Defines binary operations with overflow checking for any two integral types. The result type obeys the language rules (even when they may be counterintuitive), and overflow is set if an overflow occurs (including inadvertent change of signedness, e.g. -1 is converted to uint). Conceptually the behavior is:


struct Abort

Force all integral errors to fail by printing an error message to stderr and then abort the program. Abort is the default second argument for Checked.

struct Checked(T, Hook = Abort)

Checked integral type wraps an integral T and customizes its behavior with the help of a Hook type. The type wrapped must be one of the predefined integrals (unqualified), or another instance of Checked.

struct ProperCompare

Hook that provides arithmetically correct comparisons for equality and ordering. Comparing an object of type Checked!(X, ProperCompare) against another integral (for equality or ordering) ensures that no surprising conversions from signed to unsigned integral occur before the comparison. Using Checked!(X, ProperCompare) on either side of a comparison for equality against a floating-point number makes sure the integral can be properly converted to the floating point type, thus making sure equality is transitive.

struct Saturate

Hook that implements saturation, i.e. any arithmetic operation that would overflow leaves the result at its extreme value (min or max depending on the direction of the overflow).

struct Throw

Force all integral errors to fail by throwing an exception of type Throw.CheckFailure. The message coming with the error is similar to the one printed by Warn.

struct Warn

Hook that prints to stderr a trace of all integral errors, without affecting default behavior.

struct WithNaN

Hook that reserves a special value as a "Not a Number" representative. For signed integrals, the reserved value is T.min. For signed integrals, the reserved value is T.max.

Detailed Description

These policies may be used alone, e.g. Checked!(uint, WithNaN) defines a uint-like type that reaches a stable NaN state for all erroneous operations. They may also be "stacked" on top of each other, owing to the property that a checked integral emulates an actual integral, which means another checked integral can be built on top of it. Some combinations of interest include:

Checked!(Checked!int, ProperCompare)
defines an int with fixed comparison operators that will fail with assert(0) upon overflow. (Recall that Abort is the default policy.) The order in which policies are combined is important because the outermost policy (ProperCompare in this case) has the first crack at intercepting an operator. The converse combination Checked!(Checked!(int, ProperCompare)) is meaningless because Abort will intercept comparison and will fail without giving ProperCompare a chance to intervene.
Checked!(Checked!(int, ProperCompare), WithNaN)
defines an int-like type that supports a NaN value. For values that are not NaN, comparison works properly. Again the composition order is important; Checked!(Checked!(int, WithNaN), ProperCompare) does not have good semantics because ProperCompare intercepts comparisons before the numbers involved are tested for NaN.

The hook's members are looked up statically in a Design by Introspection manner and are all optional. The table below illustrates the members that a hook type may define and their influence over the behavior of the Checked type using it. In the table, hook is an alias for Hook if the type Hook does not introduce any state, or an object of type Hook otherwise.

Hook memberSemantics in Checked!(T, Hook)
defaultValueIf defined, Hook.defaultValue!T is used as the default initializer of the payload.
minIf defined, Hook.min!T is used as the minimum value of the payload.
maxIf defined, Hook.max!T is used as the maximum value of the payload.
hookOpCastIf defined, hook.hookOpCast!U(get) is forwarded to unconditionally when the payload is to be cast to type U.
onBadCastIf defined and hookOpCast is not defined, onBadCast!U(get) is forwarded to when the payload is to be cast to type U and the cast would lose information or force a change of sign.
hookOpEqualsIf defined, hook.hookOpEquals(get, rhs) is forwarded to unconditionally when the payload is compared for equality against value rhs of integral, floating point, or Boolean type.
hookOpCmpIf defined, hook.hookOpCmp(get, rhs) is forwarded to unconditionally when the payload is compared for ordering against value rhs of integral, floating point, or Boolean type.
hookOpUnaryIf defined, hook.hookOpUnary!op(get) (where op is the operator symbol) is forwarded to for unary operators - and ~. In addition, for unary operators ++ and --, hook.hookOpUnary!op(payload) is called, where payload is a reference to the value wrapped by Checked so the hook can change it.
hookOpBinaryIf defined, hook.hookOpBinary!op(get, rhs) (where op is the operator symbol and rhs is the right-hand side operand) is forwarded to unconditionally for binary operators +, -, *, /, %, ^^, &, |, ^, <<, >>, and >>>.
hookOpBinaryRightIf defined, hook.hookOpBinaryRight!op(lhs, get) (where op is the operator symbol and lhs is the left-hand side operand) is forwarded to unconditionally for binary operators +, -, *, /, %, ^^, &, |, ^, <<, >>, and >>>.
onOverflowIf defined, hook.onOverflow!op(get) is forwarded to for unary operators that overflow but only if hookOpUnary is not defined. Unary ~ does not overflow; unary - overflows only when the most negative value of a signed type is negated, and the result of the hook call is returned. When the increment or decrement operators overflow, the payload is assigned the result of hook.onOverflow!op(get). When a binary operator overflows, the result of hook.onOverflow!op(get, rhs) is returned, but only if Hook does not define hookOpBinary.
hookOpOpAssignIf defined, hook.hookOpOpAssign!op(payload, rhs) (where op is the operator symbol and rhs is the right-hand side operand) is forwarded to unconditionally for binary operators +=, -=, *=, /=, %=, ^^=, &=, |=, ^=, <<=, >>=, and >>>=.
onLowerBoundIf defined, hook.onLowerBound(value, bound) (where value is the value being assigned) is forwarded to when the result of binary operators +=, -=, *=, /=, %=, ^^=, &=, |=, ^=, <<=, >>=, and >>>= is smaller than the smallest value representable by T.
onUpperBoundIf defined, hook.onUpperBound(value, bound) (where value is the value being assigned) is forwarded to when the result of binary operators +=, -=, *=, /=, %=, ^^=, &=, |=, ^=, <<=, >>=, and >>>= is larger than the largest value representable by T.
hookToHashIf defined, hook.hookToHash(payload) (where payload is a reference to the value wrapped by Checked) is forwarded to when toHash is called on a Checked type. Custom hashing can be implemented in a Hook, otherwise the built-in hashing is used.