OK, what I'm trying to do is fairy complicated, but I'll try to explain.
Let's say we want (at compile-time) all derivedMembers
of class someClass
. Then we'd simply do :
const string[] methods = [__traits(derivedMembers,someClass)];
Now, how could we get someClass
from "someClass"
? (yep, its string representation).
Let me explain a bit more what I'm trying to do :
I want to create an "intermediate" function which takes a function
name as an argument (along with a params array) and calls the appropriate function from a list of available static methods in a specific (predefined) set of classes. Like execute("someFunc",["one","two","three"]);
.
Here's the full (test) code :
class Math {
static string noArgs(string[] s) { writeln(s); return ""; }
static string withOneArg(string[] s) { writeln(s); return ""; }
static string withTwoArgs(string[] s) { writeln(s); return ""; }
}
string cases()
{
string ret = "";
const string[] methods = [__traits(derivedMembers,Math)];
foreach (string s; methods)
{
ret ~= "case \"" ~ s ~ "\": return Math."~s~"(params);";
}
return ret;
}
string execute(string what, string[] params)
{
switch (what)
{
mixin(cases());
default: break;
}
return "";
}
The trouble with the above code is that it only looks for methods in Math
. How could I change it, in an elegant D-friendly way, so that it'll go through an array of classes like [Math,String,SomethingElse]
-- it doesn't have to be variable (we need it at compile-time anyway)?
UPDATE:
Tried something along the lines of :
const string[] methods = [__traits(derivedMembers,mixin("Math")];
but it complains that Cannot interpret Math at compile time
.
UPDATE 2:
Also, tried using Object.factory("Math")
but it's still not working. (Perhaps I'm just creating an instance of the Math
class?)
Let me rewrite this to show you some cool tricks:
import std.stdio;
class Math {
static string noArgs(string[] s) { writeln(s); return ""; }
static string withOneArg(string[] s) { writeln(s); return ""; }
static string withTwoArgs(string[] s) { writeln(s); return ""; }
}
class String {
static string oneArg(string[] s) { return null; }
}
string execute(string what, string[] params) {
import std.string;
auto parts = what.split(".");
auto className = parts[0];
auto methodName = parts[1];
import std.typetuple;
switch(className) {
default: assert(0, "unknown class");
foreach(possibleClass; TypeTuple!(Math, String)) {
case possibleClass.stringof:
switch(methodName) {
default: assert(0, "unknown method");
foreach(memberName; __traits(derivedMembers, possibleClass)) {
case memberName:
return __traits(getMember, possibleClass, memberName)(params);
break;
}
}
break;
}
}
assert(0);
}
void main() {
execute("Math.withOneArg", ["cool"]);
execute("String.oneArg", ["cool"]);
}
Notice that there are no mixin
expressions used at all. Instead of getting an instance of the class from a string, I just made a TypeTuple
of all the classes I wanted to use. This is preferable to mixin
because then it is less likely to find name classes when used in different scopes; if possibleClasses
were a compile-time parameter to execute
from a different module, the list of classes would still work, whereas the list of strings would see undefined identifier errors because the library module doesn't import your user module.
Another mixin
I removed was the one to generate the cases. This looks insane, but is allowed in D: if you have a compile-time foreach
(that is, a foreach
over a built-in tuple of some sort, e.g. TypeTuple
, template argument lists, the results of __traits
...) you can actually put case
statements inside them!
So, all you have to do is write a regular switch
statement on the run time variable you want to compare against, put the foreach
inside it looping over the compile-time stuff you're searching for, case that_loop_var:
and boom, you're in business.
Similarly, I used __traits(getMember)
rather than a mixin
string to call the method. This solution will help avoid name clashes and IMO is cleaner code. It can also potentially handle overloads, if wanted (with __traits(getOverloads)
instead of __traits(getMember)
, you can loop over each one then and match the parameter types).
Finally, nesting switch
es inside other case
statements is allowed. If you need to break out of an outer loop or switch
and don't want ambiguity, you can label loops and switches and use break label_name_here;
to specify which one you want to break from. Ditto for continue
with nested loops.
BTW you could also automatically generate the wrapper functions that convert string[]
to other types of arguments if you dove into the std.traits
stuff. I wish my book was out already, I wrote about this at some length in there and don't feel like writing it all right now but if you look at std.traits.ParameterTypeTuple
and ReturnType
in the same module that will get you started if you wanna try it.