从字符串获取类 - 按字符串名称调用函数

时间:2014-04-15 07:15:44

标签: mixins d dmd

好吧,我想做的事情很复杂,但我会试着解释一下。

假设我们希望(在编译时)类derivedMembers的所有someClass。那我们就这样做:

const string[] methods = [__traits(derivedMembers,someClass)];

现在,我们怎样才能从someClass获得"someClass"? (是的,它的字符串表示)。


让我再解释一下我正在尝试做的事情:

我想创建一个“中间”函数,它将function名称作为参数(以及一个params数组),并从特定(预定义)集合中的可用静态方法列表中调用相应的函数课程。与execute("someFunc",["one","two","three"]);一样。

以下是完整(测试)代码:

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 "";
}

上述代码的问题在于它只查找Math中的方法。我怎么能以优雅的D友好方式更改它,以便它将通过像[Math,String,SomethingElse]这样的类的数组 - 它不必是变量的(无论如何我们需要它在编译时) )?


更新

尝试了以下几点:

const string[] methods = [__traits(derivedMembers,mixin("Math")];

但它抱怨Cannot interpret Math at compile time


更新2:

此外,尝试使用Object.factory("Math"),但它仍然无效。 (也许我只是在创建一个Math类的实例?)

1 个答案:

答案 0 :(得分:5)

让我改写一下,向你展示一些很酷的技巧:

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"]);
}

请注意,根本没有使用mixin个表达式。我没有从字符串中获取类的实例,而是创建了我想要使用的所有类的TypeTuple。这比mixin更可取,因为当在不同的范围内使用时,它不太可能找到名称类;如果possibleClasses是来自不同模块的execute的编译时参数,则类列表仍然有效,而字符串列表将看到未定义的标识符错误,因为库模块不会导入用户模块。

我删除的另一个mixin是生成案例的那个。这看起来很疯狂,但是在D中是允许的:如果你有一个编译时foreach(即某个内置元组的foreach,例如TypeTuple,模板参数列表,__traits的结果......)你实际上可以将case语句放在其中!

所以,你所要做的就是在你要比较的运行时变量上写一个常规的switch语句,把foreach放在它里面循环编译时的东西你是寻找,case that_loop_var:和繁荣,你就是在做生意。

同样,我使用__traits(getMember)而不是mixin字符串来调用该方法。此解决方案将有助于避免名称冲突,IMO是更清晰的代码。如果需要,它也可以潜在地处理重载(使用__traits(getOverloads)而不是__traits(getMember),你可以遍历每个然后匹配参数类型。)

最后,允许在其他switch语句中嵌套case es。如果您需要突破外部循环或switch并且不需要歧义,可以标记循环和开关,并使用break label_name_here;指定要中断的循环。同上continue嵌套循环。

BTW你也可以自动生成包装函数,如果你潜入string[]的话,它会将std.traits转换成其他类型的参数。我希望我的书已经出版了,我在那里写了很长一段时间,并且不想现在就写出来但是如果你在同一个模块中看std.traits.ParameterTypeTupleReturnType如果您想尝试,请帮助您。