import std.conv; import std.traits; class BoundedOverflowException : Exception { this(string msg) { super(msg); } } struct Bounded(T, T min, T max) { T _payload; static string runCheckCode() { return q{ static if(isIntegral!T) { static if(isSigned!T) asm { jo overflow; } else // is unsigned asm { // unsigned does not set the overflow flag, but will set // the carry flag. Consider the case of ubyte 255 += 1. // 11111111 + 00000001 == 00000000. The processor sees // that as the same as -1 + 1, which is fine... but we don't // want it. The carry flag will be set there though, so we good // with that. jc overflow; } } else { // FIXME: check some other way } if(_payload < min) goto overflow; if(_payload > max) goto overflow; goto ok; overflow: throw new BoundedOverflowException("Overflow at "~file~":"~to!string(line)~" (payload: " ~ to!string(_payload) ~ ")"); ok: ; }; } T opUnary(string op, string file = __FILE__, int line = __LINE__)() { mixin("_payload " ~ op ~ ";"); mixin(runCheckCode()); } T opBinary(string op, string file = __FILE__, int line = __LINE__)(T rhs) { T tmp = void; mixin("tmp = _payload " ~ op ~ "rhs;"); _payload = tmp; mixin(runCheckCode()); return tmp; } // FIXME: a += 1 might be optimized into inc a, which doesn't set the carry flag! T opOpAssign(string op, string file = __FILE__, int line = __LINE__)(T rhs) { T tmp = void; mixin("tmp = _payload " ~ op ~ "rhs;"); _payload = tmp; mixin(runCheckCode()); return _payload; } T opAssign(T rhs, string file = __FILE__, int line = __LINE__) { _payload = rhs; mixin(runCheckCode()); return _payload; } string toString() { return to!string(_payload); } alias _payload this; } import std.stdio; void main() { Bounded!(uint, uint.min, uint.max) a2; a2 = uint.max; a2 += 2; Bounded!(int, int.min, int.max) a; a = int.max; a += 5; writefln("%s", a); a += 5; writefln("%s", a); a += 5; }