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 
with Norwegian comments.


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;
     }
}



More information about the Digitalmars-d-learn mailing list