Last active
March 5, 2018 12:25
-
-
Save Biotronic/6182f24eabb444bca90f29d7a82bd428 to your computer and use it in GitHub Desktop.
Suggested design for Layout!T
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 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))); | |
} |
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); | |
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]); | |
} | |
} |
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 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