Skip to content

Instantly share code, notes, and snippets.

@andralex
Created June 24, 2016 21:08
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save andralex/a0c0ad32704e6ba66e458ac48add4a99 to your computer and use it in GitHub Desktop.
Save andralex/a0c0ad32704e6ba66e458ac48add4a99 to your computer and use it in GitHub Desktop.
import std.traits : isIntegral, isUnsigned, Unqual;
/**
*/
pure @safe nothrow @nogc
{
int pow(int lhs, uint rhs, ref bool overflow)
{ return powImpl(lhs, rhs, overflow); }
/// ditto
long pow(long lhs, uint rhs, ref bool overflow)
{ return powImpl(lhs, rhs, overflow); }
/// ditto
uint pow(uint lhs, uint rhs, ref bool overflow)
{ return powImpl(lhs, rhs, overflow); }
/// ditto
ulong pow(ulong lhs, uint rhs, ref bool overflow)
{ return powImpl(lhs, rhs, overflow); }
}
// Inspiration: http://www.stepanovpapers.com/PAM.pdf
private T powImpl(T)(T b, uint e, ref bool overflow)
if (isIntegral!T && T.sizeof >= 4)
{
if (e <= 1) return e == 1 ? b : 1;
import core.checkedint : muls, mulu;
static if (isUnsigned!T) alias mul = mulu;
else alias mul = muls;
T r = b;
--e;
// Loop invariant: r * (b ^^ e) is the actual result
for (;; e /= 2)
{
if (e % 2)
{
r = mul(r, b, overflow);
if (e == 1) break;
}
b = mul(b, b, overflow);
}
return r;
}
unittest
{
static void testPow(T)(T x, uint e)
{
bool overflow;
foreach (uint i; 0 .. e)
{
assert(pow(x, i, overflow) == x ^^ i);
assert(!overflow);
}
assert(pow(x, e, overflow) == x ^^ e);
assert(overflow);
}
testPow!int(3, 20);
testPow!uint(3, 21);
testPow!long(3, 40);
testPow!ulong(3, 41);
}
private enum valueConvertible(T1, T2) = isIntegral!T1 && isIntegral!T2 &&
is(T1 : T2) && (
isUnsigned!T1 == isUnsigned!T2 || // same signedness
!isUnsigned!T2 && T2.sizeof > T1.sizeof // safely convertible
);
/**
*/
auto add(L, R)(const L lhs, const R rhs, ref bool overflow)
if (isIntegral!L && isIntegral!R && L.sizeof >= 4 && R.sizeof >= 4)
{
import core.checkedint;
alias Result = typeof(lhs + rhs);
static if (valueConvertible!(L, Result) && valueConvertible!(R, Result))
{
static if (isUnsigned!Result) alias impl = addu;
else alias impl = adds;
return impl(Result(lhs), Result(rhs), overflow);
}
else
{
static assert(isUnsigned!Result);
static assert(isUnsigned!L != isUnsigned!R);
// Mixed signs
static if (!isUnsigned!L)
{
if (lhs < 0)
{
return subu(Result(rhs), Result(negs(lhs, overflow)),
overflow);
}
}
else static if (!isUnsigned!R)
{
if (rhs < 0)
{
return subu(Result(lhs), Result(negs(rhs, overflow)),
overflow);
}
}
return addu(Result(lhs), Result(rhs), overflow);
}
}
unittest
{
bool overflow;
assert(add(1, 1, overflow) == 2 && !overflow);
assert(add(1, 1u, overflow) == 2 && !overflow);
assert(add(-1, 1u, overflow) == 0 && !overflow);
assert(add(1u, -1, overflow) == 0 && !overflow);
}
auto sub(L, R)(const L lhs, const R rhs, ref bool overflow)
if (isIntegral!L && isIntegral!R && L.sizeof >= 4 && R.sizeof >= 4)
{
import core.checkedint;
alias Result = typeof(lhs - rhs);
static if (valueConvertible!(L, Result) && valueConvertible!(R, Result))
{
static if (isUnsigned!Result) alias impl = subu;
else alias impl = subs;
return impl(Result(lhs), Result(rhs), overflow);
}
else
{
static assert(isUnsigned!Result);
static assert(isUnsigned!L != isUnsigned!R);
// Mixed signs
static if (!isUnsigned!L)
{
if (lhs < 0)
{
overflow = true;
return Result(0); //lhs - rhs;
}
}
else static if (!isUnsigned!R)
{
if (rhs < 0)
{
return addu(Result(lhs), Result(negs(rhs, overflow)), overflow);
}
}
return subu(Result(lhs), Result(rhs), overflow);
}
}
unittest
{
bool overflow;
assert(sub(1, 1, overflow) == 0 && !overflow);
assert(sub(1, 1u, overflow) == 0 && !overflow);
assert(sub(1u, -1, overflow) == 2 && !overflow);
assert(sub(-1, 1u, overflow) == 0 && overflow);
}
auto mul(L, R)(const L lhs, const R rhs, ref bool overflow)
if (isIntegral!L && isIntegral!R && L.sizeof >= 4 && R.sizeof >= 4)
{
import core.checkedint;
alias Result = typeof(lhs * rhs);
static if (valueConvertible!(L, Result) && valueConvertible!(R, Result))
{
static if (isUnsigned!Result) alias impl = mulu;
else alias impl = muls;
return impl(Result(lhs), Result(rhs), overflow);
}
else
{
static assert(isUnsigned!Result);
static assert(isUnsigned!L != isUnsigned!R);
// Mixed signs
static if (!isUnsigned!L)
{
if (lhs < 0)
{
overflow = true;
return Result(0); //lhs * rhs;
}
}
else static if (!isUnsigned!R)
{
if (rhs < 0)
{
overflow = true;
return Result(0);// lhs * rhs;
}
}
return mulu(Result(lhs), Result(rhs), overflow);
}
}
unittest
{
bool overflow;
assert(mul(2, 3, overflow) == 6 && !overflow);
assert(mul(2, 3u, overflow) == 6 && !overflow);
assert(mul(1u, -1, overflow) == 0 && overflow);
//assert(mul(-1, 1u, overflow) == uint.max - 1 && overflow);
}
template divrem(string op)
{
typeof(L() + R()) divrem(L, R)(const L lhs, const R rhs, ref bool overflow)
if (isIntegral!L && isIntegral!R && L.sizeof >= 4 && R.sizeof >= 4)
{
import core.checkedint;
alias Result = typeof(lhs / rhs);
static if (valueConvertible!(L, Result) &&
valueConvertible!(R, Result))
{
if (rhs != 0) return mixin("lhs"~op~"rhs");
overflow = true;
return Result(0);
}
else
{
static assert(isUnsigned!Result);
static assert(isUnsigned!L != isUnsigned!R);
// Mixed signs
static if (!isUnsigned!L)
{
if (lhs < 0 || rhs == 0)
{
overflow = true;
return Result(0); // mixin("lhs"~op~"rhs");
}
}
else static if (!isUnsigned!R)
{
if (rhs <= 0)
{
overflow = true;
return Result(0); // mixin("lhs"~op~"rhs");
}
}
return mixin("Result(lhs)"~op~"Result(rhs)");
}
}
}
unittest
{
bool overflow;
assert(divrem!"/"(6, 3, overflow) == 2 && !overflow);
assert(divrem!"/"(6, 3, overflow) == 2 && !overflow);
assert(divrem!"/"(6u, 3, overflow) == 2 && !overflow);
assert(divrem!"/"(6, 3u, overflow) == 2 && !overflow);
assert(divrem!"/"(11, 0, overflow) == 0 && overflow);
overflow = false;
assert(divrem!"/"(6u, 0, overflow) == 0 && overflow);
overflow = false;
assert(divrem!"/"(-6, 2u, overflow) == 0 && overflow);
overflow = false;
assert(divrem!"/"(-6, 0u, overflow) == 0 && overflow);
}
/**
Promotes an integral to `int`, `uint`, `long`, or `ulong` following the value
conversion rules: everything smaller than `int` is converted to `int`, and
all other types remain unchanged.
*/
pure @safe nothrow @nogc
{
int promote(byte x) { return x; }
int promote(ubyte x) { return x; }
int promote(short x) { return x; }
int promote(ushort x) { return x; }
int promote(int x) { return x; }
uint promote(uint x) { return x; }
long promote(long x) { return x; }
ulong promote(ulong x) { return x; }
}
/**
*/
struct Checked(T, Hook)
if (isIntegral!T && is(T == Unqual!T))
{
import std.algorithm : among;
import std.traits : hasMember, isFloatingPoint;
import std.experimental.allocator.common : stateSize;
/**
*/
alias Payload = T;
// state {
private T payload;
static if (stateSize!Hook > 0) Hook hook;
else alias hook = Hook;
// }
/**
*/
T representation() const { return payload; }
/**
*/
enum min = Checked(T.min);
/// ditto
enum max = Checked(T.max);
/**
*/
this(T1)(const T1 rhs) if (valueConvertible!(T1, T))
{
payload = rhs;
}
/**
*/
this(T1, Hook1)(const Checked!(T1, Hook1) rhs) if (valueConvertible!(T1, T))
{
payload = rhs.payload;
}
/**
*/
void opAssign(U)(const U rhs) if (is(typeof(Checked(rhs))))
{
// Just "reuse" the ctor
payload = Checked(rhs).payload;
}
/**
*/
T1 opCast(T1)()
if (isIntegral!T1 || isFloatingPoint!T1 || is(T1 == bool))
{
static if (is(T1 == bool))
{
return payload != 0;
}
else static if (isIntegral!T1 && valueConvertible!(T, T1))
{
return payload;
}
else // may lose bits or precision
{
static if (hasMember!(Hook, "hookOpCast"))
{
return hook.hookOpCast!T1(payload);
}
else static if (hasMember!(Hook, "onBadCast"))
{
static if (isUnsigned!T == isUnsigned!T1) // Same signedness
{
auto result = cast(T1) payload;
if (result == payload) return result;
}
else
{
// TODO: make faster
auto result = cast(T1) payload;
if (result == payload && (result < 0) == (payload < 0))
return result;
}
return hook.onBadCast!T1(payload);
}
else
{
return cast(T1) payload;
}
}
}
/**
*/
bool opEquals(T1)(const T1 rhs) //const pure @safe nothrow @nogc
if (isIntegral!T1 || isFloatingPoint!T1 || is(T1 == bool))
{
static if (valueConvertible!(T, T1) || valueConvertible!(T1, T))
{
return payload == rhs;
}
else static if (hasMember!(Hook, "hookOpEquals"))
{
return hook.hookOpEquals(payload, rhs);
}
else static if (hasMember!(Hook, "onBadOpEquals"))
{
if (payload != rhs) return false;
static if (isUnsigned!T)
{
if (rhs >= 0) return true;
}
else
{
if (payload >= 0) return true;
}
return hook.onBadOpEquals(payload, rhs);
}
else
{
return payload == rhs;
}
}
/// ditto
bool opEquals(T1, Hook1)(const Checked!(T1, Hook1) rhs)
if (is(typeof(this == rhs.payload)))
{
alias R = typeof(payload + rhs.payload);
static if (valueConvertible!(T, R) && valueConvertible!(T1, R))
{
return payload == rhs.payload;
}
else static if (is(Hook == Hook1))
{
// Use the lhs hook
return this == rhs.payload;
}
else static if (hasMember!(Hook, "hookOpEquals"))
{
return hook.hookOpEquals(payload, rhs);
}
else static if (hasMember!(Hook1, "hookOpEquals"))
{
return rhs.hook.hookOpEquals(rhs.payload, this);
}
else static if (hasMember!(Hook, "onBadOpEquals") &&
!hasMember!(Hook1, "onBadOpEquals"))
{
// Use the lhs hook
return this == rhs.payload;
}
else static if (hasMember!(Hook1, "onBadOpEquals") &&
!hasMember!(Hook, "onBadOpEquals"))
{
// Use the rhs hook
return this.payload == rhs;
}
else
{
static assert(0, "Conflict between lhs and rhs hooks,"
" use .representation on one side to disambiguate.");
}
}
/**
*/
auto opCmp(T1)(const T1 rhs) //const pure @safe nothrow @nogc
if (isIntegral!T1 || isFloatingPoint!T1 || is(T1 == bool))
{
static if (valueConvertible!(T, T1) || valueConvertible!(T1, T))
{
return payload < rhs ? -1 : payload > rhs;
}
else static if (isFloatingPoint!T1)
{
return payload < rhs ? -1
: payload > rhs ? 1
: payload == rhs ? 0 : float.nan;
}
else static if (hasMember!(Hook, "hookOpCmp"))
{
return hook.hookOpCmp(payload, rhs);
}
else static if (hasMember!(Hook, "onBadOpCmp"))
{
static assert(isUnsigned!T != isUnsigned!T1);
static if (isUnsigned!T)
{
if (rhs >= 0)
return payload < rhs ? -1 : payload > rhs;
}
else
{
if (payload >= 0)
return payload < rhs ? -1 : payload > rhs;
}
return hook.onBadOpCmp(payload, rhs);
}
else
{
// Mimic nasty built-in behavior
return payload < rhs ? -1 : payload > rhs;
}
}
/// ditto
auto opCmp(T1, Hook1)(const Checked!(T1, Hook1) rhs)
{
alias R = typeof(payload + rhs.payload);
static if (valueConvertible!(T, R) && valueConvertible!(T, R))
{
return payload < rhs.payload ? -1 : payload > rhs.payload;
}
else static if (is(Hook == Hook1))
{
// Use the lhs hook
return this.opCmp(rhs.payload);
}
else static if (hasMember!(Hook, "hookOpCmp"))
{
return hook.hookOpCmp(payload, rhs);
}
else static if (hasMember!(Hook1, "hookOpCmp"))
{
return rhs.hook.hookOpCmp(rhs.payload, this);
}
else static if (hasMember!(Hook, "onBadOpCmp") &&
!hasMember!(Hook1, "onBadOpCmp"))
{
// Use the lhs hook
return this == rhs.payload;
}
else static if (hasMember!(Hook1, "onBadOpCmp") &&
!hasMember!(Hook, "onBadOpCmp"))
{
// Use the rhs hook
return this.payload == rhs;
}
else
{
static assert(0, "Conflict between lhs and rhs hooks,"
" use .representation on one side to disambiguate.");
}
}
/**
*/
auto opUnary(string op)()
if (op == "+" || op == "-" || op == "~")
{
static if (op == "+")
return Checked(this); // "+" is not hookable
else static if (hasMember!(Hook, "hookOpUnary"))
{
auto r = hook.hookOpUnary!op(payload);
return Checked!(typeof(r), Hook)(r);
}
else static if (op == "-" && hasMember!(Hook, "onOverflow"))
{
alias R = typeof(-payload);
bool overflow;
auto r = negs(R(payload), overflow);
if (overflow) r = hook.onOverflow!op(payload);
return Checked(r);
}
else
return Checked(mixin(op ~ "payload"));
}
/// ditto
ref Checked opUnary(string op)() return
if (op == "++" || op == "--")
{
static if (hasMember!(Hook, "hookOpUnary"))
hook.hookOpUnary!op(payload);
else static if (hasMember!(Hook, "onOverflow"))
{
static if (op == "++")
{
if (payload == payload.max)
payload = hook.onOverflow!"++"(payload);
else
++payload;
}
else
{
if (payload == payload.min)
payload = hook.onOverflow!"--"(payload);
else
--payload;
}
}
else
mixin(op ~ "payload;");
return this;
}
/**
*/
auto opBinary(string op, Rhs)(const Rhs rhs)
if (isIntegral!Rhs || isFloatingPoint!Rhs || is(Rhs == bool))
{
alias R = typeof(payload + rhs);
static assert(is(typeof(mixin("payload"~op~"rhs")) == R));
static if (isIntegral!R) alias Result = Checked!(R, Hook);
else alias Result = R;
static if (hasMember!(Hook, "hookOpBinary"))
{
auto r = hook.hookOpBinary!op(payload, rhs);
return Result(r);
}
else static if (is(Rhs == bool))
{
return mixin("this"~op~"typeof(payload + rhs)(rhs)");
}
else static if (isFloatingPoint!Rhs)
{
return mixin("payload"~op~"rhs");
}
else static if (op.among("+", "-", "*", "/", "%") &&
hasMember!(Hook, "onOverflow"))
{
bool overflow;
static if (op == "+") alias impl = add;
else static if (op == "-") alias impl = sub;
else static if (op == "*") alias impl = mul;
else static if (op == "/") alias impl = divrem!"/";
else static if (op == "%") alias impl = divrem!"%";
auto r = impl(promote(payload), promote(rhs), overflow);
if (overflow) r = hook.onOverflow!op(payload, rhs);
return Result(r);
}
else static if (op == "^^" && hasMember!(Hook, "onOverflow"))
{
static if (!isUnsigned!Rhs)
{
if (rhs < 0) return Result(R(0));
}
static if (isUnsigned!R && !isUnsigned!T)
{
if (payload < 0 && (rhs & 1))
return Result(hook.onOverflow!op(payload, rhs));
}
bool overflow;
import std.conv : unsigned;
auto r = pow(R(payload), unsigned(rhs), overflow);
if (overflow) r = hook.onOverflow!op(payload, rhs);
return Result(r);
}
else static if (op.among("<<", ">>", ">>>") &&
hasMember!(Hook, "onOverflow"))
{
if (!isUnsigned!Rhs && rhs < 0 || rhs > R.sizeof)
return Result(hook.onOverflow!op(payload, rhs));
}
// Default is built-in behavior
return Result(mixin("payload"~op~"rhs"));
}
/// ditto
auto opBinary(string op, T1, Hook1)(const Checked!(T1, Hook1) rhs)
{
alias R = typeof(payload + rhs.payload);
static if (valueConvertible!(T, R) && valueConvertible!(T, R) ||
is(Hook == Hook1))
{
// Delegate to lhs
return mixin("this"~op~"rhs.payload");
}
else static if (hasMember!(Hook, "hookOpBinary"))
{
return hook.hookOpBinary!op(payload, rhs);
}
else static if (hasMember!(Hook1, "hookOpBinary"))
{
// Delegate to rhs
return mixin("this.payload"~op~"rhs");
}
else static if (hasMember!(Hook, "onOverflow") &&
!hasMember!(Hook1, "onOverflow"))
{
// Delegate to lhs
return mixin("this"~op~"rhs.payload");
}
else static if (hasMember!(Hook1, "onOverflow") &&
!hasMember!(Hook, "onOverflow"))
{
// Delegate to rhs
return mixin("this.payload"~op~"rhs");
}
else
{
static assert(0, "Conflict between lhs and rhs hooks,"
" use .representation on one side to disambiguate.");
}
}
auto opBinaryRight(string op, Lhs)(const Lhs lhs) const
if (isIntegral!Lhs || isFloatingPoint!Lhs || is(Lhs == bool))
{
// Swap enforcement to the left-hand signedness
static if (is(Lhs == bool))
{
alias R = typeof(mixin("lhs"~op~"payload"));
return mixin("Checked!(R, Hook)(R(lhs))"~op~"payload");
}
else static if (isFloatingPoint!Lhs)
return mixin("lhs"~op~"payload");
else
{
// Create hooked type on the lhs, let it handle things
return mixin("Checked!(Lhs, Hook)(lhs)"~op~"payload");
}
}
}
// representation
unittest
{
assert(Checked!(ubyte, void)(ubyte(22)).representation == 22);
}
/**
Force all overflows to fail with `assert(0)`.
*/
struct Croak
{
Dst onBadCast(Dst, Src)(Src src)
{
assert(0, "Bad cast");
}
bool onBadOpEquals(Lhs, Rhs)(Lhs lhs, Rhs rhs)
{
assert(0, "Bad comparison for equality");
}
bool onBadOpCmp(Lhs, Rhs)(Lhs lhs, Rhs rhs)
{
assert(0, "Bad comparison for ordering");
}
typeof(~lhs) onOverflow(string op, Lhs)(Lhs lhs)
{
assert(0);
}
typeof(lhs + rhs) onOverflow(string op, Lhs, Rhs)(Lhs lhs, Rhs rhs)
{
assert(0);
}
}
version(unittest) private struct CountOverflows
{
uint calls;
auto onOverflow(string op, Lhs)(Lhs lhs)
{
++calls;
return mixin(op~"lhs");
}
auto onOverflow(string op, Lhs, Rhs)(Lhs lhs, Rhs rhs)
{
++calls;
return mixin("lhs"~op~"rhs");
}
}
version(unittest) private struct CountOpBinary
{
uint calls;
auto hookOpBinary(string op, Lhs, Rhs)(Lhs lhs, Rhs rhs)
{
++calls;
return mixin("lhs"~op~"rhs");
}
}
// opBinary
@nogc nothrow pure @safe unittest
{
auto x = Checked!(int, void)(42), y = Checked!(int, void)(142);
assert(x + y == 184);
assert(x + 100 == 142);
assert(y - x == 100);
assert(200 - x == 158);
assert(y * x == 142 * 42);
assert(x / 1 == 42);
auto x1 = Checked!(int, CountOverflows)(42);
assert(x1 + 0 == 42);
assert(x1 + false == 42);
assert(is(typeof(x1 + 0.5) == double));
assert(x1 + 0.5 == 42.5);
assert(x1.hook.calls == 0);
assert(x1 + int.max == int.max + 42);
assert(x1.hook.calls == 1);
assert(x1 * 2 == 84);
assert(x1.hook.calls == 1);
assert(x1 / 2 == 21);
assert(x1.hook.calls == 1);
auto x2 = Checked!(int, CountOpBinary)(42);
assert(x2 + 1 == 43);
assert(x2.hook.calls == 1);
auto x3 = Checked!(uint, CountOverflows)(42u);
assert(x3 + 1 == 43);
assert(x3.hook.calls == 0);
assert(x3 - 1 == 41);
assert(x3.hook.calls == 0);
assert(x3 + (-42) == 0);
assert(x3.hook.calls == 0);
assert(x3 - (-42) == 84);
assert(x3.hook.calls == 0);
assert(x3 * 2 == 84);
assert(x3.hook.calls == 0);
assert(x3 * -2 == -84);
assert(x3.hook.calls == 1);
assert(x3 / 2 == 21);
assert(x3.hook.calls == 1);
assert(x3 / -2 == 0);
assert(x3.hook.calls == 2);
auto x4 = Checked!(int, CountOverflows)(42);
assert(x4 + 1 == 43);
assert(x4.hook.calls == 0);
assert(x4 + 1u == 43);
assert(x4.hook.calls == 0);
assert(x4 - 1 == 41);
assert(x4.hook.calls == 0);
assert(x4 * 2 == 84);
assert(x4.hook.calls == 0);
x4 = -2;
assert(x4 + 2u == 0);
assert(x4.hook.calls == 0);
assert(x4 * 2u == -4);
assert(x4.hook.calls == 1);
auto x5 = Checked!(int, CountOverflows)(3);
assert(x5 ^^ 0 == 1);
assert(x5 ^^ 1 == 3);
assert(x5 ^^ 2 == 9);
assert(x5 ^^ 3 == 27);
assert(x5 ^^ 4 == 81);
assert(x5 ^^ 5 == 81 * 3);
assert(x5 ^^ 6 == 81 * 9);
}
// opBinaryRight
@nogc nothrow pure @safe unittest
{
auto x1 = Checked!(int, CountOverflows)(42);
assert(1 + x1 == 43);
assert(true + x1 == 43);
assert(0.5 + x1 == 42.5);
auto x2 = Checked!(int, void)(42);
assert(x1 + x2 == 84);
assert(x2 + x1 == 84);
}
unittest
{
Checked!(int, void) x;
x = 42;
assert(x.payload == 42);
x = x;
assert(x.payload == 42);
x = short(43);
assert(x.payload == 43);
x = ushort(44);
assert(x.payload == 44);
}
unittest
{
static assert(!is(typeof(Checked!(short, void)(ushort(42)))));
static assert(!is(typeof(Checked!(int, void)(long(42)))));
static assert(!is(typeof(Checked!(int, void)(ulong(42)))));
assert(Checked!(short, void)(short(42)).payload == 42);
assert(Checked!(int, void)(ushort(42)).payload == 42);
}
// opCast
@nogc nothrow pure @safe unittest
{
static assert(is(typeof(cast(float) Checked!(int, void)(42)) == float));
assert(cast(float) Checked!(int, void)(42) == 42);
assert(is(typeof(cast(long) Checked!(int, void)(42)) == long));
assert(cast(long) Checked!(int, void)(42) == 42);
static assert(is(typeof(cast(long) Checked!(uint, void)(42u)) == long));
assert(cast(long) Checked!(uint, void)(42u) == 42);
auto x = Checked!(int, void)(42);
if (x) {} else assert(0);
x = 0;
if (x) assert(0);
static struct Hook1
{
uint calls;
Dst hookOpCast(Dst, Src)(Src value)
{
++calls;
return 42;
}
}
auto y = Checked!(long, Hook1)(long.max);
assert(cast(int) y == 42);
assert(y.hook.calls == 1);
static struct Hook2
{
uint calls;
Dst onBadCast(Dst, Src)(Src value)
{
++calls;
return 42;
}
}
auto x1 = Checked!(uint, Hook2)(100u);
assert(cast(ushort) x1 == 100);
assert(x1.hook.calls == 0);
assert(cast(int) x1 == 100);
assert(x1.hook.calls == 0);
x1 = uint.max;
assert(cast(int) x1 == 42);
assert(x1.hook.calls == 1);
}
// opEquals
@nogc nothrow pure @safe unittest
{
assert(Checked!(int, void)(42) == 42L);
assert(42UL == Checked!(int, void)(42));
static struct Hook1
{
uint calls;
bool hookOpEquals(Lhs, Rhs)(Lhs lhs, Rhs rhs)
{
++calls;
return lhs != rhs;
}
}
auto x1 = Checked!(int, Hook1)(100);
assert(x1 == Checked!(long, Hook1)(100));
assert(x1.hook.calls == 0);
assert(x1 != 100u);
assert(x1.hook.calls == 1);
static struct Hook2
{
uint calls;
bool onBadOpEquals(Lhs, Rhs)(Lhs lhs, Rhs rhs)
{
++calls;
return true;
}
}
auto x2 = Checked!(int, Hook2)(-100);
assert(x2 == -100);
assert(x2.hook.calls == 0);
assert(x2 == cast(uint) -100);
assert(x2.hook.calls == 1);
auto x3 = Checked!(uint, Hook2)(100u);
assert(x3 == 100);
assert(x2 != x3);
}
// opCmp
@nogc nothrow pure @safe unittest
{
Checked!(int, void) x;
assert(x <= x);
assert(x < 45);
assert(x < 45u);
assert(x > -45);
assert(x < 44.2);
assert(x > -44.2);
assert(!(x < double.init));
assert(!(x > double.init));
assert(!(x <= double.init));
assert(!(x >= double.init));
static struct Hook1
{
uint calls;
int hookOpCmp(Lhs, Rhs)(Lhs lhs, Rhs rhs)
{
++calls;
return 0;
}
}
auto x1 = Checked!(int, Hook1)(42);
assert(!(x1 < 43u));
assert(!(43u < x1));
assert(x1.hook.calls == 2);
static struct Hook2
{
uint calls;
int onBadOpCmp(Lhs, Rhs)(Lhs lhs, Rhs rhs)
{
++calls;
return 0;
}
}
auto x2 = Checked!(int, Hook2)(-42);
assert(!(x2 < 43u));
assert(!(43u > x2));
assert(x2.hook.calls == 2);
x2 = 42;
assert(x2 > 41u);
auto x3 = Checked!(uint, Hook2)(42u);
assert(x3 > 41);
assert(!(x3 > -41));
}
// opUnary
@nogc nothrow pure @safe unittest
{
auto x = Checked!(int, void)(42);
assert(x == +x);
static assert(is(typeof(-x) == typeof(x)));
assert(-x == Checked!(int, void)(-42));
static assert(is(typeof(~x) == typeof(x)));
assert(~x == Checked!(int, void)(~42));
static struct Hook1
{
uint calls;
auto hookOpUnary(string op, T)(T value) if (op == "-")
{
++calls;
return T(42);
}
auto hookOpUnary(string op, T)(T value) if (op == "~")
{
++calls;
return T(43);
}
}
auto x1 = Checked!(int, Hook1)(100);
assert(is(typeof(-x1) == typeof(x1)));
assert(-x1 == Checked!(int, Hook1)(42));
assert(is(typeof(~x1) == typeof(x1)));
assert(~x1 == Checked!(int, Hook1)(43));
assert(x1.hook.calls == 2);
static struct Hook2
{
uint calls;
auto hookOpUnary(string op, T)(ref T value) if (op == "++")
{
++calls;
--value;
}
auto hookOpUnary(string op, T)(ref T value) if (op == "--")
{
++calls;
++value;
}
}
auto x2 = Checked!(int, Hook2)(100);
assert(++x2 == 99);
assert(x2 == 99);
assert(--x2 == 100);
assert(x2 == 100);
auto x3 = Checked!(int, CountOverflows)(int.max - 1);
assert(++x3 == int.max);
assert(x3.hook.calls == 0);
assert(++x3 == int.min);
assert(x3.hook.calls == 1);
x3 = int.min + 1;
assert(--x3 == int.min);
assert(x3.hook.calls == 1);
assert(--x3 == int.max);
assert(x3.hook.calls == 2);
}
//
@nogc nothrow pure @safe unittest
{
Checked!(int, void) x;
assert(x == x);
assert(x == +x);
assert(x == -x);
++x;
assert(x == 1);
x++;
assert(x == 2);
x = 42;
assert(x == 42);
short _short = 43;
x = _short;
assert(x == _short);
ushort _ushort = 44;
x = _ushort;
assert(x == _ushort);
assert(x == 44.0);
assert(x != 44.1);
assert(x < 45);
assert(x < 44.2);
assert(x > -45);
assert(x > -44.2);
assert(cast(long) x == 44);
assert(cast(short) x == 44);
Checked!(uint, void) y;
assert(y <= y);
assert(y == 0);
assert(y < x);
x = -1;
assert(x > y);
}
void main(){}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment