Struct SuperStruct

A Variant which exposes members that are common across all SubTypes.

A SuperStruct!(SubTypes...) wraps an Algebraic!(SubTypes...). It can hold a single value from any of its SubTypes.

Unlike a Variant/Algebraic, SuperStruct exposes access to 'common' members that have compatible signatures.

A member is 'common' if its name describes a public function or field on every one of SubTypes. A call signature for a given member is 'compatible' if, for an instance of any one of SubTypes, that member can be called with the provided set of arguments and_ all such calls have a common return type.

SuperStruct ignores members beginning with "_" (double underscore).

Constructors

Name Description
this Construct and populate with an initial value.

Methods

Name Description
opAssign Populate the SuperStruct with an instance of one of its sub-types.
opBinary Operators are forwarded to the underlying type.
opBinary Perform a binary operation between two superstructs.
opCall Operators are forwarded to the underlying type.
opCast Extract the wrapped value of type T from the SuperStruct. Throws a VariantException if conversion to T is not possible.
opCmp Operators are forwarded to the underlying type.
opDollar Operators are forwarded to the underlying type.
opEquals Operators are forwarded to the underlying type.
opEquals Compare one SuperStruct to another of the same type.
opIndex Operators are forwarded to the underlying type.
opOpAssign Operators are forwarded to the underlying type.
opOpAssign Perform a binary operation between two superstructs.
opSlice Operators are forwarded to the underlying type.
opUnary Operators are forwarded to the underlying type.

Example

If all types have a matching field, it gets exposed:

struct Foo { int a; }
struct Bar { int a; }
auto foobar = SuperStruct!(Foo, Bar)(Foo(1));
foobar.a = 5;
assert(foobar.a == 5);

Example

If all types have a matching method, all compatible overloads are exposed:

struct Foo {
  int fun(int i) { return i; }
  int fun(int a, int b) { return a + b; }
}
struct Bar {
  int fun(int i) { return i; }
  int fun(int a, int b) { return a + b; }
  int fun(int a, int b, int c) { return a + b + c; }
}

auto foobar = SuperStruct!(Foo, Bar)(Foo());
assert(foobar.fun(1)    == 1);
assert(foobar.fun(1, 2) == 3);
assert(!__traits(compiles, foobar.fun(1,2,3))); // no such overload on Foo

Example

If a name is a field on one type and a method on another, it is exposed:

struct Foo { int a; }
struct Bar {
  private int _a;
  int a() { return _a; }
  int a(int val) { return _a = val; }
}

auto foo = SuperStruct!(Foo, Bar)(Foo());
foo.a = 5;          // sets Foo.a
assert(foo.a == 5); // gets Foo.a

auto bar = SuperStruct!(Foo, Bar)(Bar());
bar.a = 5;          // invokes Bar.a(int val)
assert(bar.a == 5); // invokes Bar.a()

Example

Templated members can be forwarded too:

struct Foo {
  int val;
  auto transmorgrify(alias fn1, alias fn2)() {
    return fn2(fn2(val));
  }
}

struct Bar {
  auto transmorgrify(alias fn1, alias fn2)() { return 0; }
}

static auto add1 = (int a) => a + 1;

alias FooBar = SuperStruct!(Foo, Bar);

FooBar f = Foo(3);
assert(f.transmorgrify!(add1, add1) == 5); // 3 + 1 + 1

FooBar b = Bar();
assert(b.transmorgrify!(add1, add1) == 0);

Example

Operators get forwarded to the underlying type

struct Foo {
  auto opSlice() { return [1,2,3]; }
}

struct Bar {
  auto opSlice() { return [4,5,6]; }
}

SuperStruct!(Foo, Bar) fb = Foo();
assert(fb[] == [1,2,3]);

Example

SuperStructs of the same type can be compared:

struct A { int i; }
struct B { int i; }
struct C { int i; bool opEquals(T)(T other) { return other.i == i; } }

SuperStruct!(A, B, C) a0 = A(0);
SuperStruct!(A, B, C) a1 = A(1);
SuperStruct!(A, B, C) b0 = B(0);
SuperStruct!(A, B, C) c0 = C(0);

assert(a0 == a0); // same type, same value
assert(a0 != a1); // same type, different value
assert(a0 != b0); // incomparable types return false
assert(a0 == c0); // different but comparable types

// SuperStructs with different sets of source types are not comparable, even
// if the types they happen to contain at the moment are.
SuperStruct!(A, B) different = A(0);
static assert(!__traits(compiles, different == a0));

Example

If members have common signatures but no common return type, the exposed

member returns a SuperStruct of the possible return types.

struct A { auto fun() { return 1; } }
struct B { auto fun() { return "hi"; } }

SuperStruct!(A, B) a = A();
SuperStruct!(A, B) b = B();

assert(a.fun == SuperStruct!(int, string)(1));
assert(b.fun == SuperStruct!(int, string)("hi"));

Example

Use cast(T) to convert a SuperStruct to a subtype

import std.exception : assertThrown;

struct Square { float size; }
struct Circle { float r; }

SuperStruct!(Square, Circle) sqr = Square(4);
SuperStruct!(Square, Circle) cir = Circle(6);

assert((cast(Square) sqr).size == 4);
assert((cast(Circle) cir).r    == 6);

assertThrown!VariantException(cast(Circle) sqr);
assertThrown!VariantException(cast(Square) cir);

Authors

Ryan Roden-Corrent (rcorre)

Copyright

© 2015, Ryan Roden-Corrent

License

MIT