Skip to content

Instantly share code, notes, and snippets.

@Biotronic
Last active March 3, 2019 21:46
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save Biotronic/8a2664c050f01aed5e0c45950509022b to your computer and use it in GitHub Desktop.
Save Biotronic/8a2664c050f01aed5e0c45950509022b to your computer and use it in GitHub Desktop.
Compile-time structs in D
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/p6n5hu$uhr$1@digitalmars.com
*
* 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