Report a bug
If you spot a problem with this page, click here to create a GitHub issue.
Improve this page
Quickly fork, edit online, and submit a pull request for this page. Requires a signed-in GitHub account. This works well for small changes. If you'd like to make larger changes you may want to consider using a local clone.

mir.algebraic

Variant and Nullable types

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.
The module defines generic Algebraic type that contains a payload. The allowed types of the paylad are defined by the unordered TypeSet.
Algebraic template accepts two arguments: self type set id and a list of type sets.

Algebraic Aliases

Name Description
Variant an algebraic type for a single type set
Nullable an algebraic type for a single type set with at least typeof(null)
Variants a list of algebraic types with cyclic type referencing, which defined over the same list of type sets

Visitor Handlers

Name Ensures can match Throws if no match Returns Nullable Multiple dispatch Argumments count Algebraic first argument
    Classic handlers
visit Yes N/A No No 1+ Yes
optionalVisit No No Yes No 1+ Yes
autoVisit No No auto No 1+ Yes
tryVisit No Yes No No 1+ Yes
    Handlers with multiple dispatch
match Yes N/A No Yes 0+ auto
optionalMatch No No Yes Yes 0+ auto
autoMatch No No auto Yes 0+ auto
tryMatch No Yes No Yes 0+ auto
    Handlers for member access
getMember Yes N/A No No 1+ Yes
optionalGetMember No No Yes No 1+ Yes
autoGetMember No No auto No 1+ Yes
tryGetMember No Yes No No 1+ Yes

Special Types

Name Description
void It is usefull to indicate a possible return type of the visitor. Can't be accesed by reference.
typeof(null) It is usefull for nullable types. Also, it is used to indicate that a visitor can't match the current value of the algebraic. Can't be accesed by reference.
This An dummy structure that is used to construct self-referencing algebraic types. Example: Variant!(int, double, string, This*[2])
SetAlias!setId An dummy structure that is used to construct cyclic-referencing lists of algebraic types.

Algebraic Traits

Name Description
isVariant Checks if the type is instance of Algebraic.
isNullable Checks if the type is instance of Algebraic with a self TypeSet that contains typeof(null).
isTypeSet Checks if the type is instance of TypeSet.

