Domanda

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?)

È stato utile?

Soluzione

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 switches 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.

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top