// Written in the D programming language./**$(SCRIPT inhibitQuickIndex = 1;) This module defines facilities for efficient checking of integral operationsagainst overflow, casting with loss of precision, unexpected change of sign,etc. The checking (and possibly correction) can be done at operation level, forexample $(LREF opChecked)$(D !"+"(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 thesame flag can be reused for a sequence of operations and tested at the end. Issuing individual checked operations is flexible and efficient but oftentedious. The $(LREF Checked) facility offers encapsulated integral wrappers thatdo all checking internally and have configurable behavior upon erroneousresults. For example, `Checked!int` is a type that behaves like `int` but abortsexecution immediately whenever involved in an operation that produces thearithmetically wrong result. The accompanying convenience function $(LREFchecked) 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, $(D checked(-1) > uint(0)) aborts execution (even though the built-incomparison $(D int(-1) > uint(0)) is surprisingly true due to language'sconversion rules modeled after C). Thus, `Checked!int` is a virtually drop-inreplacement for `int` useable in debug builds, to be replaced by `int` inrelease 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 theunderlying integral may be verified for overflow or completely redefined. If`Hook` defines no method at all and carries no state, there is no change inbehavior, i.e. $(D Checked!(int, void)) is a wrapper around `int` that adds nocustomization at all. This module provides a few predefined hooks (below) that add useful behavior to`Checked`: $(BOOKTABLE , $(TR $(TD $(LREF Abort)) $(TD fails every incorrect operation with a message to $(REF stderr, std, stdio) followed by a call to `assert(0)`. It is the default second parameter, i.e. `Checked!short` is the same as $(D Checked!(short, Abort)). )) $(TR $(TD $(LREF Throw)) $(TD fails every incorrect operation by throwing an exception. )) $(TR $(TD $(LREF Warn)) $(TD prints incorrect operations to $(REF stderr, std, stdio) but otherwise preserves the built-in behavior. )) $(TR $(TD $(LREF ProperCompare)) $(TD fixes the comparison operators `==`, `!=`, `<`, `<=`, `>`, and `>=` to return correct results in all circumstances, at a slight cost in efficiency. For example, $(D 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. )) $(TR $(TD $(LREF WithNaN)) $(TD reserves a special "Not a Number" (NaN) value akin to the homonym value reserved for floating-point values. Once a $(D Checked!(X, WithNaN)) gets this special value, it preserves and propagates it until reassigned. $(LREF isNaN) can be used to query whether the object is not a number. )) $(TR $(TD $(LREF Saturate)) $(TD implements saturating arithmetic, i.e. $(D 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. ))) These policies may be used alone, e.g. $(D 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 achecked integral emulates an actual integral, which means another checkedintegral can be built on top of it. Some combinations of interest include: $(BOOKTABLE , $(TR $(TD $(D Checked!(Checked!int, ProperCompare)))) $(TR $(TDdefines an `int` with fixedcomparison operators that will fail with `assert(0)` upon overflow. (Recall that`Abort` is the default policy.) The order in which policies are combined isimportant because the outermost policy (`ProperCompare` in this case) has thefirst crack at intercepting an operator. The converse combination $(DChecked!(Checked!(int, ProperCompare))) is meaningless because `Abort` willintercept comparison and will fail without giving `ProperCompare` a chance tointervene. )) $(TR $(TD)) $(TR $(TDNW $(D Checked!(Checked!(int, ProperCompare), WithNaN)))) $(TR $(TDdefines an `int`-liketype that supports a NaN value. For values that are not NaN, comparison worksproperly. Again the composition order is important; $(D 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 mannerand are all optional. The table below illustrates the members that a hook typemay 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 notintroduce any state, or an object of type `Hook` otherwise. $(TABLE ,$(TR $(TH `Hook` member) $(TH Semantics in $(D Checked!(T, Hook))))$(TR $(TD `defaultValue`) $(TD If defined, `Hook.defaultValue!T` is used as thedefault initializer of the payload.))$(TR $(TD `min`) $(TD If defined, `Hook.min!T` is used as the minimum value ofthe payload.))$(TR $(TD `max`) $(TD If defined, `Hook.max!T` is used as the maximum value ofthe payload.))$(TR $(TD `hookOpCast`) $(TD If defined, `hook.hookOpCast!U(get)` is forwardedto unconditionally when the payload is to be cast to type `U`.))$(TR $(TD `onBadCast`) $(TD If defined and `hookOpCast` is $(I 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.))$(TR $(TD `hookOpEquals`) $(TD If defined, $(D hook.hookOpEquals(get, rhs)) isforwarded to unconditionally when the payload is compared for equality againstvalue `rhs` of integral, floating point, or Boolean type.))$(TR $(TD `hookOpCmp`) $(TD If defined, $(D hook.hookOpCmp(get, rhs)) isforwarded to unconditionally when the payload is compared for ordering againstvalue `rhs` of integral, floating point, or Boolean type.))$(TR $(TD `hookOpUnary`) $(TD If defined, `hook.hookOpUnary!op(get)` (where `op`is the operator symbol) is forwarded to for unary operators `-` and `~`. Inaddition, for unary operators `++` and `--`, `hook.hookOpUnary!op(payload)` iscalled, where `payload` is a reference to the value wrapped by `Checked` so thehook can change it.))$(TR $(TD `hookOpBinary`) $(TD If defined, $(D hook.hookOpBinary!op(get, rhs))(where `op` is the operator symbol and `rhs` is the right-hand side operand) isforwarded to unconditionally for binary operators `+`, `-`, `*`, `/`, `%`,`^^`, `&`, `|`, `^`, `<<`, `>>`, and `>>>`.))$(TR $(TD `hookOpBinaryRight`) $(TD If defined, $(Dhook.hookOpBinaryRight!op(lhs, get)) (where `op` is the operator symbol and`lhs` is the left-hand side operand) is forwarded to unconditionally for binaryoperators `+`, `-`, `*`, `/`, `%`, `^^`, `&`, `|`, `^`, `<<`, `>>`, and `>>>`.))$(TR $(TD `onOverflow`) $(TD If defined, `hook.onOverflow!op(get)` is forwardedto for unary operators that overflow but only if `hookOpUnary` is not defined.Unary `~` does not overflow; unary `-` overflows only when the most negativevalue 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 theresult of `hook.onOverflow!op(get)`. When a binary operator overflows, theresult of $(D hook.onOverflow!op(get, rhs)) is returned, but only if `Hook` doesnot define `hookOpBinary`.))$(TR $(TD `hookOpOpAssign`) $(TD If defined, $(D hook.hookOpOpAssign!op(payload,rhs)) (where `op` is the operator symbol and `rhs` is the right-hand sideoperand) is forwarded to unconditionally for binary operators `+=`, `-=`, `*=`, `/=`, `%=`,`^^=`, `&=`, `|=`, `^=`, `<<=`, `>>=`, and `>>>=`.))$(TR $(TD `onLowerBound`) $(TD If defined, $(D hook.onLowerBound(value, bound))(where `value` is the value being assigned) is forwarded to when the result ofbinary operators `+=`, `-=`, `*=`, `/=`, `%=`, `^^=`, `&=`, `|=`, `^=`, `<<=`, `>>=`,and `>>>=` is smaller than the smallest value representable by `T`.))$(TR $(TD `onUpperBound`) $(TD If defined, $(D hook.onUpperBound(value, bound))(where `value` is the value being assigned) is forwarded to when the result ofbinary operators `+=`, `-=`, `*=`, `/=`, `%=`, `^^=`, `&=`, `|=`, `^=`, `<<=`, `>>=`,and `>>>=` is larger than the largest value representable by `T`.))$(TR $(TD `hookToHash`) $(TD If defined, $(D hook.hookToHash(payload))(where `payload` is a reference to the value wrapped by Checked) is forwardedto when `toHash` is called on a Checked type. Custom hashing can be implementedin a `Hook`, otherwise the built-in hashing is used.))) Source: $(PHOBOSSRC std/checkedint.d)*/module std.checkedint;import std.traits : isFloatingPoint, isIntegral, isNumeric, isUnsigned, Unqual; ///@safe unittest{ int[] concatAndAdd(int[] a, int[] b, int offset) { // Aborts on overflow on size computation auto r = new int[(checked(a.length) + b.length).get]; // Aborts on overflow on element computation foreach (i; 0 .. a.length) r[i] = (a[i] + checked(offset)).get; foreach (i; 0 .. b.length) r[i + a.length] = (b[i] + checked(offset)).get; return r; } assert(concatAndAdd([1, 2, 3], [4, 5], -1) == [0, 1, 2, 3, 4]);} /// `Saturate` stops at an overflow@safe unittest{ auto x = (cast(byte) 127).checked!Saturate; assert(x == 127); x++; assert(x == 127);} /// `WithNaN` has a special "Not a Number" (NaN) value akin to the homonym value reserved for floating-point values@safe unittest{ auto x = 100.checked!WithNaN; assert(x == 100); x /= 0; assert(x.isNaN);} /// `ProperCompare` fixes the comparison operators ==, !=, <, <=, >, and >= to return correct results@safe unittest{ uint x = 1; auto y = x.checked!ProperCompare; assert(x < -1); // built-in comparison assert(y > -1); // ProperCompare} /// `Throw` fails every incorrect operation by throwing an exception@safe unittest{ import std.exception : assertThrown; auto x = -1.checked!Throw; assertThrown(x / 0); assertThrown(x + int.min); assertThrown(x == uint.max);} /**Checked integral type wraps an integral `T` and customizes its behavior with thehelp of a `Hook` type. The type wrapped must be one of the predefined integrals(unqualified), or another instance of `Checked`. Params: T = type that is wrapped in the `Checked` type Hook = hook type that customizes the behavior of the `Checked` type*/struct Checked(T, Hook = Abort)if (isIntegral!T || is(T == Checked!(U, H), U, H)){ import std.algorithm.comparison : among; import std.experimental.allocator.common : stateSize; import std.format.spec : FormatSpec; import std.range.primitives : isInputRange, ElementType; import std.traits : hasMember, isSomeChar; /** The type of the integral subject to checking. */ alias Representation = T; // state { static if (hasMember!(Hook, "defaultValue")) private T payload = Hook.defaultValue!T; else private T payload; /** `hook` is a member variable if it has state, or an alias for `Hook` otherwise. */ static if (stateSize!Hook > 0) Hook hook; else alias hook = Hook; // } state // get /** Returns: A copy of the underlying value. */ auto get() inout { return payload; } /// @safe unittest { auto x = checked(ubyte(42)); static assert(is(typeof(x.get()) == ubyte)); assert(x.get == 42); const y = checked(ubyte(42)); static assert(is(typeof(y.get()) == const ubyte)); assert(y.get == 42); } /** Defines the minimum and maximum. These values are hookable by defining `Hook.min` and/or `Hook.max`. */ static if (hasMember!(Hook, "min")) { enum Checked!(T, Hook) min = Checked!(T, Hook)(Hook.min!T); /// @safe unittest { assert(Checked!short.min == -32768); assert(Checked!(short, WithNaN).min == -32767); assert(Checked!(uint, WithNaN).max == uint.max - 1); } } else { /// ditto enum Checked!(T, Hook) min = Checked(T.min); } static if (hasMember!(Hook, "max")) { /// ditto enum Checked!(T, Hook) max = Checked(Hook.max!T); } else { /// ditto enum Checked!(T, Hook) max = Checked(T.max); } /** Constructor taking a value properly convertible to the underlying type. `U` may be either an integral that can be converted to `T` without a loss, or another `Checked` instance whose representation may be in turn converted to `T` without a loss. */ this(U)(U rhs) if (valueConvertible!(U, T) || !isIntegral!T && is(typeof(T(rhs))) || is(U == Checked!(V, W), V, W) && is(typeof(Checked!(T, Hook)(rhs.get)))) { static if (isIntegral!U) payload = rhs; else payload = rhs.payload; } /// @safe unittest { auto a = checked(42L); assert(a == 42); auto b = Checked!long(4242); // convert 4242 to long assert(b == 4242); } /** Assignment operator. Has the same constraints as the constructor. Params: rhs = The value to assign Returns: A reference to `this` */ ref Checked opAssign(U)(U rhs) return if (is(typeof(Checked!(T, Hook)(rhs)))) { static if (isIntegral!U) payload = rhs; else payload = rhs.payload; return this; } /// @safe unittest { Checked!long a; a = 42L; assert(a == 42); a = 4242; assert(a == 4242); } /// @safe unittest { Checked!long a, b; a = b = 3; assert(a == 3 && b == 3); } /** Construct from a decimal string. The conversion follows the same rules as $(REF to, std, conv) converting a string to the wrapped `T` type. Params: str = an $(REF_ALTTEXT input range, isInputRange, std,range,primitives) of characters */ this(Range)(Range str) if (isInputRange!Range && isSomeChar!(ElementType!Range)) { import std.conv : to; this(to!T(str)); } /** $(REF to, std, conv) can convert a string to a `Checked!T`: */ @system unittest { import std.conv : to; const a = to!long("1234"); const b = to!(Checked!long)("1234"); assert(a == b); } // opCast /** Casting operator to integral, `bool`, or floating point type. If a cast to a floating-point type is requested and `Hook` defines `onBadCast`, the cast is verified by ensuring $(D get == cast(T) U(get)). If that is not `true`, `hook.onBadCast!U(get)` is returned. If a cast to an integral type is requested and `Hook` defines `onBadCast`, the cast is verified by ensuring `get` and $(D cast(U) get) are the same arithmetic number. (Note that `int(-1)` and `uint(1)` are different values arithmetically although they have the same bitwise representation and compare equal by language rules.) If the numbers are not arithmetically equal, `hook.onBadCast!U(get)` is returned. Params: U = The type to cast to Returns: If `Hook` defines `hookOpCast`, the call immediately returns `hook.hookOpCast!U(get)`. Otherwise, casting to `bool` yields $(D get != 0) and casting to another integral that can represent all values of `T` returns `get` promoted to `U`. */ U opCast(U, this _)() if (isIntegral!U || isFloatingPoint!U || is(U == bool)) { static if (hasMember!(Hook, "hookOpCast")) { return hook.hookOpCast!U(payload); } else static if (is(U == bool)) { return payload != 0; } else static if (valueConvertible!(T, U)) { return payload; } // may lose bits or precision else static if (!hasMember!(Hook, "onBadCast")) { return cast(U) payload; } else { if (isUnsigned!T || !isUnsigned!U || T.sizeof > U.sizeof || payload >= 0) { auto result = cast(U) payload; // If signedness is different, we need additional checks if (result == payload && (!isUnsigned!T || isUnsigned!U || result >= 0)) return result; } return hook.onBadCast!U(payload); } } /// @safe unittest { assert(cast(uint) checked(42) == 42); assert(cast(uint) checked!WithNaN(-42) == uint.max); } // opEquals /** Compares `this` against `rhs` for equality. If `U` is also an instance of `Checked`, both hooks (left- and right-handLines 1–500 of 3595. Load more
// Written in the D programming language. /** $(SCRIPT inhibitQuickIndex = 1;) 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 $(LREF opChecked)$(D !"+"(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 $(LREF 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 $(LREF 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, $(D checked(-1) > uint(0)) aborts execution (even though the built-in comparison $(D 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. $(D 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`: $(BOOKTABLE , $(TR $(TD $(LREF Abort)) $(TD fails every incorrect operation with a message to $(REF stderr, std, stdio) followed by a call to `assert(0)`. It is the default second parameter, i.e. `Checked!short` is the same as $(D Checked!(short, Abort)). )) $(TR $(TD $(LREF Throw)) $(TD fails every incorrect operation by throwing an exception. )) $(TR $(TD $(LREF Warn)) $(TD prints incorrect operations to $(REF stderr, std, stdio) but otherwise preserves the built-in behavior. )) $(TR $(TD $(LREF ProperCompare)) $(TD fixes the comparison operators `==`, `!=`, `<`, `<=`, `>`, and `>=` to return correct results in all circumstances, at a slight cost in efficiency. For example, $(D 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. )) $(TR $(TD $(LREF WithNaN)) $(TD reserves a special "Not a Number" (NaN) value akin to the homonym value reserved for floating-point values. Once a $(D Checked!(X, WithNaN)) gets this special value, it preserves and propagates it until reassigned. $(LREF isNaN) can be used to query whether the object is not a number. )) $(TR $(TD $(LREF Saturate)) $(TD implements saturating arithmetic, i.e. $(D 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. )) ) These policies may be used alone, e.g. $(D 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: $(BOOKTABLE , $(TR $(TD $(D Checked!(Checked!int, ProperCompare)))) $(TR $(TD 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 $(D Checked!(Checked!(int, ProperCompare))) is meaningless because `Abort` will intercept comparison and will fail without giving `ProperCompare` a chance to intervene. )) $(TR $(TD)) $(TR $(TDNW $(D Checked!(Checked!(int, ProperCompare), WithNaN)))) $(TR $(TD 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; $(D 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. $(TABLE , $(TR $(TH `Hook` member) $(TH Semantics in $(D Checked!(T, Hook))) ) $(TR $(TD `defaultValue`) $(TD If defined, `Hook.defaultValue!T` is used as the default initializer of the payload.) ) $(TR $(TD `min`) $(TD If defined, `Hook.min!T` is used as the minimum value of the payload.) ) $(TR $(TD `max`) $(TD If defined, `Hook.max!T` is used as the maximum value of the payload.) ) $(TR $(TD `hookOpCast`) $(TD If defined, `hook.hookOpCast!U(get)` is forwarded to unconditionally when the payload is to be cast to type `U`.) ) $(TR $(TD `onBadCast`) $(TD If defined and `hookOpCast` is $(I 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.) ) $(TR $(TD `hookOpEquals`) $(TD If defined, $(D 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.) ) $(TR $(TD `hookOpCmp`) $(TD If defined, $(D 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.) ) $(TR $(TD `hookOpUnary`) $(TD If 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.) ) $(TR $(TD `hookOpBinary`) $(TD If defined, $(D 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 `>>>`.) ) $(TR $(TD `hookOpBinaryRight`) $(TD If defined, $(D 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 `>>>`.) ) $(TR $(TD `onOverflow`) $(TD If 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 $(D hook.onOverflow!op(get, rhs)) is returned, but only if `Hook` does not define `hookOpBinary`.) ) $(TR $(TD `hookOpOpAssign`) $(TD If defined, $(D 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 `>>>=`.) ) $(TR $(TD `onLowerBound`) $(TD If defined, $(D 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`.) ) $(TR $(TD `onUpperBound`) $(TD If defined, $(D 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`.) ) $(TR $(TD `hookToHash`) $(TD If defined, $(D 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.) ) ) Source: $(PHOBOSSRC std/checkedint.d) */ module std.checkedint; import std.traits : isFloatingPoint, isIntegral, isNumeric, isUnsigned, Unqual; /// @safe unittest { int[] concatAndAdd(int[] a, int[] b, int offset) { // Aborts on overflow on size computation auto r = new int[(checked(a.length) + b.length).get]; // Aborts on overflow on element computation foreach (i; 0 .. a.length) r[i] = (a[i] + checked(offset)).get; foreach (i; 0 .. b.length) r[i + a.length] = (b[i] + checked(offset)).get; return r; } assert(concatAndAdd([1, 2, 3], [4, 5], -1) == [0, 1, 2, 3, 4]); } /// `Saturate` stops at an overflow @safe unittest { auto x = (cast(byte) 127).checked!Saturate; assert(x == 127); x++; assert(x == 127); } /// `WithNaN` has a special "Not a Number" (NaN) value akin to the homonym value reserved for floating-point values @safe unittest { auto x = 100.checked!WithNaN; assert(x == 100); x /= 0; assert(x.isNaN); } /// `ProperCompare` fixes the comparison operators ==, !=, <, <=, >, and >= to return correct results @safe unittest { uint x = 1; auto y = x.checked!ProperCompare; assert(x < -1); // built-in comparison assert(y > -1); // ProperCompare } /// `Throw` fails every incorrect operation by throwing an exception @safe unittest { import std.exception : assertThrown; auto x = -1.checked!Throw; assertThrown(x / 0); assertThrown(x + int.min); assertThrown(x == uint.max); } /** 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`. Params: T = type that is wrapped in the `Checked` type Hook = hook type that customizes the behavior of the `Checked` type */ struct Checked(T, Hook = Abort) if (isIntegral!T || is(T == Checked!(U, H), U, H)) { import std.algorithm.comparison : among; import std.experimental.allocator.common : stateSize; import std.format.spec : FormatSpec; import std.range.primitives : isInputRange, ElementType; import std.traits : hasMember, isSomeChar; /** The type of the integral subject to checking. */ alias Representation = T; // state { static if (hasMember!(Hook, "defaultValue")) private T payload = Hook.defaultValue!T; else private T payload; /** `hook` is a member variable if it has state, or an alias for `Hook` otherwise. */ static if (stateSize!Hook > 0) Hook hook; else alias hook = Hook; // } state // get /** Returns: A copy of the underlying value. */ auto get() inout { return payload; } /// @safe unittest { auto x = checked(ubyte(42)); static assert(is(typeof(x.get()) == ubyte)); assert(x.get == 42); const y = checked(ubyte(42)); static assert(is(typeof(y.get()) == const ubyte)); assert(y.get == 42); } /** Defines the minimum and maximum. These values are hookable by defining `Hook.min` and/or `Hook.max`. */ static if (hasMember!(Hook, "min")) { enum Checked!(T, Hook) min = Checked!(T, Hook)(Hook.min!T); /// @safe unittest { assert(Checked!short.min == -32768); assert(Checked!(short, WithNaN).min == -32767); assert(Checked!(uint, WithNaN).max == uint.max - 1); } } else { /// ditto enum Checked!(T, Hook) min = Checked(T.min); } static if (hasMember!(Hook, "max")) { /// ditto enum Checked!(T, Hook) max = Checked(Hook.max!T); } else { /// ditto enum Checked!(T, Hook) max = Checked(T.max); } /** Constructor taking a value properly convertible to the underlying type. `U` may be either an integral that can be converted to `T` without a loss, or another `Checked` instance whose representation may be in turn converted to `T` without a loss. */ this(U)(U rhs) if (valueConvertible!(U, T) || !isIntegral!T && is(typeof(T(rhs))) || is(U == Checked!(V, W), V, W) && is(typeof(Checked!(T, Hook)(rhs.get)))) { static if (isIntegral!U) payload = rhs; else payload = rhs.payload; } /// @safe unittest { auto a = checked(42L); assert(a == 42); auto b = Checked!long(4242); // convert 4242 to long assert(b == 4242); } /** Assignment operator. Has the same constraints as the constructor. Params: rhs = The value to assign Returns: A reference to `this` */ ref Checked opAssign(U)(U rhs) return if (is(typeof(Checked!(T, Hook)(rhs)))) { static if (isIntegral!U) payload = rhs; else payload = rhs.payload; return this; } /// @safe unittest { Checked!long a; a = 42L; assert(a == 42); a = 4242; assert(a == 4242); } /// @safe unittest { Checked!long a, b; a = b = 3; assert(a == 3 && b == 3); } /** Construct from a decimal string. The conversion follows the same rules as $(REF to, std, conv) converting a string to the wrapped `T` type. Params: str = an $(REF_ALTTEXT input range, isInputRange, std,range,primitives) of characters */ this(Range)(Range str) if (isInputRange!Range && isSomeChar!(ElementType!Range)) { import std.conv : to; this(to!T(str)); } /** $(REF to, std, conv) can convert a string to a `Checked!T`: */ @system unittest { import std.conv : to; const a = to!long("1234"); const b = to!(Checked!long)("1234"); assert(a == b); } // opCast /** Casting operator to integral, `bool`, or floating point type. If a cast to a floating-point type is requested and `Hook` defines `onBadCast`, the cast is verified by ensuring $(D get == cast(T) U(get)). If that is not `true`, `hook.onBadCast!U(get)` is returned. If a cast to an integral type is requested and `Hook` defines `onBadCast`, the cast is verified by ensuring `get` and $(D cast(U) get) are the same arithmetic number. (Note that `int(-1)` and `uint(1)` are different values arithmetically although they have the same bitwise representation and compare equal by language rules.) If the numbers are not arithmetically equal, `hook.onBadCast!U(get)` is returned. Params: U = The type to cast to Returns: If `Hook` defines `hookOpCast`, the call immediately returns `hook.hookOpCast!U(get)`. Otherwise, casting to `bool` yields $(D get != 0) and casting to another integral that can represent all values of `T` returns `get` promoted to `U`. */ U opCast(U, this _)() if (isIntegral!U || isFloatingPoint!U || is(U == bool)) { static if (hasMember!(Hook, "hookOpCast")) { return hook.hookOpCast!U(payload); } else static if (is(U == bool)) { return payload != 0; } else static if (valueConvertible!(T, U)) { return payload; } // may lose bits or precision else static if (!hasMember!(Hook, "onBadCast")) { return cast(U) payload; } else { if (isUnsigned!T || !isUnsigned!U || T.sizeof > U.sizeof || payload >= 0) { auto result = cast(U) payload; // If signedness is different, we need additional checks if (result == payload && (!isUnsigned!T || isUnsigned!U || result >= 0)) return result; } return hook.onBadCast!U(payload); } } /// @safe unittest { assert(cast(uint) checked(42) == 42); assert(cast(uint) checked!WithNaN(-42) == uint.max); } // opEquals /** Compares `this` against `rhs` for equality. If `U` is also an instance of `Checked`, both hooks (left- and right-hand