Compile time metaprogramming

simendsjo simendsjo at gmail.com
Fri Dec 2 13:00:44 PST 2011

```I had a little talk with one of my teachers regarding the culprits of
wrong unit assumptions in code
(http://mars.jpl.nasa.gov/msp98/news/mco990930.html).

We had a little different views on how much pain it would be for
developers to code using an SI library, so I hacked together a small
proof-of-concept in D to show how D's metaprogramming could make things
a lot less painful than, say, Java.

I thought I could share it here too even though it's just a small hack

import std.traits, std.stdio, std.conv;

void main() {
auto f = feet(10);
auto m = meter(10.0); // kan fint bruke desimaltall

// en type til en annen - konvertering
// kan gjerne bruke /, * e.l. også - metaprogrammering er en god
ting :)
auto feetMeter = f + m;
assert( feetMeter.siunit == "feet" );
assert( feetMeter >= feet(42.808) &&
feetMeter <= feet(42.809) );

auto meterFeet = m + f;
assert( meterFeet.siunit == "meter" );
assert( meterFeet >= meter(13.0475) &&
meterFeet <= meter(13.0485) );

// kan også bruke skalarer - og fint mikse floats inn i det
assert( m * 2.2 == meter(22) );

// eller konvertere direkte
auto f2 = meterFeet.to!"feet"();
assert( f2.siunit == "feet" );
assert( f2 >= feet(42.808) &&
f2 <= feet(42.809) );

// men kan ikke konvertere uten konverteringsfunksjoner
static assert(!__traits(compiles, m.to!"finnes ikke"()) );

// men kan ikke manipulere urelaterte typer
static assert(!__traits(compiles, feet(10) + celsius(10)) );

// kan caste til kompatible typer
double d = cast(double)m;
assert( d == 10.0 );

// eller sammenlikne direkte
assert( m == 10 );
assert( m > 9 );
assert( m < 11 );
}

auto meter(V)(V value) pure nothrow @safe {
return Value!("meter", V)(value);
}

auto feet(V)(V value) pure nothrow @safe {
return Value!("feet", V)(value);
}

auto celsius(V)(V value) pure nothrow @safe {
return Value!("celsius", V)(value);
}

auto fahrenheit(V)(V value) pure nothrow @safe {
return Value!("fahrenheit", V)(value);
}

auto SIConvert(string S, string D, T)(T value) pure nothrow @safe
if(S == "meter" && D == "feet") {
return value / 0.3048;
}

auto SIConvert(string S, string D, T)(T value) pure nothrow @safe
if(S == "feet" && D == "meter") {
return value * 0.3048;
}

auto SIConvert(string S, string D, T)(T value) pure nothrow @safe
if(S == "celsius" && D == "fahrenheit") {
return (value - 32) * (5/9);
}

auto SIConvert(string S, string D, T)(T value) pure nothrow @safe
if(S == "fahrenheit" && D == "celsius") {
return value * (9/5) + 32;
}

template isSIValue(T) {
// HACK: Sjekker kun om det har en unit verdi...
static if( is(typeof(T.siunit) : string) )
enum isSIValue = true;
else
enum isSIValue = false;
}

auto SIConvert(S, D, T)(T value) pure nothrow @safe
if(isSIValue!S && isSIValue!D) {
static if( S.siunit == D.siunit )
return value;
else {
static assert(__traits(compiles, SIConvert!(S.siunit, D.siunit,
T)(value)),
"Cannot convert value of type "~T.stringof~
" from "~S.siunit~" to "~D.siunit);
return SIConvert!(S.siunit, D.siunit, T)(value);
}
}

struct Value(string SIUnit, V) if(isNumeric!V) {
enum siunit = SIUnit; // finnes kun ved kompilering
immutable V value;

this(V)(V value) pure nothrow @safe {
this.value = value;
}

auto opBinary(string op, RHS)(RHS r) const nothrow pure @safe
if(isSIValue!RHS) {
auto converted = SIConvert!(RHS, typeof(this))(r.value);
mixin("auto newval = value "~op~" converted;");
return Value!(siunit, typeof(newval))(newval);
}

auto opBinary(string op, RHS)(const RHS r) const nothrow pure @safe
if(isNumeric!RHS) {
mixin("auto val = value "~op~" r;");
return Value!(siunit, typeof(val))(val);
}

auto to(string unit)() {
auto val = SIConvert!(siunit, unit, typeof(value))(value);
return Value!(unit, typeof(val))(val);
}

T opCast(T)() const pure nothrow @safe
if(is(typeof(value) : T)) {
return cast(T)value;
}

bool opEquals(T)(const T o) const pure nothrow @safe
if(isSIValue!T) {
return o.siunit == siunit && o.value == value;
}

bool opEquals(T)(const T o) const pure nothrow @safe
if(is(T : typeof(value))) {
return value == o;
}

int opCmp(T)(const T v) const pure nothrow @safe
if(isSIValue!T) {
static assert(T.siunit == siunit, "Cannot compare "~siunit~" to
"~T.siunit~
". Try converting the value first");
return value < v ? -1 : (value > v ? 1 : 0);
}

int opCmp(T)(const T o) const pure nothrow @safe
if(isNumeric!T) {
return value < o ? -1 : (value > o ? 1 : 0);
}

string toString() const @trusted {
return .to!string(value)~" "~siunit;
}
}

```