Download Reference Manual
The Developer's Library for D
About Wiki Forums Source Search Contact

Variant

Moderators: kris

Posted: 02/02/07 19:14:14

Phobos has std.boxer, so I think Tango needs to do one better. I'd like to offer up the name "Variant" since that's what it is.

I would like to see us adopt std.boxer, and improve upon it. IIRC, it was composed before we had IFTI in place so it probably could be streamlined.

Tango also has more to offer in the way of runtime conversion of types and data, so the core of such a type might take a very different shape.

Author Message

Posted: 02/03/07 20:27:49

You have some ideas on this, Pragma? I'm sure you do :)

Posted: 02/05/07 03:51:41

Ideas? Just a few. Overall I think a refactoring/re-visioning of Boxer is long overdue.

Thanks to the changes made in D since Boxer was written, we're now *really* close to having something that resembles duck typing. Here's a stub to wet your appetite:

// compiles under DMD 1.004
    struct Variant{
        static Variant opCall(T)(T value){
            Variant _this;
            return _this;
        }
        
        Variant opAssign(T)(T value){
            return *this;
        }
        
        T get(T)(){
            T dummy;
            return dummy;
        }

        T opAdd(T)(T other){
            T result;
            return get!(T) + other;
        }
    }
    
    alias Variant Var; // looks better
    
    void main(){
        Var x = 42;            // uses opCall/opAssign combo
        x = "hello world";     // more opAssign fun
        int bar = x.get!(int); // not bad, but better than "unbox!(T)(box)"
        int y = x + 69;        // quack!
    }

The only wart is the get() operation there. If D were to recognize T opCast(T)() as a valid overload for cast(), it would be perfect. Add some compile-time traits and type deduction for those operator overloads, and we're in business.

Off the top of my head, good uses for this will likely be code generators, scripting language bindings, and D scripting.

Posted: 02/15/07 09:23:35

See also #132 for a mention on Variant/Any/Whatnot.

Posted: 03/05/07 06:13:17

I have written a simple stack-based Variant for you :)

// Module:	variant.d
// Author:	oldrev
// License:	BSD

module variant;

import std.typetuple;
import std.traits;
import std.stdio;

private template MaxSizeImp(T, V...)
{
    static if(V.length > 0)
        private const int tailResult = MaxSizeImp!(V).result;
    else 
        private const int tailResult = T.sizeof;

    public const int result = T.sizeof > tailResult ? T.sizeof : tailResult;
};

private template MaxSize(TList...)
{
    const int MaxSize = MaxSizeImp!(TList).result;
}

/*
private template RemoveSmaller(int Size, TList...)
{
    static if (TList.length == 0)
	alias TList RemoveSmaller;
    else static if (TList[0].sizeof < Size)
	alias RemoveSmaller!(Size, TList[1 .. length]) RemoveSmaller;
    else
	alias TypeTuple!(TList[0], RemoveSmaller!(Size, TList[1 .. length])) RemoveSmaller;
}

private template MaxAlignmentImp(T, V...)
{
    static if(V.length > 0)
        private const int tailResult = MaxAlignmentImp!(V).result;
    else 
        private const int tailResult = T.alignof;

    public const int result = T.alignof > tailResult ? T.alignof : tailResult;
};

private template MaxAlignment(TList...)
{
    const int MaxAlignment = MaxAlignmentImp!(RemoveSmaller!(MaxSize!(TList), TList)).result;
}
*/


struct Variant(TList...)
{
    public alias TList TypeList;
    public alias Variant!(TypeList) SelfType;
	private alias ubyte[MaxSize!(TypeList)] Holder;

    private Holder  m_held;
    private int     m_which = -1;

    public int which()
    {
        return m_which;
    }

	public SelfType assign(ValueType)(ValueType val)
	{
		static if(is(ValueType == SelfType))
		{
			foreach(T; TypeList)
			{
				const int i = IndexOf!(T, TypeList);
				if(val.which == i)
				{
					assign!(T)(val.get!(T));
					m_which = i;		
				}
			}
		}		
		else
		{
			const int i = IndexOf!(ValueType, TypeList);
			static assert(i >= 0);
			ValueType* heldPtr = cast(ValueType*)m_held.ptr;
			*heldPtr = val;
			m_which = i;
		}

		return *this;
	}

    public SelfType opAssign(ValueType)(ValueType rhs)
    {
        return assign!(ValueType)(rhs);
    }

	public bool opEquals(ValueType)(ValueType rhs)
	{
        assert(!empty);

		static if(is(ValueType == SelfType))
		{
			foreach(T; TypeList)
			{
				const int i = IndexOf!(T, TypeList);
				if(i == which)
				{
					return (rhs.which == which) && (get!(T) == rhs.get!(T));
				}
			}
		}
		else
		{
			const int i = IndexOf!(ValueType, TypeList);
			static assert(i >= 0);
		
			return get!(ValueType)() == rhs;
		}
	}

	public int opCmp(ValueType)(ValueType rhs)
	{
		if(rhs == *this)return 0;
		static if(is(ValueType == SelfType))
		{
			foreach(T; TypeList)
			{
				const int i = IndexOf!(T, TypeList);
				if((i == which) && (rhs.which == which))
				{
					return get!(T) < rhs.get!(T) ? -1 : 1;
				}
			}
		}
		else
		{
			const int i = IndexOf!(ValueType, TypeList);
			static assert(i >= 0);
		
			return get!(ValueType)() < rhs ? -1 : 1;
		}
	}
	