Type Set

  • Type set is unordered. Example:TypeSet!(int, double) and TypeSet!(double, int) are the same.
  • Duplicats are ignored. Example: TypeSet!(float, int, float) and TypeSet!(int, float) are the same.
  • Types are automatically unqualified if this operation can be performed implicitly. Example: TypeSet!(const int) and TypeSet!int` are the same.
  • Non trivial TypeSet!(A, B, ..., etc) is allowed.
  • Trivial TypeSet!T is allowed.
  • Empty TypeSet!() is allowed.

Visitors

  • Visitors are allowed to return values of different types If there are more then one return type then the an Algebraic type is returned.
  • Visitors are allowed to accept additional arguments. The arguments can be passed to the visitor handler.
  • Multiple visitors can be passes to the visitor handler.
  • Visitors are matched according to the common Dlang Function Overloading rules.
  • Visitors are allowed accept algebraic value by reference except the value of typeof(null).
  • Visitors are called without algebraic value if its algebraic type is void.
  • If the visitors arguments has known types, then such visitors should be passed to a visitor handler before others to make the compiler happy. This includes visitors with no arguments, which is used to match void type.

Implementation Features

  • BetterC support. Runtime TypeInfo is not used.
  • Copy-constructors and postblit constructors are supported.
  • toHash, opCmp. opEquals, and toString support.
  • No string or template mixins are used.
  • Optimised for fast execution.
See Also:
License:
Authors:
Ilya Yaroshenko
enum bool isVariant(T);
Checks if the type is instance of Algebraic.
Examples:
static assert(isVariant!(Variant!(int, string)));
static assert(isVariant!(const Variant!(int[], string)));
static assert(isVariant!(Nullable!(int, string)));
static assert(!isVariant!int);
template isNullable(T)
Checks if the type is instance of Algebraic with a self TypeSet that contains typeof(null).
Examples:
static assert(isNullable!(Nullable!(int, string)));
static assert(isNullable!(Nullable!()));

static assert(!isNullable!(Variant!()));
static assert(!isNullable!(Variant!string));
static assert(!isNullable!int);
static assert(!isNullable!string);
struct SetAlias(uint id);
Dummy type for Variants self-referencing.
struct This;
Dummy type for Variant and Nullable self-referencing.
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 with Variant by using This as a placeholder whenever a reference to the type being defined is needed. The Variant instantiation will perform alpha renaming on its constituent types, replacing This with the self-referenced type. The structure of the type involving This may be arbitrarily complex.
import mir.functional: Tuple = RefTuple;

// A tree is either a leaf or a branch of two others
alias Tree(Leaf) = Variant!(Leaf, Tuple!(This*, This*));
alias Leafs = Tuple!(Tree!int*, Tree!int*);

Tree!int tree = Leafs(new Tree!int(41), new Tree!int(43));
Tree!int* right = tree.get!Leafs[1];
assert(*right == 43);
Examples:
// An object is a double, a string, or a hash of objects
alias Obj = Variant!(double, string, This[string]);
alias Map = Obj[string];

Obj obj = "hello";
assert(obj._is!string);
assert(obj.trustedGet!string == "hello");
obj = 42.0;
assert(obj.get!double == 42);
obj = ["customer": Obj("John"), "paid": Obj(23.95)];
assert(obj.get!Map["customer"] == "John");
template TypeSet(T...)
Type set for Variants self-referencing.
Examples:
struct S {}
alias C = S;
alias Int = int;
static assert(__traits(isSame, TypeSet!(S, int), TypeSet!(Int, C)));
static assert(__traits(isSame, TypeSet!(S, int, int), TypeSet!(Int, C)));
static assert(!__traits(isSame, TypeSet!(uint, S), TypeSet!(int, S)));
enum bool isTypeSet(T);
Checks if the type is instance of TypeSet.
Examples:
static assert(isTypeSet!(TypeSet!()));
static assert(isTypeSet!(TypeSet!void));
static assert(isTypeSet!(TypeSet!(void, int, typeof(null))));
template Variants(Sets...) if (allSatisfy!(isTypeSet, Sets))

Cyclic-Referential Types

A useful and popular use of algebraic data structures is for defining cyclic self-referential data structures, i.e. a kit of structures that embed references to values of their own type within. This is achieved with Variants by using SetAlias as a placeholder whenever a reference to the type being defined is needed. The Variant instantiation will perform alpha renaming on its constituent types, replacing SetAlias with the self-referenced type. The structure of the type involving SetAlias may be arbitrarily complex.
Examples:
alias V = Variants!(
    TypeSet!(string, long, SetAlias!1*), // string, long, and pointer to V[1] type
    TypeSet!(SetAlias!0[], int), // int and array of V[0] type elements
);

alias A = V[0];
alias B = V[1];

A arr = new B([A("hey"), A(100)]);
assert(arr._is!(B*));
assert(arr.trustedGet!(B*)._is!(A[]));
alias Variants = staticMap!(TypeSetsInst, Iota!(Sets.length));
template Variant(T...)
Variant Type (aka Algebraic Type).
Compatible with BetterC mode.
Examples:
Variant!(int, double, string) v = 5;
assert(v.get!int == 5);
v = 3.14;
assert(v == 3.14);
// auto x = v.get!long; // won't compile, type long not allowed
// v = '1'; // won't compile, type char not allowed
Examples:
Single argument Variant
static struct S
{
    int n;
    this(ref return scope inout S rhs) inout
    {
        this.n = rhs.n + 1;
    }
}

Variant!S a = S();
auto b = a;

import mir.conv;
assert(b.get!S.n == 1);
assert(a.get!S.n == 0);
Examples:
Empty type set
Variant!() a;
auto b = a;
assert(a.toHash == 0);
assert(a == b);
assert(a <= b && b >= a);
static assert(typeof(a).sizeof == 1);
Examples:
Small types
struct S { ubyte d; }
static assert(Nullable!(byte, char, S).sizeof == 2);
Examples:
Clever packaging
struct S { ubyte[3] d; }
static assert(Nullable!(ushort, wchar, S).sizeof == 4);
Examples:
opPostMove support
import std.algorithm.mutation: move;

static struct S
{
    uint s;

    void opPostMove(const ref S old) nothrow
    {
        this.s = old.s + 1;
    }
}

Variant!S a;

auto b = a.move;
assert(b.get!S.s == 1);
template Nullable(T...)
Nullable Variant Type (aka Algebraic Type).
The impllementation is defined as ``` alias Nullable(T...) = Variant!(typeof(null), T); ```
In additional to common algebraic API the following members can be accesssed:
Compatible with BetterC mode.
Examples:
Single type Nullable
static assert(is(Nullable!int == Variant!(typeof(null), int)));

Nullable!int a = 5;
assert(a.get!int == 5);

a.nullify;
assert(a.isNull);

a = 4;
assert(!a.isNull);
assert(a.get == 4);
assert(a == 4);
a = 4;

a = null;
assert(a == null);
Examples:
Empty nullable type set support
Nullable!() a;
auto b = a;
assert(a.toHash == 0);
assert(a == b);
assert(a <= b && b >= a);
static assert(typeof(a).sizeof == 1);
struct Algebraic(uint _setId, _TypeSets...) if (allSatisfy!(isTypeSet, _TypeSets) && (_setId < _TypeSets.length));
Implementation of Variant, Variants, and Nullable.
alias AllowedTypes = AliasSeq!(_ApplyAliasesImpl!(_TypeSets.length, TemplateArgsOf!(_TypeSets[_setId])));
Allowed types list
See Also:
Examples:
import std.traits: TemplateArgsOf;
import std.meta: AliasSeq;

alias V = Nullable!
(
    This*,
    string,
    double,
    bool,
);

static assert(is(V.AllowedTypes == TemplateArgsOf!(TypeSet!(
    typeof(null),
    bool,
    string,
    double,
    V*))));
this(uint rhsId, RhsTypeSets...)(Algebraic!(rhsId, RhsTypeSets) rhs)
if (allSatisfy!(Contains!AllowedTypes, Algebraic!(rhsId, RhsTypeSets).AllowedTypes));
const size_t toHash();
const bool opEquals()(auto ref const typeof(this) rhs);
const auto opCmp()(auto ref const typeof(this) rhs);
const string toString()();

const void toString(W)(ref scope W w);
Requires mir-algorithm package
const bool opCast(C)()
if (is(C == bool));
const bool isNull();

void nullify();

auto get()()
if (allSatisfy!(isCopyable, AllowedTypes[1 .. $]) && (AllowedTypes.length != 2));
Defined if the first type is typeof(null)
inout ref inout(AllowedTypes[1]) get();

inout @property ref inout(AllowedTypes[1]) get()(auto ref inout(AllowedTypes[1]) fallback);
Gets the value if not null. If this is in the null state, and the optional parameter fallback was provided, it will be returned. Without fallback, calling get with a null state is invalid.
When the fallback type is different from the Nullable type, get(T) returns the common type.
Parameters:
inout(AllowedTypes[1]) fallback the value to return in case the Nullable is null.
Returns:
The value held internally by this Nullable.
Examples:
enum E { a = "a", b = "b" }
Nullable!E f = E.a;
auto e = f.get();
static assert(is(typeof(e) == E), Nullable!E.AllowedTypes.stringof);
assert(e == E.a);

assert(f.get(E.b) == E.a);

f = null;
assert(f.get(E.b) == E.b);
@property ref auto trustedGet(R : Algebraic!(retId, RetTypeSets), uint retId, RetTypeSets, this This)() return
if (allSatisfy!(Contains!AllowedTypes, Algebraic!(retId, RetTypeSets).AllowedTypes));
Zero cost always nothrow get alternative
@property ref auto get(R : Algebraic!(retId, RetTypeSets), uint retId, RetTypeSets, this This)() return
if (allSatisfy!(Contains!AllowedTypes, Algebraic!(retId, RetTypeSets).AllowedTypes));
Throws:
Exception if the storage contains value of the type that isn't represented in the allowed type set of the requested algebraic.
template visit(visitors...)
Applies a delegate or function to the given Variant depending on the held type, ensuring that all types are handled by the visiting functions.
Examples:
alias Number = Variant!(int, double);

Number x = 23;
Number y = 1.0;

assert(x.visit!((int v) => true, (float v) => false));
assert(y.visit!((int v) => false, (float v) => true));
Examples:
alias Number = Nullable!(int, double);

Number z = null; // default
Number x = 23;
Number y = 1.0;

() nothrow {
    assert(x.visit!((int v) => true, (float v) => false));
    assert(y.visit!((int v) => false, (v) => true));
    assert(z.visit!((typeof(null) v) => true, (v) => false));
} ();

auto xx = x.get;
static assert (is(typeof(xx) == Variant!(int, double)));
assert(xx.visit!((int v) => v, (float v) => 0) == 23);
assert(xx.visit!((ref v) => v) == 23);

x = null;
y.nullify;

assert(x.isNull);
assert(y.isNull);
assert(z.isNull);
assert(z == y);
Examples:
Checks .Algebraic.toString and void Algerbraic.toString requries mir-algorithm package
import mir.conv: to;
enum MIR_ALGORITHM = __traits(compiles, { import mir.format; });

alias visitorHandler = visit!(
    (typeof(null)) => "NULL",
    () => "VOID",
    (ref r) {r += 1;}, // returns void
);

alias secondOrderVisitorHandler = visit!(
    () => "SO VOID", // void => to "RV VOID"
    (str) => str, // string to => it self
);

alias V = Nullable!(void, int);
static assert(is(V == Variant!(typeof(null), void, int)));

V variant;

assert(secondOrderVisitorHandler(visitorHandler(variant)) == "NULL");
assert(variant.to!string == "null");

variant = V._void;
assert(variant._is!void);
assert(is(typeof(variant.get!void()) == void));

assert(secondOrderVisitorHandler(visitorHandler(variant)) == "VOID");
assert(variant.to!string == "void");

variant = 5;

assert(secondOrderVisitorHandler(visitorHandler(variant)) == "SO VOID");
assert(variant == 6);
assert(variant.to!string == (MIR_ALGORITHM ? "6" : "int"));
template tryVisit(visitors...)
Behaves as visit but doesn't enforce at compile time that all types can be handled by the visiting functions.
Throws:
Exception if naryFun!visitors can't be called with provided arguments
Examples:
alias Number = Variant!(int, double);

Number x = 23;

assert(x.tryVisit!((int v) => true));
template optionalVisit(visitors...)
Behaves as visit but doesn't enforce at compile time that all types can be handled by the visiting functions.
Returns:
nullable variant, null value is used if naryFun!visitors can't be called with provided arguments.
Examples:
static struct S { int a; }

Variant!(S, double) variant;

alias optionalVisitInst = optionalVisit!((ref value) => value + 0);

// do nothing because of variant isn't initialized
Nullable!double result = optionalVisitInst(variant);
assert(result.isNull);

variant = S(2);
// do nothing because of lambda can't compile
result = optionalVisitInst(variant);
assert(result == null);

variant = 3.0;
result = optionalVisitInst(variant);
assert (result == 3.0);
template autoVisit(visitors...)
Behaves as visit but doesn't enforce at compile time that all types can be handled by the visiting functions.
Returns:
optionally nullable type, null value is used if naryFun!visitors can't be called with provided arguments.
template match(visitors...)
Applies a delegate or function to the given arguments depending on the held type, ensuring that all types are handled by the visiting functions.
The handler supports multiple dispatch or multimethods: a feature of handler in which a function or method can be dynamically dispatched based on the run time (dynamic) type or, in the more general case, some other attribute of more than one of its arguments.
Examples:
struct Asteroid { uint size; }
struct Spaceship { uint size; }
alias SpaceObject = Variant!(Asteroid, Spaceship);

alias collideWith = match!(
    (Asteroid x, Asteroid y) => "a/a",
    (Asteroid x, Spaceship y) => "a/s",
    (Spaceship x, Asteroid y) => "s/a",
    (Spaceship x, Spaceship y) => "s/s",
);

import mir.utility: min;
alias oops = match!((a, b) => (a.size + b.size) > 3 && min(a.size, b.size) > 1);

alias collide = (x, y) => oops(x, y) ? "big-boom" : collideWith(x, y);

auto ea = Asteroid(1);
auto es = Spaceship(2);
auto oa = SpaceObject(ea);
auto os = SpaceObject(es);

// Asteroid-Asteroid
assert(collide(ea, ea) == "a/a");
assert(collide(ea, oa) == "a/a");
assert(collide(oa, ea) == "a/a");
assert(collide(oa, oa) == "a/a");

// Asteroid-Spaceship
assert(collide(ea, es) == "a/s");
assert(collide(ea, os) == "a/s");
assert(collide(oa, es) == "a/s");
assert(collide(oa, os) == "a/s");

// Spaceship-Asteroid
assert(collide(es, ea) == "s/a");
assert(collide(es, oa) == "s/a");
assert(collide(os, ea) == "s/a");
assert(collide(os, oa) == "s/a");

// Spaceship-Spaceship
assert(collide(es, es) == "big-boom");
assert(collide(es, os) == "big-boom");
assert(collide(os, es) == "big-boom");
assert(collide(os, os) == "big-boom");
template tryMatch(visitors...)
Behaves as match but doesn't enforce at compile time that all types can be handled by the visiting functions.
Throws:
Exception if naryFun!visitors can't be called with provided arguments
Examples:
import std.exception: assertThrown;
struct Asteroid { uint size; }
struct Spaceship { uint size; }
alias SpaceObject = Variant!(Asteroid, Spaceship);

alias collideWith = tryMatch!(
    (Asteroid x, Asteroid y) => "a/a",
    // No visitor for A/S pair 
    // (Asteroid x, Spaceship y) => "a/s",
    (Spaceship x, Asteroid y) => "s/a",
    (Spaceship x, Spaceship y) => "s/s",
);

import mir.utility: min;
alias oops = match!((a, b) => (a.size + b.size) > 3 && min(a.size, b.size) > 1);

alias collide = (x, y) => oops(x, y) ? "big-boom" : collideWith(x, y);

auto ea = Asteroid(1);
auto es = Spaceship(2);
auto oa = SpaceObject(ea);
auto os = SpaceObject(es);

// Asteroid-Asteroid
assert(collide(ea, ea) == "a/a");
assert(collide(ea, oa) == "a/a");
assert(collide(oa, ea) == "a/a");
assert(collide(oa, oa) == "a/a");

// Asteroid-Spaceship
assertThrown!Exception(collide(ea, es));
assertThrown!Exception(collide(ea, os));
assertThrown!Exception(collide(oa, es));
assertThrown!Exception(collide(oa, os));

 // not enough information to deduce the type from (ea, es) pair
static assert(is(typeof(collide(ea, es)) == void));
// can deduce the type based on other return values
static assert(is(typeof(collide(ea, os)) == string));
static assert(is(typeof(collide(oa, es)) == string));
static assert(is(typeof(collide(oa, os)) == string));

// Spaceship-Asteroid
assert(collide(es, ea) == "s/a");
assert(collide(es, oa) == "s/a");
assert(collide(os, ea) == "s/a");
assert(collide(os, oa) == "s/a");

// Spaceship-Spaceship
assert(collide(es, es) == "big-boom");
assert(collide(es, os) == "big-boom");
assert(collide(os, es) == "big-boom");
assert(collide(os, os) == "big-boom");
template optionalMatch(visitors...)
Behaves as match but doesn't enforce at compile time that all types can be handled by the visiting functions.
Returns:
nullable variant, null value is used if naryFun!visitors can't be called with provided arguments.
Examples:
struct Asteroid { uint size; }
struct Spaceship { uint size; }
alias SpaceObject = Variant!(Asteroid, Spaceship);

alias collideWith = optionalMatch!(
    (Asteroid x, Asteroid y) => "a/a",
    // No visitor for A/S pair 
    // (Asteroid x, Spaceship y) => "a/s",
    (Spaceship x, Asteroid y) => "s/a",
    (Spaceship x, Spaceship y) => "s/s",
);

import mir.utility: min;
alias oops = match!((a, b) => (a.size + b.size) > 3 && min(a.size, b.size) > 1);

alias collide = (x, y) => oops(x, y) ? Nullable!string("big-boom") : collideWith(x, y);

auto ea = Asteroid(1);
auto es = Spaceship(2);
auto oa = SpaceObject(ea);
auto os = SpaceObject(es);

// Asteroid-Asteroid
assert(collide(ea, ea) == "a/a");
assert(collide(ea, oa) == "a/a");
assert(collide(oa, ea) == "a/a");
assert(collide(oa, oa) == "a/a");

// Asteroid-Spaceship
// assert(collide(ea, es).isNull);  // Compiler error: incompatible types
assert(collideWith(ea, es).isNull); // OK
assert(collide(ea, os).isNull);
assert(collide(oa, es).isNull);
assert(collide(oa, os).isNull);


// Spaceship-Asteroid
assert(collide(es, ea) == "s/a");
assert(collide(es, oa) == "s/a");
assert(collide(os, ea) == "s/a");
assert(collide(os, oa) == "s/a");

// Spaceship-Spaceship
assert(collide(es, es) == "big-boom");
assert(collide(es, os) == "big-boom");
assert(collide(os, es) == "big-boom");
assert(collide(os, os) == "big-boom");

// check types  

static assert(!__traits(compiles, collide(Asteroid.init, Spaceship.init)));
static assert(is(typeof(collideWith(Asteroid.init, Spaceship.init)) == Nullable!()));

static assert(is(typeof(collide(Asteroid.init, Asteroid.init)) == Nullable!string));
static assert(is(typeof(collide(Asteroid.init, SpaceObject.init)) == Nullable!string));
static assert(is(typeof(collide(SpaceObject.init, Asteroid.init)) == Nullable!string));
static assert(is(typeof(collide(SpaceObject.init, SpaceObject.init)) == Nullable!string));
static assert(is(typeof(collide(SpaceObject.init, Spaceship.init)) == Nullable!string));
static assert(is(typeof(collide(Spaceship.init, Asteroid.init)) == Nullable!string));
static assert(is(typeof(collide(Spaceship.init, SpaceObject.init)) == Nullable!string));
static assert(is(typeof(collide(Spaceship.init, Spaceship.init)) == Nullable!string));
template autoMatch(visitors...)
Behaves as match but doesn't enforce at compile time that all types can be handled by the visiting functions.
Returns:
optionally nullable type, null value is used if naryFun!visitors can't be called with provided arguments.
Examples:
struct Asteroid { uint size; }
struct Spaceship { uint size; }
alias SpaceObject = Variant!(Asteroid, Spaceship);

alias collideWith = autoMatch!(
    (Asteroid x, Asteroid y) => "a/a",
    // No visitor for A/S pair 
    // (Asteroid x, Spaceship y) => "a/s",
    (Spaceship x, Asteroid y) => "s/a",
    (Spaceship x, Spaceship y) => "s/s",
);

import mir.utility: min;
alias oops = match!((a, b) => (a.size + b.size) > 3 && min(a.size, b.size) > 1);

import mir.conv: to;
alias collide = (x, y) => oops(x, y) ? "big-boom".to!(typeof(collideWith(x, y))) : collideWith(x, y);

auto ea = Asteroid(1);
auto es = Spaceship(2);
auto oa = SpaceObject(ea);
auto os = SpaceObject(es);

// Asteroid-Asteroid
assert(collide(ea, ea) == "a/a");
assert(collide(ea, oa) == "a/a");
assert(collide(oa, ea) == "a/a");
assert(collide(oa, oa) == "a/a");

// Asteroid-Spaceship
// assert(collide(ea, es).isNull);  // Compiler error: incompatible types
assert(collideWith(ea, es).isNull); // OK
assert(collide(ea, os).isNull);
assert(collide(oa, es).isNull);
assert(collide(oa, os).isNull);

// Spaceship-Asteroid
assert(collide(es, ea) == "s/a");
assert(collide(es, oa) == "s/a");
assert(collide(os, ea) == "s/a");
assert(collide(os, oa) == "s/a");

// Spaceship-Spaceship
assert(collide(es, es) == "big-boom");
assert(collide(es, os) == "big-boom");
assert(collide(os, es) == "big-boom");
assert(collide(os, os) == "big-boom");

// check types  

static assert(!__traits(compiles, collide(Asteroid.init, Spaceship.init)));
static assert(is(typeof(collideWith(Asteroid.init, Spaceship.init)) == Nullable!()));

static assert(is(typeof(collide(Asteroid.init, Asteroid.init)) == string));
static assert(is(typeof(collide(SpaceObject.init, Asteroid.init)) == string));
static assert(is(typeof(collide(Spaceship.init, Asteroid.init)) == string));
static assert(is(typeof(collide(Spaceship.init, SpaceObject.init)) == string));
static assert(is(typeof(collide(Spaceship.init, Spaceship.init)) == string));

static assert(is(typeof(collide(Asteroid.init, SpaceObject.init)) == Nullable!string));
static assert(is(typeof(collide(SpaceObject.init, SpaceObject.init)) == Nullable!string));
static assert(is(typeof(collide(SpaceObject.init, Spaceship.init)) == Nullable!string));
template getMember(string member)
Applies a member handler to the given Variant depending on the held type, ensuring that all types are handled by the visiting handler.
Examples:
static struct S { auto bar(int a) { return a; }}
static struct C { alias bar = (double a) => a * 2; }

alias V = Variant!(S, C);

V x = S();
V y = C();

static assert(is(typeof(x.getMember!"bar"(2)) == Variant!(int, double)));
assert(x.getMember!"bar"(2) == 2);
assert(y.getMember!"bar"(2) != 4);
assert(y.getMember!"bar"(2) == 4.0);
template tryGetMember(string member)
Behaves as getMember but doesn't enforce at compile time that all types can be handled by the member visitor.
Throws:
Exception if member can't be accessed with provided arguments
Examples:
static struct S { int bar(int a) { return a; }}
static struct C { alias Bar = (double a) => a * 2; }

alias V = Variant!(S, C);

V x = S();
V y = C();

static assert(is(typeof(x.tryGetMember!"bar"(2)) == int));
static assert(is(typeof(y.tryGetMember!"Bar"(2)) == double));
assert(x.tryGetMember!"bar"(2) == 2);
assert(y.tryGetMember!"Bar"(2) == 4.0);
Examples:
alias Number = Variant!(int, double);

Number x = Number(23);
Number y = Number(1.0);

assert(x.visit!((int v) => true, (float v) => false));
assert(y.visit!((int v) => false, (float v) => true));
template optionalGetMember(string member)
Behaves as getMember but doesn't enforce at compile time that all types can be handled by the member visitor.
Returns:
nullable variant, null value is used if the member can't be called with provided arguments.
template autoGetMember(string member)
Behaves as getMember but doesn't enforce at compile time that all types can be handled by the member visitor.
Returns:
optionally nullable type, null value is used if the member can't be called with provided arguments.