Last update June 7, 2007

Daniel Keep /
Variant



Difference (last change) (Author, normal page display)

Changed: 515,519c515,516

// Everything else will be passed on to the std.format.doFormat
// function. To do that, we need to fake the _arguments and _argptr
// vararg arguments.
version( Tango )
// Special case: strings (for h3r3tic)
else if( type is typeid(char[]) )

Changed: 521,524c518
TypeInfo?[1] tis = [type];
void* args = this.data.ptr;

return layout.convert(tis, args, "{}");
return get!(char[]);

Added: 525a520,528
else if( type is typeid(wchar[]) )
{
return .toString(get!(wchar[]));
}
else if( type is typeid(dchar[]) )
{
return .toString(get!(dchar[]));
}
// Everything else

Changed: 528,530c531
char[] result;
// Special-cased strings to make h3t3ric happy.
if( isAny(type, typeid(char[]), typeid(wchar[]), typeid(dchar[])) )
version( Tango )

Changed: 532,543c533,536
TypeInfo?[2] tis = [typeid(char[]), type];
void[] data;
char[] fs = "%s";

void[] vdata = this.data;
data = new void[(int[]).sizeof + vdata.length];
scope(exit) delete data;

data[0..(int[]).sizeof] = (cast(void*)&fs)[0..fs.sizeof];
data[(int[]).sizeof..$] = vdata;

doFormat((dchar c){encode(result,c);}, tis, data.ptr);
TypeInfo?[1] tis = [type];
void* args = this.data.ptr;

return layout.convert(tis, args, "{}");

Added: 546a540
char[] result;

Added: 548a543
return result;

Deleted: 550d544
return result;

Here is a fairly complete implementation of a Variant type for D. It's basically an easier-to-use version of Box. It works with Phobos and mostly fine with Tango (Tango is missing some string formatters that cause the unit tests to fail, but other than that it should be fine).

Here's a simple example:

Variant v; // Variants initialise to "void" containers
v = 34; // You can change the value simply by assigning to a variant
v = v + 4; // Almost all operators are overloaded...
v += 4; // ...including opOpAssign versions.
writefln(v); // Variants also have a sane toString.

Here is the source. Any suggestions, improvements or fixes are welcome. Please email me with any changes (email is on my user page).

/**
 * Variant
 * Written by Daniel Keep.
 *
 * I hereby release this code to the Public Domain; share and enjoy.
 */
module variant;

version( Tango )
{
    pragma(msg, `
    ** WARNING **

The Variant module does not fully support Tango.  Tango lacks string
formatters for some basic types, and thus does not pass the unit tests.
`);

    import tango.core.Exception : TracedException;
    import tango.core.Vararg : va_list;
    import tango.text.convert.Float;
    import tango.text.convert.Integer;
    import tango.text.convert.Layout : Layout;
    import tango.text.convert.Utf;

    private
    {
        alias tango.text.convert.Float.toUtf8 toUtf8;
        alias tango.text.convert.Integer.toUtf8 toUtf8;
        alias tango.text.convert.Utf.toUtf8 toUtf8;
    
        char[] toUtf8(bool v)
        {
            return v ? "true" : "false";
        }
    
        alias toUtf8 toString;
    
        Layout!(char) layout;
    }

    static this()
    {
        layout = new Layout!(char);
    }
}
else
{
    import std.format : doFormat;
    import std.stdarg : va_list;
    import std.string : toString;
    import std.utf : encode;
}

private template maxT(uint a, uint b)
{
    const maxT = (a > b) ? a : b;
}

private struct AtomicTypes
{
    union
    {
        bool _bool;
        char _char;
        wchar _wchar;
        dchar _dchar;
        byte _byte;
        short _short;
        int _int;
        long _long;
        ubyte _ubyte;
        ushort _ushort;
        uint _uint;
        ulong _ulong;
        float _float;
        double _double;
        real _real;
        ifloat _ifloat;
        idouble _idouble;
        ireal _ireal;
        void* ptr;
        void[] arr;
        Object obj;
        ubyte[maxT!(_real.sizeof,arr.sizeof)] data;
    }
}

private template isAtomicType(T)
{
    static if( is( T == bool )
            || is( T == char )
            || is( T == wchar )
            || is( T == dchar )
            || is( T == byte )
            || is( T == short )
            || is( T == int )
            || is( T == long )
            || is( T == ubyte )
            || is( T == ushort )
            || is( T == uint )
            || is( T == ulong )
            || is( T == float )
            || is( T == double )
            || is( T == real )
            || is( T == ifloat )
            || is( T == idouble )
            || is( T == ireal ) )
        const isAtomicType = true;
    else
        const isAtomicType = false;
}

private template isArray(T)
{
    static if( is( T U : U[] ) )
        const isArray = true;
    else
        const isArray = false;
}

private template isPointer(T)
{
    static if( is( T U : U* ) )
        const isPointer = true;
    else
        const isPointer = false;
}

private template isObject(T)
{
    static if( is( T : Object ) )
        const isObject = true;
    else
        const isObject = false;
}

private template isStaticArray(T)
{
    static if( is( typeof(T.init)[(T).sizeof / typeof(T.init).sizeof] == T ) )
        const isStaticArray = true;
    else
        const isStaticArray = false;
}

private bool isAny(T,argsT...)(T v, argsT args)
{
    foreach( arg ; args )
        if( v is arg ) return true;
    return false;
}

private
{
    const tibool = typeid(bool);
    const tichar = typeid(char);
    const tiwchar = typeid(wchar);
    const tidchar = typeid(dchar);
    const tibyte = typeid(byte);
    const tishort = typeid(short);
    const tiint = typeid(int);
    const tilong = typeid(long);
    const tiubyte = typeid(ubyte);
    const tiushort = typeid(ushort);
    const tiuint = typeid(uint);
    const tiulong = typeid(ulong);
    const tifloat = typeid(float);
    const tidouble = typeid(double);
    const tireal = typeid(real);
    const tiifloat = typeid(ifloat);
    const tiidouble = typeid(idouble);
    const tiireal = typeid(ireal);
}

private bool canImplicitCastTo(dsttypeT)(TypeInfo srctype)
{
    static if( is( dsttypeT == char ) )
        return isAny(srctype, tibool, tiubyte);

    else static if( is( dsttypeT == wchar ) )
        return isAny(srctype, tibool, tiubyte, tiushort, tichar);

    else static if( is( dsttypeT == dchar ) )
        return isAny(srctype, tibool, tiubyte, tiushort, tiuint, tichar,
                tiwchar);

    else static if( is( dsttypeT == byte ) )
        return isAny(srctype, tibool);

    else static if( is( dsttypeT == ubyte ) )
        return isAny(srctype, tibool, tichar);

    else static if( is( dsttypeT == short ) )
        return isAny(srctype, tibool, tibyte, tiubyte, tichar);

    else static if( is( dsttypeT == ushort ) )
        return isAny(srctype, tibool, tibyte, tiubyte, tichar, tiwchar);

    else static if( is( dsttypeT == int ) )
        return isAny(srctype, tibool, tibyte, tiubyte, tishort, tiushort,
                tichar, tiwchar);

    else static if( is( dsttypeT == uint ) )
        return isAny(srctype, tibool, tibyte, tiubyte, tishort, tiushort,
                tichar, tiwchar, tidchar);

    else static if( is( dsttypeT == long ) || is( dsttypeT == ulong ) )
        return isAny(srctype, tibool, tibyte, tiubyte, tishort, tiushort,
                    tiint, tiuint, tichar, tiwchar, tidchar);

    else static if( is( dsttypeT == float ) )
        return isAny(srctype, tibool, tibyte, tiubyte);

    else static if( is( dsttypeT == double ) )
        return isAny(srctype, tibool, tibyte, tiubyte, tifloat);

    else static if( is( dsttypeT == real ) )
        return isAny(srctype, tibool, tibyte, tiubyte, tifloat, tidouble);

    else static if( is( dsttypeT == idouble ) )
        return isAny(srctype, tiifloat);

    else static if( is( dsttypeT == ireal ) )
        return isAny(srctype, tiifloat, tiidouble);

    else
        return false;
}

version( Tango )
    alias TracedException BaseException;
else
    alias Exception BaseException;

class VariantTypeMismatchException : BaseException
{
    this(TypeInfo expected, TypeInfo got)
    {
        version( Tango )
            super("cannot convert "~expected.toUtf8
                    ~" value to a "~got.toUtf8);
        else
            super("cannot convert "~expected.toString
                    ~" value to a "~got.toString);
    }
}

private template storageT(T)
{
    static if( isStaticArray!(T) )
        alias typeof(T.dup) storageT;
    else
        alias T storageT;
}

struct Variant
{
    TypeInfo type = typeid(void);
    AtomicTypes value;

    static Variant opCall(T)(T value)
    {
        Variant _this;

        static if( isStaticArray!(T) )
            _this = value.dup;

        else
            _this = value;

        return _this;
    }

