Skip to content

Instantly share code, notes, and snippets.

@Biotronic
Last active March 5, 2018 12:25
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save Biotronic/6182f24eabb444bca90f29d7a82bd428 to your computer and use it in GitHub Desktop.
Save Biotronic/6182f24eabb444bca90f29d7a82bd428 to your computer and use it in GitHub Desktop.
Suggested design for Layout!T
module layout;
import std.meta;
import std.traits;
import packs;
/**
* Deconstructs a type into its primitive fields, recursing into sub-types.
*
* Params:
* T = The type to deconstruct.
*
* Returns:
* An AliasSeq of `Field` named packs. Each Field entry has fields `Type`, `name`, and `offset`.
*
*/
alias Layout(T) = LayoutImpl!(T, 0, "");
/// ditto
@safe unittest
{
struct S
{
union
{
int a;
float b;
}
}
struct S2
{
S s1, s2;
}
alias layout = Layout!S2;
static assert(layout.length == 4);
static assert(layout[0].equals!(Field!(int, "s1.a", 0)));
static assert(layout[1].equals!(Field!(float, "s1.b", 0)));
static assert(layout[2].equals!(Field!(int, "s2.a", 4)));
static assert(layout[3].equals!(Field!(float, "s2.b", 4)));
}
private template LayoutImpl(T, size_t offset, string prefix)
{
template getMemberInfo(string memberName, size_t idx = 0)
{
static if (__traits(identifier, T.tupleof[idx]) != memberName)
alias getMemberInfo = getMemberInfo!(memberName, idx + 1);
else
alias getMemberInfo = Field!(typeof(T.tupleof[idx]), prefix~memberName, T.tupleof[idx].offsetof+offset);
}
template impl(names...)
{
static if (names.length == 0)
alias impl = AliasSeq!();
else static if (names.length > 1)
alias impl = AliasSeq!(impl!(names[0..$/2]), impl!(names[$/2..$]));
else
{
alias memberInfo = getMemberInfo!(names[0]);
static if (is(memberInfo.Type == struct))
alias impl = LayoutImpl!(memberInfo.Type, memberInfo.offset + offset, memberInfo.name~".");
else
alias impl = memberInfo;
}
}
static if (isBuiltinType!T)
{
alias LayoutImpl = AliasSeq!(Field!(T, "", 0));
}
else
{
static if (is(T == class))
alias LayoutTmp = AliasSeq!(Field!(T, prefix~"__vptr", offset), Field!(T, prefix~"__monitor", offset + size_t.sizeof), impl!(FieldNameTuple!T));
else static if (is(T == interface))
alias LayoutTmp = AliasSeq!(Field!(T, prefix~"__vptr", offset), impl!(FieldNameTuple!T));
else
alias LayoutTmp = impl!(FieldNameTuple!T);
template Cmp(Ts...)
{
static if (Ts[0].offset != Ts[1].offset)
enum Cmp = Ts[0].offset < Ts[1].offset;
else
enum Cmp = Ts[0].name < Ts[1].name;
}
alias LayoutImpl = staticSort!(Cmp, LayoutTmp);
}
}
alias Field = DefinePack!("Type", Type, "name", string, "offset", size_t);
@safe unittest
{
struct S1
{
align(1):
int n;
byte b;
int n2;
}
alias layout = Layout!S1;
assert(layout.length == 3);
static assert(is(layout[0].Type == int));
static assert( layout[0].name == "n");
static assert( layout[0].offset == 0);
static assert(is(layout[1].Type == byte));
static assert( layout[1].name == "b");
static assert( layout[1].offset == 4);
static assert(is(layout[2].Type == int));
static assert( layout[2].name == "n2");
static assert( layout[2].offset == 5);
}
@safe unittest
{
class C
{
int n;
string s;
}
alias layout = Layout!C;
static assert(layout.length == 4);
static assert(layout[0].equals!(Field!(C, "__vptr", 0)));
static assert(layout[1].equals!(Field!(C, "__monitor", size_t.sizeof)));
static assert(layout[2].equals!(Field!(int, "n", size_t.sizeof*2)));
static assert(layout[3].equals!(Field!(string, "s", size_t.sizeof*2 + 4)));
}
@safe unittest
{
alias layout = Layout!int;
assert(layout.length == 1);
assert(layout[0].equals!(Field!(int, "", 0)));
}
@safe unittest
{
static struct S
{
union
{
int a;
float b;
}
}
alias layout = Layout!S;
assert(layout.length == 2);
assert(layout[0].equals!(Field!(int, "a", 0)));
assert(layout[1].equals!(Field!(float, "b", 0)));
}
@safe unittest
{
struct S1
{
align(1):
int n;
byte b;
int n2;
}
struct S2
{
align(4):
S1 s1, s2;
}
alias layout = Layout!S2;
assert(layout.length == 6);
static assert(is(layout[0].Type == int));
static assert( layout[0].name == "s1.n");
static assert( layout[0].offset == 0);
static assert(is(layout[1].Type == byte));
static assert( layout[1].name == "s1.b");
static assert( layout[1].offset == 4);
static assert(is(layout[2].Type == int));
static assert( layout[2].name == "s1.n2");
static assert( layout[2].offset == 5);
static assert(is(layout[3].Type == int));
static assert( layout[3].name == "s2.n");
static assert( layout[3].offset == 12);
static assert(is(layout[4].Type == byte));
static assert( layout[4].name == "s2.b");
static assert( layout[4].offset == 16);
static assert(is(layout[5].Type == int));
static assert( layout[5].name == "s2.n2");
static assert( layout[5].offset == 17);
}
/**
* Checks if two types have identical layouts.
*
* Params:
* T1 = the first type to compare.
* T2 = the second type to compare.
*/
template hasSameLayout(T1, T2)
{
alias layout1 = Layout!T1;
alias layout2 = Layout!T2;
bool impl()
{
static if (layout1.length != layout2.length)
{
return false;
}
static foreach (i; 0..layout1.length)
{
static if (!is(layout1[i].Type == layout2[i].Type))
return false;
static if (layout1[i].offset != layout2[i].offset)
return false;
}
return true;
}
enum hasSameLayout = impl();
}
/// ditto
@safe unittest
{
struct S1
{
align(1):
int n;
byte b;
int n2;
}
struct S2
{
align(4):
S1 s1, s2;
}
struct S3
{
align(1):
int n;
byte b;
int n2;
align(4):
int n3;
align(1):
byte b2;
int n4;
}
static assert(hasSameLayout!(S2, S3));
}
@safe unittest
{
import privatetest;
alias layout = Layout!SComplex;
assert(layout.length == 2);
assert(layout[0].equals!(Field!(int, "n1", 0)));
assert(layout[1].equals!(Field!(int, "n2", 4)));
}
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);
static assert(is(field1.Type == int));
static assert(field1.name == "a");
static assert(field1.offset == 0);
static 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));
static assert([foo1.foo.expand] == [1,2,3,4]);
}
public template isDefine(alias Define, alias T)
if (isInstanceOf!(DefinePack, Define) && isInstanceOf!(NamedPack, T))
{
alias Dnames = Stride!(2, TemplateArgsOf!Define);
alias Tnames = Stride!(2, TemplateArgsOf!T);
alias values = Stride!(2, TemplateArgsOf!T[1..$]);
static if ([Dnames] == [Tnames])
enum isDefine = is(typeof({ alias a = Define!values; }));
else
enum isDefine = false;
}
@safe unittest
{
alias Field = DefinePack!("Type", Type, "name", string, "offset", int);
alias isField = ApplyLeft!(isDefine, Field);
alias field1 = Field!(int, "a", 0);
alias notField1 = NamedPack!("Type", string, "name", "foo", "offset", 3);
alias notField2 = NamedPack!("Type", "a", "name", int, "offset", [1]);
static assert(isField!field1);
static assert(isField!notField1);
static assert(!isField!notField2);
}
// 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)
{
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__));
static assert(a.a == 1);
static 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]);
}
}
module privatetest;
struct SComplex {
int n1;
private int n2;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment