std.variant
This module implements a discriminated union type (a.k.a. tagged union, algebraic type). Such types are useful for type-uniform binary interfaces, interfacing with scripting languages, and comfortable exploratory programming.
A Variant
object can hold a value of any type, with very few restrictions (such as shared
types and noncopyable types). Setting the value is as immediate as assigning to the Variant
object. To read back the value of the appropriate type T
, use the get
method. To query whether a Variant
currently holds a value of type T
, use peek
. To fetch the exact type currently held, call type
, which returns the TypeInfo
of the current value.
In addition to Variant
, this module also defines the Algebraic
type constructor. Unlike Variant
, Algebraic
only allows a finite set of types, which are specified in the instantiation (e.g. Algebraic!(int, string)
may only hold an int
or a string
).
- Credits
- Reviewed by Brad Roberts. Daniel Keep provided a detailed code review prompting the following improvements: (1) better support for arrays; (2) support for associative arrays; (3) friendlier behavior towards the garbage collector.
- License:
- Boost License 1.0.
- Authors:
- Andrei Alexandrescu
- Source
- std/variant.d
- Examples:
-
Variant a; // Must assign before use, otherwise exception ensues // Initialize with an integer; make the type int Variant b = 42; writeln(b.type); // typeid (int) // Peek at the value assert(b.peek!(int) !is null && *b.peek!(int) == 42); // Automatically convert per language rules auto x = b.get!(real); // Assign any other type, including other variants a = b; a = 3.14; writeln(a.type); // typeid (double) // Implicit conversions work just as with built-in types assert(a < b); // Check for convertibility assert(!a.convertsTo!(int)); // double not convertible to int // Strings and all other arrays are supported a = "now I'm a string"; writeln(a); // "now I'm a string" // can also assign arrays a = new int[42]; writeln(a.length); // 42 a[5] = 7; writeln(a[5]); // 7 // Can also assign class values class Foo {} auto foo = new Foo; a = foo; assert(*a.peek!(Foo) == foo); // and full type information is preserved
- template maxSize(T...)
-
Gives the
sizeof
the largest type given.- Examples:
-
static assert(maxSize!(int, long) == 8); static assert(maxSize!(bool, byte) == 1); struct Cat { int a, b, c; } static assert(maxSize!(bool, Cat) == 12);
- struct VariantN(size_t maxDataSize, AllowedTypesParam...);
-
Back-end type seldom used directly by user code. Two commonly-used types using
VariantN
are:-
Algebraic
: A closed discriminated union with a limited type universe (e.g.,Algebraic!(int, double, string)
only accepts these three types and rejects anything else). -
Variant
: An open discriminated union allowing an unbounded set of types. If any of the types in theVariant
are larger than the largest built-in type, they will automatically be boxed. This means that even large types will only be the size of a pointer within theVariant
, but this also implies some overhead.Variant
can accommodate all primitive types and all user-defined types.
BothAlgebraic
andVariant
shareVariantN
's interface. (See their respective documentations below.)
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 tomaxDataSize
(rounded up for alignment) can be stored in aVariantN
object without being boxed (types larger than this will be boxed).- Examples:
-
alias Var = VariantN!(maxSize!(int, double, string)); Var a; // Must assign before use, otherwise exception ensues // Initialize with an integer; make the type int Var b = 42; writeln(b.type); // typeid (int) // Peek at the value assert(b.peek!(int) !is null && *b.peek!(int) == 42); // Automatically convert per language rules auto x = b.get!(real); // Assign any other type, including other variants a = b; a = 3.14; writeln(a.type); // typeid (double) // Implicit conversions work just as with built-in types assert(a < b); // Check for convertibility assert(!a.convertsTo!(int)); // double not convertible to int // Strings and all other arrays are supported a = "now I'm a string"; writeln(a); // "now I'm a string"
- Examples:
- can also assign arrays
alias Var = VariantN!(maxSize!(int[])); Var a = new int[42]; writeln(a.length); // 42 a[5] = 7; writeln(a[5]); // 7
- Examples:
- Can also assign class values
alias Var = VariantN!(maxSize!(int*)); // classes are pointers Var a; class Foo {} auto foo = new Foo; a = foo; assert(*a.peek!(Foo) == foo); // and full type information is preserved
- alias AllowedTypes = This2Variant!(VariantN, AllowedTypesParam);
-
The list of allowed types. If empty, any type is allowed.
- enum bool allowed(T);
-
Tells whether a type
T
is statically allowed for storage inside aVariantN
object by lookingT
up inAllowedTypes
. - this(T)(T value);
-
Constructs a
VariantN
value given an argument of a generic type. Statically rejects disallowed types. - this(T : VariantN!(tsize, Types), size_t tsize, Types...)(T value)
Constraints: if (!is(T : VariantN) && (Types.length > 0) && allSatisfy!(allowed, Types)); -
Allows assignment from a subset algebraic type
- VariantN opAssign(T)(T rhs);
-
Assigns a
VariantN
from a generic argument. Statically rejects disallowed types. - const pure nothrow @property bool hasValue();
-
Returns true if and only if the
VariantN
object holds a valid value (has been initialized with, or assigned from, a valid value).- Examples:
-
Variant a; assert(!a.hasValue); Variant b; a = b; assert(!a.hasValue); // still no value a = 5; assert(a.hasValue);
- inout @property inout(T)* peek(T)();
-
If the
VariantN
object holds a value of the exact typeT
, returns a pointer to that value. Otherwise, returnsnull
. In cases whereT
is statically disallowed,peek
will not compile.- Examples:
-
Variant a = 5; auto b = a.peek!(int); assert(b !is null); *b = 6; writeln(a); // 6
- const nothrow @property @trusted TypeInfo type();
-
Returns the
typeid
of the currently held value. - const @property bool convertsTo(T)();
-
Returns
true
if and only if theVariantN
object holds an object implicitly convertible to typeT
. Implicit convertibility is defined as per ImplicitConversionTargets. - inout @property inout(T) get(T)();
inout @property auto get(uint index)()
Constraints: if (index < AllowedTypes.length); -
Returns the value stored in the
VariantN
object, either by specifying the needed type or the index in the list of allowed types. The latter overload only applies to bounded variants (e.g.Algebraic
).- Parameters:
T The requested type. The currently stored value must implicitly convert to the requested type, in fact DecayStaticToDynamicArray!T
. If an implicit conversion is not possible, throws aVariantException
.index The index of the type among AllowedTypesParam
, zero-based.
- @property T coerce(T)();
-
Returns the value stored in the
VariantN
object, explicitly converted (coerced) to the requested typeT
. IfT
is a string type, the value is formatted as a string. If theVariantN
object is a string, a parse of the string to typeT
is attempted. If a conversion is not possible, throws aVariantException
. - string toString();
-
Formats the stored value as a string.
- const bool opEquals(T)(auto ref T rhs)
Constraints: if (allowed!T || is(immutable(T) == immutable(VariantN))); -
Comparison for equality used by the "==" and "!=" operators.
- int opCmp(T)(T rhs)
Constraints: if (allowed!T); -
Ordering comparison used by the "<", "<=", ">", and ">=" operators. In case comparison is not sensible between the held value and
rhs
, an exception is thrown. - const nothrow @safe size_t toHash();
-
Computes the hash of the held value.
- VariantN opBinary(string op, T)(T rhs)
Constraints: if ((op == "+" || op == "-" || op == "*" || op == "/" || op == "^^" || op == "%") && is(typeof(opArithmetic!(T, op)(rhs))));
VariantN opBinary(string op, T)(T rhs)
Constraints: if ((op == "&" || op == "|" || op == "^" || op == ">>" || op == "<<" || op == ">>>") && is(typeof(opLogic!(T, op)(rhs))));
VariantN opBinaryRight(string op, T)(T lhs)
Constraints: if ((op == "+" || op == "*") && is(typeof(opArithmetic!(T, op)(lhs))));
VariantN opBinaryRight(string op, T)(T lhs)
Constraints: if ((op == "&" || op == "|" || op == "^") && is(typeof(opLogic!(T, op)(lhs))));
VariantN opBinary(string op, T)(T rhs)
Constraints: if (op == "~");
VariantN opOpAssign(string op, T)(T rhs); -
Arithmetic between
VariantN
objects and numeric values. All arithmetic operations return aVariantN
object typed depending on the types of both values involved. The conversion rules mimic D's built-in rules for arithmetic conversions. - inout inout(Variant) opIndex(K)(K i);
Variant opIndexAssign(T, N)(T value, N i);
Variant opIndexOpAssign(string op, T, N)(T value, N i); -
Array and associative array operations. If a
VariantN
contains an (associative) array, it can be indexed into. Otherwise, an exception is thrown.- Examples:
-
Variant a = new int[10]; a[5] = 42; writeln(a[5]); // 42 a[5] += 8; writeln(a[5]); // 50 int[int] hash = [ 42:24 ]; a = hash; writeln(a[42]); // 24 a[42] /= 2; writeln(a[42]); // 12
- @property size_t length();
-
If the
VariantN
contains an (associative) array, returns the length of that array. Otherwise, throws an exception. - int opApply(Delegate)(scope Delegate dg)
Constraints: if (is(Delegate == delegate)); -
If the
VariantN
contains an array, appliesdg
to each element of the array in turn. Otherwise, throws an exception.
-
- template Algebraic(T...)
-
Algebraic data type restricted to a closed set of possible types. It's an alias for
VariantN
with an appropriately-constructed maximum size.Algebraic
is useful when it is desirable to restrict what a discriminated type could hold to the end of defining simpler and more efficient manipulation.- Examples:
-
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
- Examples:
-
Self-Referential Types
A useful and popular use of algebraic data structures is for defining self-referential data structures, i.e. structures that embed references to values of their own type within. This is achieved withAlgebraic
by usingThis
as a placeholder whenever a reference to the type being defined is needed. TheAlgebraic
instantiation will perform alpha renaming on its constituent types, replacingThis
with the self-referenced type. The structure of the type involvingThis
may be arbitrarily complex.import std.typecons : Tuple, tuple; // A tree is either a leaf or a branch of two other trees alias 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 objects alias Obj = Algebraic!(double, string, This[string]); Obj obj = "hello"; writeln(obj.get!1); // "hello" obj = 42.0; writeln(obj.get!0); // 42 obj = ["customer": Obj("John"), "paid": Obj(23.95)]; writeln(obj.get!2["customer"]); // "John"
- alias Variant = VariantN!32LU.VariantN;
-
Alias for
VariantN
instantiated with the largest size ofcreal
,char[]
, andvoid delegate()
. This ensures thatVariant
is large enough to 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 for storing larger types unboxed, or for saving memory.- Examples:
-
Variant a; // Must assign before use, otherwise exception ensues // Initialize with an integer; make the type int Variant b = 42; writeln(b.type); // typeid (int) // Peek at the value assert(b.peek!(int) !is null && *b.peek!(int) == 42); // Automatically convert per language rules auto x = b.get!(real); // Assign any other type, including other variants a = b; a = 3.14; writeln(a.type); // typeid (double) // Implicit conversions work just as with built-in types assert(a < b); // Check for convertibility assert(!a.convertsTo!(int)); // double not convertible to int // Strings and all other arrays are supported a = "now I'm a string"; writeln(a); // "now I'm a string"
- Examples:
- can also assign arrays
Variant a = new int[42]; writeln(a.length); // 42 a[5] = 7; writeln(a[5]); // 7
- Examples:
- Can also assign class values
Variant a; class Foo {} auto foo = new Foo; a = foo; assert(*a.peek!(Foo) == foo); // and full type information is preserved
- Variant[] variantArray(T...)(T args);
-
Returns an array of variants constructed from
args
.This is by design. During construction the
Variant
needs static type information about the type being held, so as to store a pointer to function for fast retrieval.- Examples:
-
auto a = variantArray(1, 3.14, "Hi!"); writeln(a[1]); // 3.14 auto b = Variant(a); // variant array as variant writeln(b[1]); // 3.14
- class VariantException: object.Exception;
-
Thrown in three cases:
- An uninitialized
Variant
is used in any way except assignment andhasValue
; - A
get
orcoerce
is attempted with an incompatible target type; - A comparison between
Variant
objects of incompatible types is attempted.
- Examples:
-
import std.exception : assertThrown; Variant v; // uninitialized use assertThrown!VariantException(v + 1); assertThrown!VariantException(v.length); // .get with an incompatible target type assertThrown!VariantException(Variant("a").get!int); // comparison between incompatible types assertThrown!VariantException(Variant(3) < Variant("a"));
- TypeInfo source;
-
The source type in the conversion or comparison
- TypeInfo target;
-
The target type in the conversion or comparison
- An uninitialized
- template visit(Handlers...) if (Handlers.length > 0)
-
Applies a delegate or function to the given
Algebraic
depending on the held type, ensuring that all types are handled by the visiting functions.The delegate or function having the currently held value as parameter is called with
variant
's current value. Visiting handlers are passed in the template parameter list. It is statically ensured that all held types ofvariant
are handled across all handlers.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.- Returns:
- The return type of visit is deduced from the visiting functions and must be the same across all overloads.
- Throws:
-
VariantException
ifvariant
doesn't hold a value and no parameter-less fallback function is specified.
- Examples:
-
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 usage Algebraic!(int, string) emptyVar; auto rslt = emptyVar.visit!((string s) => cast(int) s.length, (int i) => i, () => -1)(); writeln(rslt); // -1 // Generic function usage Algebraic!(int, float, real) number = 2; writeln(number.visit!(x => x += 1)); // 3 // Generic function for int/float with separate behavior for string Algebraic!(int, float, string) something = 2; assert(something.visit!((string s) => s.length, x => x) == 2); // generic something = "asdf"; assert(something.visit!((string s) => s.length, x => x) == 4); // string // Generic handler and empty handler Algebraic!(int, float, real) empty2; writeln(empty2.visit!(x => x + 1, () => -1)); // -1
- auto visit(VariantType)(VariantType variant)
Constraints: if (isAlgebraic!VariantType);
- template tryVisit(Handlers...) if (Handlers.length > 0)
-
Behaves as
visit
but doesn't enforce that all types are handled by the visiting functions.If a parameter-less function is specified it is called when either
variant
doesn't hold a value or holds a type which isn't handled by the visiting functions.- Returns:
- The return type of tryVisit is deduced from the visiting functions and must be the same across all overloads.
- Throws:
-
VariantException
ifvariant
doesn't hold a value orvariant
holds a value which isn't handled by the visiting functions, when no parameter-less fallback function is specified.
- Examples:
-
Algebraic!(int, string) variant; variant = 10; auto which = -1; variant.tryVisit!((int i) { which = 0; })(); writeln(which); // 0 // Error function usage variant = "test"; variant.tryVisit!((int i) { which = 0; }, () { which = -100; })(); writeln(which); // -100
- auto tryVisit(VariantType)(VariantType variant)
Constraints: if (isAlgebraic!VariantType);
© 1999–2021 The D Language Foundation
Licensed under the Boost License 1.0.
https://dlang.org/phobos/std_variant.html