    Variant opAssign(T)(T value)
    {
        static if( isStaticArray!(T) )
        {
            return (*this = value.dup);
        }
        else
        {
            type = typeid(T);

            static if( isAtomicType!(T) )
            {
                mixin("this.value._"~T.stringof~"=value;");
            }
            else static if( isArray!(T) )
            {
                this.value.arr = (cast(void*)value.ptr)
                    [0 .. value.length];
            }
            else static if( isPointer!(T) )
            {
                this.value.ptr = cast(void*)T;
            }
            else static if( isObject!(T) )
            {
                this.value.obj = T;
            }
            else
            {
                if( T.sizeof <= this.value.data.length )
                {
                    this.value.data[0..T.sizeof] =
                        (cast(ubyte*)&value)[0..T.sizeof];
                }
                else
                {
                    auto buffer = (cast(ubyte*)&value)[0..T.sizeof].dup;
                    this.value.arr = cast(void[])buffer;
                }
            }
            return *this;
        }
    }

    bool isA(T)()
    {
        return cast(bool)(typeid(T) is type);
    }

    bool isImplicitly(T)()
    {
        return ( cast(bool)(typeid(T) is type)
                || canImplicitCastTo!(T)(type) );
    }

    storageT!(S) get(S)()
    {
        alias storageT!(S) T;

        if( type !is typeid(T)
                // Let D do runtime check itself
                && !isObject!(T)
                // Allow implicit upcasts
                && !canImplicitCastTo!(T)(type)
          )
            throw new VariantTypeMismatchException(type,typeid(T));

        static if( isAtomicType!(T) )
        {
            if( type is typeid(T) )
            {
                return mixin("this.value._"~T.stringof);
            }
            else
            {
                if( type is tibool ) return cast(T)this.value._bool;
                else if( type is tichar ) return cast(T)this.value._char;
                else if( type is tiwchar ) return cast(T)this.value._wchar;
                else if( type is tidchar ) return cast(T)this.value._dchar;
                else if( type is tibyte ) return cast(T)this.value._byte;
                else if( type is tishort ) return cast(T)this.value._short;
                else if( type is tiint ) return cast(T)this.value._int;
                else if( type is tilong ) return cast(T)this.value._long;
                else if( type is tiubyte ) return cast(T)this.value._ubyte;
                else if( type is tiushort ) return cast(T)this.value._ushort;
                else if( type is tiuint ) return cast(T)this.value._uint;
                else if( type is tiulong ) return cast(T)this.value._ulong;
                else if( type is tifloat ) return cast(T)this.value._float;
                else if( type is tidouble ) return cast(T)this.value._double;
                else if( type is tireal ) return cast(T)this.value._real;
                else if( type is tiifloat ) return cast(T)this.value._ifloat;
                else if( type is tiidouble ) return cast(T)this.value._idouble;
                else if( type is tiireal ) return cast(T)this.value._ireal;
                else
                    throw new VariantTypeMismatchException(type,typeid(T));
            }
        }
        else static if( isArray!(T) )
        {
            return (cast(typeof(T[0])*)this.value.arr.ptr)
                [0 .. this.value.arr.length];
        }
        else static if( isPointer!(T) )
        {
            return cast(T)this.value.ptr;
        }
        else static if( isObject!(T) )
        {
            return cast(T)this.value.obj;
        }
        else
        {
            if( T.sizeof <= this.value.data.length )
            {
                T result;
                (cast(ubyte*)&result)[0..T.sizeof] =
                    this.value.data[0..T.sizeof];
                return result;
            }
            else
            {
                T result;
                (cast(ubyte*)&result)[0..T.sizeof] =
                    (cast(ubyte[])this.value.arr)[0..T.sizeof];
                return result;
            }
        }
        assert(false);
    }

    typeof(T+T) opAdd(T)(T rhs)     { return get!(T) + rhs; }
    typeof(T+T) opAdd_r(T)(T lhs)   { return lhs + get!(T); }
    typeof(T-T) opSub(T)(T rhs)     { return get!(T) - rhs; }
    typeof(T-T) opSub_r(T)(T lhs)   { return lhs - get!(T); }
    typeof(T*T) opMul(T)(T rhs)     { return get!(T) * rhs; }
    typeof(T*T) opMul_r(T)(T lhs)   { return lhs * get!(T); }
    typeof(T/T) opDiv(T)(T rhs)     { return get!(T) / rhs; }
    typeof(T/T) opDiv_r(T)(T lhs)   { return lhs / get!(T); }
    typeof(T%T) opMod(T)(T rhs)     { return get!(T) % rhs; }
    typeof(T%T) opMod_r(T)(T lhs)   { return lhs % get!(T); }
    typeof(T&T) opAnd(T)(T rhs)     { return get!(T) & rhs; }
    typeof(T&T) opAnd_r(T)(T lhs)   { return lhs & get!(T); }
    typeof(T|T) opOr(T)(T rhs)      { return get!(T) | rhs; }
    typeof(T|T) opOr_r(T)(T lhs)    { return lhs | get!(T); }
    typeof(T^T) opXor(T)(T rhs)     { return get!(T) ^ rhs; }
    typeof(T^T) opXor_r(T)(T lhs)   { return lhs ^ get!(T); }
    typeof(T<<T) opShl(T)(T rhs)    { return get!(T) << rhs; }
    typeof(T<<T) opShl_r(T)(T lhs)  { return lhs << get!(T); }
    typeof(T>>T) opShr(T)(T rhs)    { return get!(T) >> rhs; }
    typeof(T>>T) opShr_r(T)(T lhs)  { return lhs >> get!(T); }
    typeof(T>>>T) opUShr(T)(T rhs)  { return get!(T) >>> rhs; }
    typeof(T>>>T) opUShr_r(T)(T lhs){ return lhs >>> get!(T); }
    typeof(T~T) opCat(T)(T rhs)     { return get!(T) ~ rhs; }
    typeof(T~T) opCat_r(T)(T lhs)   { return lhs ~ get!(T); }

    Variant opAddAssign(T)(T value) { return (*this = get!(T) + value); }
    Variant opSubAssign(T)(T value) { return (*this = get!(T) - value); }
    Variant opMulAssign(T)(T value) { return (*this = get!(T) * value); }
    Variant opDivAssign(T)(T value) { return (*this = get!(T) / value); }
    Variant opModAssign(T)(T value) { return (*this = get!(T) % value); }
    Variant opAndAssign(T)(T value) { return (*this = get!(T) & value); }
    Variant opOrAssign(T)(T value)  { return (*this = get!(T) | value); }
    Variant opXorAssign(T)(T value) { return (*this = get!(T) ^ value); }
    Variant opShlAssign(T)(T value) { return (*this = get!(T) << value); }
    Variant opShrAssign(T)(T value) { return (*this = get!(T) >> value); }
    Variant opUShrAssign(T)(T value){ return (*this = get!(T) >>> value); }
    Variant opCatAssign(T)(T value) { return (*this = get!(T) ~ value); }

    int opEquals(T)(T rhs)
    {
        static if( is( T == Variant ) )
            return opEqualsVariant(rhs);
        else
            return get!(T) == rhs;
    }
    int opCmp(T)(T rhs)
    {
        static if( is( T == Variant ) )
            return opCmpVariant(rhs);
        else
        {
            auto lhs = get!(T);
            return (lhs < rhs) ? -1 : (lhs == rhs) ? 0 : 1;
        }
    }
    hash_t toHash()
    {
        return type.getHash(data.ptr);
    }

    private int opEqualsVariant(Variant rhs)
    {
        if( type != rhs.type ) return false;
        return cast(bool) type.equals(data.ptr, rhs.data.ptr);
    }

    private int opCmpVariant(Variant rhs)
    {
        if( type != rhs.type )
            throw new VariantTypeMismatchException(type, rhs.type);
        return type.compare(data.ptr, rhs.data.ptr);
    }

    private void[] data()
    {
        if( type.tsize <= value.data.length )
            return cast(void[])(value.data);
        else
            return value.arr;
    }

    version( Tango )
    {
        alias toStringImpl toUtf8;
        version( PhobosCompatibility )
            alias toStringImpl toString;
    }
    else
        alias toStringImpl toString;

    private char[] toStringImpl()
    {
        // Special case: void type
        if( type is typeid(void) )
        {
            return "Variant.init";
        }
        // Special case: strings (for h3r3tic)
        else if( type is typeid(char[]) )
        {
            return get!(char[]);
        }
        else if( type is typeid(wchar[]) )
        {
            return .toString(get!(wchar[]));
        }
        else if( type is typeid(dchar[]) )
        {
            return .toString(get!(dchar[]));
        }
        // Everything else
        else
        {
            version( Tango )
            {
                TypeInfo[1] tis = [type];
                void* args = this.data.ptr;
    
                return layout.convert(tis, args, "{}");
            }
            else
            {
                char[] result;
                TypeInfo[1] tis = [type];
                doFormat((dchar c){encode(result,c);}, tis, this.data.ptr);
                return result;
            }
        }
    }
}

version( Tango )
{
    version( PhobosCompatibility ) {}
    else
        version = Tango_NoUnittests;
}

