import std.stdio : writeln; import std.traits : ParameterTypeTuple, ParameterIdentifierTuple, ParameterDefaultValueTuple; import std.typetuple : TypeTuple, staticIndexOf, staticMap; import std.conv : to; struct NamedArg(string _name, Arg) { enum name = _name; Arg value; alias value this; } template isNotNamedArg(T...) if (T.length == 1) { enum isNotNamedArg = !isNamedArg!T; } template isNamedArg(T...) if (T.length == 1) { static if (is(typeof(T[0]))) { enum isNamedArg = isNamedArg!(typeof(T[0])); } else { enum isNamedArg = is(T[0] == NamedArg!U, U...); } } unittest { assert( isNamedArg!(NamedArg!("a", int))); assert(!isNamedArg!int); NamedArg!("a", float) f; assert(isNamedArg!f); } final abstract class Args { @property static auto opDispatch(string name, Arg)(Arg arg) { return NamedArg!(name, Arg)(arg); } } template staticFilter(alias F, T...) { static if (T.length == 0) { alias staticFilter = TypeTuple!(); } else static if (T.length == 1) { static if (F!T) { alias staticFilter = TypeTuple!(T); } else { alias staticFilter = TypeTuple!(); } } else { alias staticFilter = TypeTuple!( staticFilter!(F, T[0..$/2]), staticFilter!(F, T[$/2..$]), ); } } template Head(T...) if (T.length) { alias Head = TypeTuple!(T[0]); } template Tail(T...) { static if (T.length) { alias Tail = T[1..$]; } else { alias Tail = TypeTuple!(); } } template Chop(size_t block, alias F) { alias Chop = TypeTuple!(); } template Chop(size_t blocks, alias F, T...) if (T.length && T.length % blocks == 0) { alias Chop = TypeTuple!( F!(T[0..$/blocks]), Chop!(blocks-1, F, T[$/blocks..$]) ); } template staticZip(size_t N, alias F) { alias staticZip = TypeTuple!(); } template staticZip(size_t N, alias F, T...) { static if (T.length == 0) { alias staticZip = TypeTuple!(); } else { alias staticZip = TypeTuple!( F!(Chop!(N, Head, T)), staticZip!(N, F, Chop!(N, Tail, T)) ); } } final abstract class ArgInfo(string _name, _type, _defaultValue...) if (_defaultValue.length == 1) { enum name = _name; alias type = _type; alias value = _defaultValue; } template staticToString(T...) { enum staticToString = T.stringof; } template staticNameOf(T) { enum staticNameOf = T.name; } string sortNamedArgs(string[] realNames, string[] defaults, string[] lookHere, int line = __LINE__, string file = __FILE__) { string result = "alias sorted = TypeTuple!("; foreach (i, name; realNames) { bool found = false; foreach (j, needle; lookHere) { if (needle == name) { if (found) { return "static assert(false, \"" ~ file ~ "(" ~ to!string(line) ~ "): Duplicate parameter '" ~ needle ~ "'.\");"; } found = true; result ~= "lookup!(" ~ to!string(j) ~ ", namedArgs), "; } } if (!found) { result ~= "lookup!(" ~ to!string(i) ~ ", paramDefaults[unnamedArgs.length..$]), "; } } return result ~ ");"; } template lookup(int i, T...) { alias lookup = T[i]; } struct Compare(T1, T2, string _name) { static if (isNamedArg!T1) { alias Type1 = typeof(T1.value); } else { alias Type1 = T1; } enum name = _name; alias Type2 = T2; } template checkTypesImpl(int line = __LINE__, string file = __FILE__, int idx, T...) { static if(T.length == 0) { enum checkTypesImpl = true; } else { alias T1 = TypeTuple!(T[0].Type1); alias T2 = TypeTuple!(T[0].Type2); enum N1 = T[0].name; static assert(!is(T1 == TypeTuple!void), file ~ "(" ~ to!string(line) ~ "): Missing value for parameter " ~ N1 ~ "."); static assert(is(T2 : T1), file ~ "(" ~ to!string(line) ~ "): type mismatch. Expected " ~ T1.stringof ~ ", got " ~ T2.stringof ~ " for parameter " ~ N1 ~ "."); enum checkTypesImpl = checkTypesImpl!(line, file, idx+1, T[1..$]); } } template checkTypes(int line = __LINE__, string file = __FILE__, int idxStart, T...) { enum checkTypes = checkTypesImpl!(line, file, idxStart, staticZip!(3, Compare, T)); } template nameify(alias fn) { auto nameify(int line = __LINE__, string file = __FILE__, T...)(T args) { auto namedArgs = staticFilter!(isNamedArg, args); auto unnamedArgs = staticFilter!(isNotNamedArg, args); static assert(is(typeof(TypeTuple!(unnamedArgs, namedArgs)) == T), "Named arguments must follow unnamed arguments"); alias paramNames = ParameterIdentifierTuple!fn; alias paramDefaults = ParameterDefaultValueTuple!fn; alias paramTypes = ParameterTypeTuple!fn; alias allArgs = staticZip!(3, ArgInfo, paramNames[unnamedArgs.length..$], paramTypes[unnamedArgs.length..$], paramDefaults[unnamedArgs.length..$]); mixin(sortNamedArgs( [paramNames[unnamedArgs.length..$]], [staticMap!(staticToString, paramDefaults[unnamedArgs.length..$])], [staticMap!(staticNameOf, typeof(namedArgs))], line, file)); static assert(sorted.length == paramTypes[unnamedArgs.length..$].length); static assert(checkTypes!(line, file, unnamedArgs.length, typeof(sorted), paramTypes[unnamedArgs.length..$], paramNames[unnamedArgs.length..$])); fn(unnamedArgs, sorted); } } void test(int a, int b, string c = "foo", int d = 14) { writeln(a); writeln(b); writeln(c); writeln(d); } alias test2 = nameify!test; void main() { test2(1, Args.d = 4, Args.c = "bar", Args.b = 13); }