namespace F { template <typename T1, ..., typename Tn> struct Union { ... template <typename U> Union(const U &x); template <typename U> operator const U&() const; template <typename U> constexpr unsigned index(); }; template <typename T1, ..., typename Tn> pure unsigned index(Union<T1, ..., Tn> u); } |
A discriminated union between n child types.
A Union object can be constructed from an object of any child type using an up-cast, e.g.:
Ti x = ...; auto u = (Union<T1, ..., Tn>)x;This will create a copy of x and assumes Ti is copy constructible.
The child object can be retrieved via a downcast, e.g.:
auto &y = (Ti &)u; auto y = (Ti)u; // AlternativeNote that it is undefined (nullptr dereference) if u was not constructed from a Ti object.
The type of a Union object can be retrieved at runtime via the index function, e.g.:
switch(index(u)) { ... }The index function returns 0 if u is the first child type, 1 for the second child type, etc. The index of a child type can be determined via the static function:
Union<T1, ..., Tn>::index<Ti>() = i-1
A Union object is compact, i.e.
sizeof(Union<T1, ..., Tn>) = sizeof(void *)This means that Union objects can be passed-by-copy without performance loss. A Union object is implemented internally as tagged pointers, and can support up to 16 child types.
See the documentation on value functions for more information.
Example 1We can use a Union to define a simple Maybe type as follows:
struct Nothing { }; template <typename T> using Maybe = F::Union<Nothing, T>; #define NOTHING Maybe<void>::index<Nothing>() #define JUST Maybe<void>::index<void>()This is analogous to the Maybe type from Haskell.
We can define Maybe comparison as follows:
template <typename T> int compare(Maybe<T> x, Maybe<T> y) { switch (index(x)) { case NOTHING: switch (index(y)) { case NOTHING: return 0; case JUST: return 1; } case JUST: switch (index(y)) { case NOTHING: return -1; case JUST: return compare((T)x, (T)y); } } }Example 2
We can define a List type as follows:
template <typename T> struct ListNode; struct ListEmpty; template <typename T> using List = F::Union<ListNode<T>, ListEmpty>; template <typename T> struct ListNode { T elem; List<T> next; }; #define LIST_EMPTY List<void>::index<ListEmpty>() #define LIST_NODE List<void>::index<ListNode<void>>()
We can calculate the length of a list as follows:
template <typename T> int length(List<T> xs) { int len = 0; while (true) { switch (index(xs)) { case LIST_EMPTY: return len; case LIST_NODE: len++; auto &node = (ListNode &)xs; xs = node.next; } } }
namespace F { template <typename T1, ..., typename Tn> struct Result { ... }; } |
A specialized tuple of objects. Unlike the related Tuple type, a Result can be used to return multiple values from a function without relying on heap allocated memory. Furthermore, Result can be matched using C++17's structure binding feature:
Result<float, float, float> get_point_3D(...); auto [x, y, z] = get_point_3D(...);This provides a convenient way to extract multiple return values from a function call.
An object of type Result is not necessarily compact.
namespace F { template <typename T1, ..., typename Tn> struct Tuple { ... }; } |
A tuple of objects. Unlike the related Result type, a a Tuple is a compact object and relies on heap allocation.
See the documentation on tuple functions for more information.
namespace F { template <typename T> struct Optional { ... }; } |
An optional object that may or may not hold the underlying value. Unlike the related Maybe type, an Optional is not a compact object.
See the documentation on value functions for more information.
namespace F { struct Nothing { // Empty }; template <typename T> using Maybe = Union<Nothing, T>; enum { NOTHING = Maybe<void>::index<Nothing>(), JUST = Maybe<void>::index<void>() }; } |
A maybe
type that may or may not hold a value.
Unlike the related Optional type, a
Maybe is a compact object and is more likely to rely on
heap allocation.
See the documentation on maybe functions for more information.
namespace F { struct Nil { // Empty }; template <typename T> struct Node; template <typename T> using List = Union<Nil, Node<T>>; template <typename T> struct Node { Value<T> elem; List<T> next; }; enum { NIL = List<void>::index<Nil>(), NODE = List<void>::index<Node<void>>() }; } |
A (singularly-linked) list type. See the documentation on list functions for more information.
namespace F { template <typename K, typename V> struct Map { ... }; } |
A key-value map type. See the documentation on map functions for more information.
namespace F { template <typename T> struct Set { ... }; } |
A set type. See the documentation on set functions for more information.
namespace F { template <typename T> struct Vector { ... }; } |
A vector type. See the documentation on vector functions for more information.
namespace F { struct String { ... }; } |
A unicode string type. See the documentation on string functions for more information.