version( Tango_NoUnittests )
{
    unittest
    {
        pragma(msg,
`    ** WARNING **

The Variant unittests cannot currently be run under Tango without the
PhobosCompatibility version flag.  Why?  Because I can't be bothered to write
all the unit tests out twice just because Tango uses toUtf8 instead of
toString.
`);
    }
}
else
{
    unittest
    {
        Variant v;
        assert( v.isA!(void), v.type.toString );

        v = 42;
        assert( v.isA!(int), v.type.toString );
        assert( v.isImplicitly!(long), v.type.toString );
        assert( v.isImplicitly!(ulong), v.type.toString );
        assert( !v.isImplicitly!(uint), v.type.toString );
        assert( v.get!(int) == 42 );
        assert( v.get!(long) == 42L );
        assert( v.get!(ulong) == 42uL );

        v = "Hello, World!"c;
        assert( v.isA!(char[]), v.type.toString );
        assert( !v.isImplicitly!(wchar[]), v.type.toString );
        assert( v.get!(char[]) == "Hello, World!" );

        v = [1,2,3,4,5];
        assert( v.isA!(int[]), v.type.toString );
        assert( v.get!(int[]) == [1,2,3,4,5] );

        v = 3.1413;
        assert( v.isA!(double), v.type.toString );
        assert( v.isImplicitly!(real), v.type.toString );
        assert( !v.isImplicitly!(float), v.type.toString );
        assert( v.get!(double) == 3.1413 );
        
        auto u = Variant(v);
        assert( u.isA!(double), u.type.toString );
        assert( u.get!(double) == 3.1413 );

        v = 38;
        assert( v + 4 == 42 );
        assert( 4 + v == 42 );
        assert( v - 4 == 34 );
        assert( 4 - v == -34 );
        assert( v * 2 == 76 );
        assert( 2 * v == 76 );
        assert( v / 2 == 19 );
        assert( 2 / v == 0 );
        assert( v % 2 == 0 );
        assert( 2 % v == 2 );
        assert( (v & 6) == 6 );
        assert( (6 & v) == 6 );
        assert( (v | 9) == 47 );
        assert( (9 | v) == 47 );
        assert( (v ^ 5) == 35 );
        assert( (5 ^ v) == 35 );
        assert( v << 1 == 76 );
        assert( 1 << Variant(2) == 4 );
        assert( v >> 1 == 19 );
        assert( 4 >> Variant(2) == 1 );

        assert( Variant("abc") ~ "def" == "abcdef" );
        assert( "abc" ~ Variant("def") == "abcdef" );

        v = 38; v += 4; assert( v == 42 );
        v = 38; v -= 4; assert( v == 34 );
        v = 38; v *= 2; assert( v == 76 );
        v = 38; v /= 2; assert( v == 19 );
        v = 38; v %= 2; assert( v == 0 );
        v = 38; v &= 6; assert( v == 6 );
        v = 38; v |= 9; assert( v == 47 );
        v = 38; v ^= 5; assert( v == 35 );
        v = 38; v <<= 1; assert( v == 76 );
        v = 38; v >>= 1; assert( v == 19 );

        v = "abc"; v ~= "def"; assert( v == "abcdef" );

        v = Variant.init;   assert( v.toString == "Variant.init", v.toString );
        v = 42;             assert( v.toString == "42", v.toString );
        v = "abc";          assert( v.toString == "abc", v.toString );
        v = [1,2,3];        assert( v.toString == "[1,2,3]", v.toString );
        v = 1+2.0i;         assert( v.toString == "1+2i", v.toString );

        assert( Variant(0) < Variant(42) );
        assert( Variant(42) > Variant(0) );
        assert( Variant(21) == Variant(21) );
        assert( Variant(0) != Variant(42) );
        assert( Variant("bar") == Variant("bar") );
        assert( Variant("foo") != Variant("bar") );
        {
            auto v1 = Variant(42);
            auto v2 = Variant("foo");
            auto v3 = Variant(1+2.0i);

            int[Variant] hash;
            hash[v1] = 0;
            hash[v2] = 1;
            hash[v3] = 2;
            
            assert( hash[v1] == 0 );
            assert( hash[v2] == 1 );
            assert( hash[v3] == 2 );
        }
        {
            int[char[]] hash;
            hash["a"] = 1;
            hash["b"] = 2;
            hash["c"] = 3;
            Variant vhash = hash;

            assert( vhash.get!(int[char[]])["a"] == 1 );
            assert( vhash.get!(int[char[]])["b"] == 2 );
            assert( vhash.get!(int[char[]])["c"] == 3 );
        }
    }
}


FrontPage | News | TestPage | MessageBoard | Search | Contributors | Folders | Index | Help | Preferences | Edit

Edit text of this page (date of last change: June 7, 2007 9:56 (diff))