Last active
March 3, 2019 21:46
-
-
Save Biotronic/8a2664c050f01aed5e0c45950509022b to your computer and use it in GitHub Desktop.
Compile-time structs in D
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
module packs; | |
import std.meta; | |
import std.traits; | |
/* | |
* Named packs (compile-time structs, or compile-time tuples) in D. | |
* | |
* PackedAliasSeq was recently discussed[0] on the D forum. Turns out, an | |
* implementation already exists in std.meta, called Pack. | |
* While Pack enables the implementation of some interesting algorithms (StaticZip, | |
* for one), its unstructured nature leaves something to be desired. | |
* This is my attempt at filling the gaps, by defining a NamedPack, which is | |
* essentially a compile-time struct. It has named fields that can hold types, | |
* aliases, values, templates, etc. | |
* Since it's just a template, no typeinfo is generated, and it should thus not lead | |
* to bloated executables. | |
* | |
* [0]: https://forum.dlang.org/post/[email protected] | |
* | |
* This module defines two main templates: | |
* NamedPack | |
* The NamedPack itself. Takes as arguments a list of alternating field names | |
* (strings) and field values (aliases, types or values) | |
* DefinePack | |
* Defines a NamedPack 'pattern'. Essentially just sugar, this sets up a | |
* structure to be reused in the future. | |
*/ | |
/** | |
* Defines a compile-time pattern to be reused. | |
* Params: | |
* T = An alternating list of field definitions. Each field definition is a string | |
* containing its name, and a type or template defining the values the field | |
* can hold. | |
* Two templates have special meaning. If the field type is Type or Alias, the | |
* field will take either a type or an alias, respectively. | |
*/ | |
public template DefinePack(T...) | |
if (T.length % 2 == 0 && | |
distinctFieldNames!(Stride!(2, T)) && | |
allSatisfy!(isDefinePackArgument, Stride!(2, T[1..$]))) | |
{ | |
template DefinePack(T2...) | |
if (T2.length == values.length && is(ArgsMatch!T2)) | |
{ | |
alias DefinePack = NamedPack!(staticZip!(AliasSeq, Pack!names, Pack!T2)); | |
} | |
alias names = Stride!(2, T); | |
alias values = Stride!(2, T[1..$]); | |
private template ArgsMatch(T2...) | |
{ | |
static foreach (i; 0..values.length) | |
{ | |
static if (__traits(isSame, values[i], Type)) static assert(is(T2[i])); | |
else static if (__traits(isSame, values[i], Alias)) static assert(isAlias(T2[i])); | |
else static if (__traits(isTemplate, values[i])) static assert(isInstanceOf!(values[i], T2[i])); | |
else static if (is(values[i])) static assert(is(typeof(T2[i]) : values[i])); | |
else static assert(false, "Shouldn't ever happen."); | |
} | |
alias ArgsMatch = int; | |
} | |
} | |
/// ditto | |
@safe unittest | |
{ | |
alias Field = DefinePack!("Type", Type, "name", string, "offset", int); | |
alias field1 = Field!(int, "a", 0); | |
assert(is(field1.Type == int)); | |
assert(field1.name == "a"); | |
assert(field1.offset == 0); | |
assert(field1.equals!(NamedPack!("Type", int, "name", "a", "offset", 0))); | |
} | |
/// ditto | |
@safe unittest | |
{ | |
alias Foo = DefinePack!("foo", Pack); | |
alias foo1 = Foo!(Pack!(1,2,3,4)); | |
assert([foo1.foo.expand] == [1,2,3,4]); | |
} | |
// Check that DefinePack's arguments are correct. | |
private enum isDefinePackArgument(T...) = T.length == 1 && (is(T[0]) || __traits(isTemplate, T[0])); | |
/// Placeholder for a DefinePack field that is expected to be a type. | |
public template Type() {} | |
/** | |
* Defines a 'compile-time struct' with named fields. | |
* | |
* Params: | |
* T = An alternating list of fields. Each field consists of a string name and a | |
* value, which can be any valid template parameter. | |
*/ | |
public template NamedPack(T...) | |
if (T.length % 2 == 0 && distinctFieldNames!(Stride!(2, T))) | |
{ | |
static foreach (i; 0.. T.length/2) | |
{ | |
static assert(isSomeString!(typeof(T[i*2]))); | |
mixin("alias "~T[i*2]~" = Alias!(T[i*2+1]);"); | |
} | |
alias names = Stride!(2, T); | |
static if (T.length > 0) alias values = Stride!(2, T[1..$]); | |
else alias values = AliasSeq!(); | |
alias expand = T; | |
alias slice(size_t start, size_t end) = NamedPack!(T[2*start..2*end]); | |
template equals(alias rhs : NamedPack!U, U...) | |
{ | |
enum equals = Pack!T.equals!(Pack!U); | |
} | |
} | |
/// ditto | |
@safe unittest | |
{ | |
alias a = NamedPack!("a", 1, "b", int, "c", mixin(__MODULE__)); | |
alias b = NamedPack!("a", 1, "b", int, "c", mixin(__MODULE__)); | |
assert(a.a == 1); | |
assert(is(a.b == int)); | |
static assert(a.equals!b); | |
} | |
/// Checks if T is a NamedPack. | |
public template isNamedPack(alias T) | |
{ | |
alias isNamedPackImpl(alias a : NamedPack!b, b...) = int; | |
enum isNamedPack = is(isNamedPackImpl!T); | |
} | |
private enum bool distinctFieldNames(names...) = __traits(compiles, | |
{ | |
static foreach (name; names) | |
static if (is(typeof(name) : string)) | |
mixin("enum int" ~ name ~ " = 0;"); | |
}); | |
/** | |
* Defines an alias tuple - a set of template parameters that belong together and | |
* don't auto-expand. | |
* | |
* Params: | |
* T = The template parameters to hold onto. | |
*/ | |
public template Pack(T...) | |
{ | |
alias expand = T; | |
enum length = T.length; | |
template equals(alias rhs) | |
{ | |
static if (T.length != rhs.expand.length) | |
enum equals = false; | |
else static if (T.length == 0) | |
enum equals = true; | |
else | |
enum equals = Pack!(T[1..$]).equals!(Pack!(rhs.expand[1..$])) && isSame!(T[0], rhs.expand[0]); | |
} | |
} | |
/// ditto | |
@safe unittest | |
{ | |
alias a = Pack!(int, 1); | |
alias b = Pack!("Sugar", [19]); | |
alias c = AliasSeq!(a, b); | |
alias d = AliasSeq!(a.expand, b.expand); | |
static assert(c.length == 2); | |
static assert(d.length == 4); | |
} | |
/// Checks if T is a Pack. | |
public template isPack(alias T) | |
{ | |
alias isPackImpl(alias a : Pack!b, b...) = int; | |
enum isPack = is(isPackImpl!T); | |
} | |
/// Gets the first element of a Pack. | |
public template Head(alias T) | |
if (isPack!T && T.length > 0) | |
{ | |
alias Head = Alias!(T.expand[0]); | |
} | |
/// Gets the tail of a Pack (every element but the first). | |
public template Tail(alias T) | |
if (isPack!T && T.length > 0) | |
{ | |
alias Tail = Pack!(T.expand[1..$]); | |
} | |
/// Gets the first half of a Pack. | |
public template HeadHalf(alias T) | |
if (isPack!T) | |
{ | |
alias HeadHalf = Pack!(T.expand[0..$/2]); | |
} | |
/// Gets the last half of a Pack. | |
public template TailHalf(alias T) | |
if (isPack!T) | |
{ | |
alias TailHalf = Pack!(T.expand[$/2..$]); | |
} | |
/** | |
* Iterates over two Packs in lockstep, instantiating Fn!(p1[i], p2[i]) for each step. | |
* | |
* Params: | |
* Fn = A template to be instantiated on each pair of values in p1 and p2. | |
* p1 = The first Pack to iterate over. | |
* p2 = The second Pack to iterate over. | |
*/ | |
public template staticZip(alias Fn, alias p1, alias p2) | |
if (isPack!p1 && isPack!p2 && p1.length == p2.length) | |
{ | |
static if (p1.length == 0) | |
alias staticZip = AliasSeq!(); | |
else static if (p1.length == 1) | |
alias staticZip = AliasSeq!(Fn!(Head!p1, Head!p2)); | |
else | |
alias staticZip = AliasSeq!( | |
staticZip!(Fn, HeadHalf!p1, HeadHalf!p2), | |
staticZip!(Fn, TailHalf!p1, TailHalf!p2) | |
); | |
} | |
/// ditto | |
@safe unittest | |
{ | |
alias a = Pack!(1,2); | |
alias b = Pack!(3,4); | |
enum combine(int a, int b) = a+b; | |
alias c = staticZip!(combine, a, b); | |
static assert(c.length == 2); | |
static assert(c[0] == 4); | |
static assert(c[1] == 6); | |
static assert(Pack!c.equals!(Pack!c)); | |
} | |
private template expectType(T) {} | |
template isSame(ab...) | |
if (ab.length == 2) | |
{ | |
static if (__traits(compiles, expectType!(ab[0]), | |
expectType!(ab[1]))) | |
{ | |
enum isSame = is(ab[0] == ab[1]); | |
} | |
else static if (!__traits(compiles, expectType!(ab[0])) && | |
!__traits(compiles, expectType!(ab[1])) && | |
__traits(compiles, expectBool!(ab[0] == ab[1]))) | |
{ | |
static if (!__traits(compiles, &ab[0]) || | |
!__traits(compiles, &ab[1])) | |
enum isSame = (ab[0] == ab[1]); | |
else | |
enum isSame = __traits(isSame, ab[0], ab[1]); | |
} | |
else | |
{ | |
enum isSame = __traits(isSame, ab[0], ab[1]); | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment