Integer promotion issue

H. S. Teoh hsteoh at quickfur.ath.cx
Sun Jan 26 00:22:43 UTC 2020


On Sun, Jan 26, 2020 at 12:07:47AM +0000, Evan via Digitalmars-d wrote:
> On Saturday, 25 January 2020 at 23:58:00 UTC, Steven Schveighoffer wrote:
> > On 1/25/20 6:46 PM, Evan wrote:
> > > [...]
> > 
> > Not really. You can cast yourself, write your own type that does it,
> > or use masking to have the compiler accept the result (i.e. (a + b)
> > & 0xff).
> > 
> > I'd recommend your own type, as it will look a lot cleaner.
> > 
> > -Steve
> 
> If I make my own type then I have to write more casts from my type to
> built in types. I want to create less work for myself not more.

No you don't.  Check this out (put it in a file called "nopromote.d" and
import it):

	module nopromote;

	enum isNarrowInt(T) = is(T : int) || is(T : uint);

	/**
	 * A wrapper around a built-in narrow int that truncates the
	 * result of arithmetic operations to the narrow type,
	 * overriding built-in int promotion rules.
	 */
	struct Np(T)
	    if (isNarrowInt!T)
	{
	    T impl;
	    alias impl this;

	    /**
	     * Truncating binary operator.
	     */
	    Np opBinary(string op, U)(U u)
		if (is(typeof((T x, U y) => mixin("x " ~ op ~ " y"))))
	    {
		return Np(cast(T) mixin("this.impl " ~ op ~ " u"));
	    }

	    /**
	     * Truncating unary operator.
	     */
	    Np opUnary(string op)()
		if (is(typeof((T x) => mixin(op ~ "cast(int) x"))))
	    {
		return Np(cast(T) mixin(op ~ " cast(int) this.impl"));
	    }

	    /**
	     * Infectiousness: any expression containing Np should
	     * automatically use Np operator semantics.
	     */
	    Np opBinaryRight(string op, U)(U u)
		if (is(typeof((T x, U y) => mixin("x " ~ op ~ " y"))))
	    {
		return Np(cast(T) mixin("u " ~ op ~ " this.impl"));
	    }
	}

	/**
	 * Returns: A lightweight wrapped type that overrides built-in
	 * arithmetic operators to always truncate to the given type
	 * without promoting to int or uint.
	 */
	auto np(T)(T t)
	    if (isNarrowInt!T)
	{
	    return Np!T(t);
	}

	// Test binary ops
	@safe unittest
	{
	    ubyte x = 1;
	    ubyte y = 2;
	    auto z = x.np + y;
	    static assert(is(typeof(z) : ubyte));
	    assert(z == 3);

	    byte zz = x.np + y;
	    assert(zz == 3);

	    x = 255;
	    z = x.np + y;
	    assert(z == 1);
	}

	@safe unittest
	{
	    byte x = 123;
	    byte y = 5;
	    auto z = x.np + y;
	    static assert(is(typeof(z) : byte));
	    assert(z == byte.min);

	    byte zz = x.np + y;
	    assert(zz == byte.min);
	}

	@safe unittest
	{
	    import std.random;
	    short x = cast(short) uniform(0, 10);
	    short y = 10;
	    auto z = x.np + y;
	    static assert(is(typeof(z) : short));
	    assert(z == x + 10);

	    short s = x.np + y;
	    assert(s == x + 10);
	}

	// Test unary ops
	@safe unittest
	{
	    byte b = 10;
	    auto c = -b.np;
	    static assert(is(typeof(c) : byte));
	    assert(c == -10);

	    ubyte ub = 16;
	    auto uc = -ub.np;
	    static assert(is(typeof(uc) : ubyte));
	    assert(uc == 0xF0);
	}

	version(unittest)
	{
	    // These tests are put here as actual module functions, to
	    // force optimizer not to discard calls to these functions,
	    // so that we can see the actual generated code.
	    byte byteNegate(byte b) { return -b.np; }
	    ubyte ubyteNegate(ubyte b) { return -b.np; }

	    byte byteTest1(int choice, byte a, byte b)
	    {
		if (choice == 1)
		    return a.np + b;
		if (choice == 2)
		    return a.np / b;
		assert(0);
	    }

	    short shortAdd(short a, short b) { return a.np + b; }

	    // Test opBinaryRight
	    byte byteRightTest(byte a, byte c)
	    {
		auto result = a + c.np;
		static assert(is(typeof(result) : byte));
		return result;
	    }

	    unittest
	    {
		assert(byteRightTest(127, 1) == byte.min);
	    }

	    short multiTest1(short x, short y)
	    {
		return short(2) + 2*(x - y.np);
	    }

	    unittest
	    {
		// Test wraparound semantics.
		assert(multiTest1(32767, 16384) == short.min);
	    }

	    short multiTest2(short a, short b)
	    {
		short x = a;
		short y = b;
		return (2*x + 1) * (y.np/2 - 1);
	    }

	    unittest
	    {
		assert(multiTest2(1, 4) == 3);
	    }
	}

As the unittests show, all you need to do is to insert .np somewhere in
your arithmetic expression, and it automatically "taints" the expression
to be the given narrow type and returns a narrow type as result. No
casts are needed in user code.

I use this module to work around silly archaic C/C++ integer promotion
rules that Walter insists D must have.

Hope this helps.


T

-- 
Дерево держится корнями, а человек - друзьями.


More information about the Digitalmars-d mailing list