	public TypeInfo type()
	{
		foreach(T; TypeList)
		{
			const int i = IndexOf!(T, TypeList);
			if(i == which)
			{
				return typeid(TypeList[i]);
			}
		}
	}

    public ValueType get(ValueType)()
    {
        assert(typeid(ValueType) == type);
        return *(cast(ValueType*)m_held.ptr);
    }

    public bool empty()
    {
        return m_which < 0;
    }

	public void swap(inout SelfType var)
	{
		Holder h;
		h[] = m_held;
		int w = which;
		m_held[] = var.m_held;
		m_which = var.which;
		var.m_held[] = h;
		var.m_which = w;
	}

    public static SelfType opCall(ValueType)(ValueType val)
    {
        SelfType var;
        var = val;
        return var;
    }

}

void main()
{
    class Foo
    {
		public:
		int n = 0;
		int x = 0;
		int y = 0;
		int z = 0;

		Foo opAssign(int rhs)
		{
            x = rhs;
            y = rhs;
			z = rhs;
			return this;
		}
    }

    struct Bar
    {
        public:
        int x = 0;
        int y = 1;
        int z = 2;
    }

    auto v = Variant!(double, int, char[], Foo, Bar)(2);
	v = 2;
    assert(v == 2);
	assert(v <= 2);
    v = 2.22;
    assert(v == 2.22);
	assert(v.type == typeid(double));
	v = new Foo;
	assert(v.type == typeid(Foo));
	v.get!(Foo)() = 2;
	assert(v.get!(Foo)().z == 2);
	typeof(v) v2 = v;
	assert(v2 == v);
	v = "Foobar".dup;
	assert(v == "Foobar".dup);
	
	v.swap(v2);
	assert(v2.get!(char[])() == "Foobar");
    
    Bar b;
    b.x = 10;
    v = b;
    assert(v.get!(Bar)().x == 10);

}

Posted: 05/17/07 14:20:37 -- Modified: 05/18/07 06:50:35 by
DRK

I've done up an implementation of Pragma's idea. It supports (as far as I can tell) any type you care to throw at it, uses the v.get!(T) syntax to unpack the value, supports all operator overloads with the exception of (Variant op Variant) (since I have no idea how to do it), indexing and slicing, has a toHash method and has a sane toUtf8 overload.

The code is written to run under both Phobos and Tango (you can always remove the Phobos stuff if you want). The only problem with the Tango version is that Tango doesn't support as many types for formatting as Phobos does, so some of the unit tests fail. Also, the unit tests are written to use .toString, so you have to compile with PhobosCompatibility? to get the unit tests to run.

But apart from that, it's a Variant type. It's also public domain if you want to steal it.

Edit: Stupid me, I forgot to actually post the link. I'd lose my head if it wasn't stapled on...

http://www.prowiki.org/wiki4d/wiki.cgi?DanielKeep/Variant

Posted: 05/18/07 13:54:20

DRK wrote:

That's a work of art, and certainly kicks the crap out of anything I had in mind. Thanks for taking the time to write something like this.

-- EricAnderton at yahoo

Posted: 05/21/07 15:11:34

Nice work!

Posted: 05/22/07 22:26:51

I like it too :-)

But I just would request few minor improvements:

1. It probably can not store another Variant in itself because of nature of D's opAssign; it would be great to have such a possibility, which is sometimes very usefull; probably separate template member/external method assign() would help here; or calling explicit opAssign is enough? or maybe using templated static opCall() member method?; the last proposition would help also with passing arguments to methods, which has in its signature Variant parameters... (for reference see: doost/Any)

2. Having method isEmpty() and clear() would be nice; asking if type == typeid(void) to check if variant is empty is not very natural

3. std.boxer can convert variable arguments of function into array of boxes. It is also quite nice feature worth of implementing.

Posted: 05/25/07 03:31:06

aarti_pl wrote:

I like it too :-)

But I just would request few minor improvements:

1. This looks do-able if you add another static if() check inside both the opAssign() and static opCall() methods:

static if(is(T == Variant)){ this.value = value.value; this.type = value.type; }

2. Agreed, as isEmpty() would probably cover the case nicely. Still, have you tried comparing the variant to null? I haven't tried it, but the code looks like it'll cover that.

3. Templates to the rescue?

//untested, but this should work
Variant[] variantArray(V...)(V args){
  Variant[] result;
  result.length = args.length;
  foreach(i,arg; args){
    result[i] = arg;
  }
  return result;
}

Thanks again Daniel, this is great work.

-- EricAnderton at yahoo

Posted: 06/20/07 18:04:48

Any status update on this?

I'm very interested in seeing a Variant type in Tango. Are there any major roadblocks or objections preventing it?

Thanks, John Demme

Posted: 06/21/07 02:10:09

Sorry, it's on the list. I'll try and get it in for the next release.

Posted: 06/22/07 18:25:26

sean wrote:

Sorry, it's on the list. I'll try and get it in for the next release.

Great! Thanks. Never any need to apologize for volunteer work, IMO.