CreditsReviewed by Brad Roberts. Daniel Keep provided a detailed code reviewprompting the following improvements: (1) better support for arrays; (2) supportfor associative arrays; (3) friendlier behavior towards the garbage collector.
Sourcestd/variant.d
Variant a;// Must assign before use, otherwise exception ensues// Initialize with an integer; make the type intVariant b = 42;writeln(b.type);// typeid (int)// Peek at the valueassert(b.peek!(int) !isnull && *b.peek!(int) == 42);// Automatically convert per language rulesauto x = b.get!(real);// Assign any other type, including other variantsa = b;a = 3.14;writeln(a.type);// typeid (double)// Implicit conversions work just as with built-in typesassert(a < b);// Check for convertibilityassert(!a.convertsTo!(int));// double not convertible to int// Strings and all other arrays are supporteda ="now I'm a string";writeln(a);// "now I'm a string"// can also assign arraysa =newint[42];writeln(a.length);// 42a[5] = 7;writeln(a[5]);// 7// Can also assign class valuesclass Foo {}auto foo =new Foo;a = foo;assert(*a.peek!(Foo) == foo);// and full type information is preserved
maxSize(Ts...);struct Cat {int a, b, c; }align(1)struct S{long l;ubyte b;}align(1)struct T{ubyte b;long l;}staticassert(maxSize!(int,long) == 8);staticassert(maxSize!(bool,byte) == 1);staticassert(maxSize!(bool, Cat) == 12);staticassert(maxSize!(char) == 1);staticassert(maxSize!(char,short,ubyte) == 2);staticassert(maxSize!(char,long,ubyte) == 8);import std.algorithm.comparison : max;staticassert(maxSize!(long, S) == max(long.sizeof, S.sizeof));staticassert(maxSize!(S, T) == max(S.sizeof, T.sizeof));staticassert(maxSize!(int,ubyte[7]) == 7);staticassert(maxSize!(int,ubyte[3]) == 4);staticassert(maxSize!(int,int,ubyte[3]) == 4);staticassert(maxSize!(void,int,ubyte[3]) == 4);staticassert(maxSize!(void) == 1);
VariantN(size_t maxDataSize, AllowedTypesParam...);VariantN are:VariantN is a discriminated union type parameterized with the largest size of the types stored (maxDataSize) and with the list of allowed types (AllowedTypes). If the list is empty, then any type up of size up to maxDataSize (rounded up for alignment) can be stored in aVariantN object without being boxed (types larger than this will be boxed).alias Var =VariantN!(maxSize!(int,double, string));Var a;// Must assign before use, otherwise exception ensues// Initialize with an integer; make the type intVar b = 42;writeln(b.type);// typeid (int)// Peek at the valueassert(b.peek!(int) !isnull && *b.peek!(int) == 42);// Automatically convert per language rulesauto x = b.get!(real);// Assign any other type, including other variantsa = b;a = 3.14;writeln(a.type);// typeid (double)// Implicit conversions work just as with built-in typesassert(a < b);// Check for convertibilityassert(!a.convertsTo!(int));// double not convertible to int// Strings and all other arrays are supporteda ="now I'm a string";writeln(a);// "now I'm a string"
alias Var =VariantN!(maxSize!(int[]));Var a =newint[42];writeln(a.length);// 42a[5] = 7;writeln(a[5]);// 7
alias Var =VariantN!(maxSize!(int*));// classes are pointersVar a;class Foo {}auto foo =new Foo;a = foo;assert(*a.peek!(Foo) == foo);// and full type information is preserved
AllowedTypes = This2Variant!(VariantN, AllowedTypesParam);allowed(T);value);value)opAssign(T)(Trhs);hasValue() const;Variant a;assert(!a.hasValue);Variant b;a = b;assert(!a.hasValue);// still no valuea = 5;assert(a.hasValue);
peek(T)() inout;Variant a = 5;auto b = a.peek!(int);assert(b !isnull);*b = 6;writeln(a);// 6
type() const;convertsTo(T)() const;get(T)() inout;get(uint index)() inout| T | The requested type. The currently stored value must implicitly convert to the requested type, in factDecayStaticToDynamicArray!T. If an implicit conversion is not possible, throws aVariantException. |
| index | The index of the type amongAllowedTypesParam, zero-based. |
coerce(T)();toString();opEquals(T)(auto ref Trhs) constopCmp(T)(Trhs)rhs, an exception is thrown.toHash() const;opBinary(string op, T)(Trhs)rhs))));opBinary(string op, T)(Trhs)rhs))));opBinaryRight(string op, T)(Tlhs)lhs))));opBinaryRight(string op, T)(Tlhs)lhs))));opBinary(string op, T)(Trhs)opOpAssign(string op, T)(Trhs);opIndex(K)(Ki) inout;opIndexAssign(T, N)(Tvalue, Ni);opIndexOpAssign(string op, T, N)(Tvalue, Ni);Variant a =newint[10];a[5] = 42;writeln(a[5]);// 42a[5] += 8;writeln(a[5]);// 50int[int] hash = [ 42:24 ];a = hash;writeln(a[42]);// 24a[42] /= 2;writeln(a[42]);// 12
length();opApply(Delegate)(scope Delegatedg)dg to each element of the array in turn. Otherwise, throws an exception.Algebraic(T...)Algebraic isuseful when it is desirable to restrict what a discriminated typecould hold to the end of defining simpler and more efficientmanipulation.auto v =Algebraic!(int,double, string)(5);assert(v.peek!(int));v = 3.14;assert(v.peek!(double));// auto x = v.peek!(long); // won't compile, type long not allowed// v = '1'; // won't compile, type char not allowed
Algebraic by usingThis as a placeholder whenever areference to the type being defined is needed. TheAlgebraic instantiationwill performalpha renaming on its constituent types, replacingThiswith the self-referenced type. The structure of the type involvingThis maybe arbitrarily complex.import std.typecons : Tuple, tuple;// A tree is either a leaf or a branch of two other treesalias Tree(Leaf) =Algebraic!(Leaf, Tuple!(This*, This*));Tree!int tree = tuple(new Tree!int(42),new Tree!int(43));Tree!int* right = tree.get!1[1];writeln(*right);// 43// An object is a double, a string, or a hash of objectsalias Obj =Algebraic!(double, string, This[string]);Obj obj ="hello";writeln(obj.get!1);// "hello"obj = 42.0;writeln(obj.get!0);// 42obj = ["customer": Obj("John"),"paid": Obj(23.95)];writeln(obj.get!2["customer"]);// "John"
Variant = VariantN!32LU.VariantN;Variant is large enoughto hold all of D's predefined types unboxed, including all numeric types,pointers, delegates, and class references. You may want to useVariantN directly with a different maximum size either forstoring larger types unboxed, or for saving memory.Variant a;// Must assign before use, otherwise exception ensues// Initialize with an integer; make the type intVariant b = 42;writeln(b.type);// typeid (int)// Peek at the valueassert(b.peek!(int) !isnull && *b.peek!(int) == 42);// Automatically convert per language rulesauto x = b.get!(real);// Assign any other type, including other variantsa = b;a = 3.14;writeln(a.type);// typeid (double)// Implicit conversions work just as with built-in typesassert(a < b);// Check for convertibilityassert(!a.convertsTo!(int));// double not convertible to int// Strings and all other arrays are supporteda ="now I'm a string";writeln(a);// "now I'm a string"
Variant a =newint[42];writeln(a.length);// 42a[5] = 7;writeln(a[5]);// 7
Variant a;class Foo {}auto foo =new Foo;a = foo;assert(*a.peek!(Foo) == foo);// and full type information is preserved
variantArray(T...)(Targs);args.auto a =variantArray(1, 3.14,"Hi!");writeln(a[1]);// 3.14auto b = Variant(a);// variant array as variantwriteln(b[1]);// 3.14
VariantException:object.Exception;import std.exception : assertThrown;Variant v;// uninitialized useassertThrown!VariantException(v + 1);assertThrown!VariantException(v.length);// .get with an incompatible target typeassertThrown!VariantException(Variant("a").get!int);// comparison between incompatible typesassertThrown!VariantException(Variant(3) < Variant("a"));
source;target;visit(Handlers...) if (Handlers.length > 0)visit allows delegates and static functions to be passed as parameters. If a function with an untyped parameter is specified, this function is called when the variant contains a type that does not match any other function. This can be used to apply the same function across multiple possible types. Exactly one generic function is allowed. If a function without parameters is specified, this function is called whenvariant doesn't hold a value. Exactly one parameter-less function is allowed. Duplicate overloads matching the same type in one of the visitors are disallowed.Algebraic!(int, string) variant;variant = 10;assert(variant.visit!((string s) =>cast(int) s.length, (int i) => i)() == 10);variant ="string";assert(variant.visit!((int i) => i, (string s) =>cast(int) s.length)() == 6);// Error function usageAlgebraic!(int, string) emptyVar;auto rslt = emptyVar.visit!((string s) =>cast(int) s.length, (int i) => i, () => -1)();writeln(rslt);// -1// Generic function usageAlgebraic!(int,float,real) number = 2;writeln(number.visit!(x => x += 1));// 3// Generic function for int/float with separate behavior for stringAlgebraic!(int,float, string) something = 2;assert(something.visit!((string s) => s.length, x => x) == 2);// genericsomething ="asdf";assert(something.visit!((string s) => s.length, x => x) == 4);// string// Generic handler and empty handlerAlgebraic!(int,float,real) empty2;writeln(empty2.visit!(x => x + 1, () => -1));// -1
visit(VariantType)(VariantTypevariant)tryVisit(Handlers...) if (Handlers.length > 0)Algebraic!(int, string) variant;variant = 10;auto which = -1;variant.tryVisit!((int i) { which = 0; })();writeln(which);// 0// Error function usagevariant ="test";variant.tryVisit!((int i) { which = 0; }, () { which = -100; })();writeln(which);// -100
tryVisit(VariantType)(VariantTypevariant)