J.R. Allen
J.R. Allen

Reputation: 71

D: call a function using a string variable with its name

D learner here... if I have a string (value only known at runtime) that is the name of a function I want to call, how can I do that? Example below...

void func001() {
//stuff
}

void func002() {
//stuff
}

// .........

void func100() {
//stuff
}

void main(char[][] args) {
  auto funcnum = to!uint(args[0]);
  auto funcname = format('func%03d', funcnum);
  ///// need to run the function named 'funcname' here
}

Upvotes: 7

Views: 750

Answers (4)

DejanLekic
DejanLekic

Reputation: 19797

The approach you are taking is most likely not what you ultimately want to do. I strongly recommend you read about the Command pattern as that is most likely what you are trying to do.

PS. The Wikipedia article gives some complicated examples. In short, your Command objects can have names and you can easily have a map of Command objects, that you can lookup by name. The real power is that you do not have an enormous switch or something similar. Instead you simply pass a Command that you want to execute, and it knows exactly what to do.

If OOP approach is not good for you, then it is an exercise for you to come up with declarative solution.

It is, I believe, possible to define an user defined attribute (UDA) named, say, command,and use it to annotate every function you want to be treated as "command", and then you can use compile-time introspection like what Adam does in his example to setup everything so you can execute these functions when needed. You can have a map CommandDelegate[string] where CommandDelegate is simply void delegate() or similar...

Upvotes: 0

A. Abramov
A. Abramov

Reputation: 1865

Instead of going all wild with converting your string (parameter, runtime) into a function call ( which are mostly compiled ) and getting into intense memory/runtime/DLL stuff, you can just make a simple if statement.

Some pseudo for you, I'll happily translate it to D if you want me to -

Given functions func001, func002, func003:
 Read and store a string input
 if the input is equal to "func001":
  Call func001
 else if input is equal to "func002":
  Call func002
 else if the input is equal to "func003":
  Call func 003
 else
  Print "Not a valid function name. Available functions are func001, func002, and func003."

Upvotes: 3

Abstract type
Abstract type

Reputation: 1921

You can also use an associative array, assuming that each function matches the same prototype:

module test;
import std.format, std.stdio, std.conv;

void func001() {
    writeln(__FUNCTION__);
}

void func002() {
    writeln(__FUNCTION__);
}

alias Proto = void function();
Proto[string] funcs;

// assign the functions to a string in the static constructor
static this() {
    funcs["func001"] = &func001;
    funcs["func002"] = &func002;
}

void main(string[] args) {
    if (args.length < 2) return;
    //!\ note that first argument is always the application exename /!\\
    auto funcnum = to!uint(args[1]);
    auto funcname = format("func%03d", funcnum);

    // try to get the matching function pointer
    Proto* f = funcname in funcs;

    // call it if the function pointer is assigned
    if (f != null) (*f)(); 
}

Note that in your initial example you've made an error with the argument. args[0] is always set to the application exename. The first custom argument is actually args[1].

The solution I propose will work if 1 or 2 is passed as argument and prints:

test.func001

test.func002

or nothing

Upvotes: 6

Adam D. Ruppe
Adam D. Ruppe

Reputation: 25595

Here's an example using compile time reflection. With __traits(allMembers), we can loop through the names of all members in an aggregate (module, struct, class, etc.) and with __traits(getMember), we can fetch a member by name and do stuff like call it.

The tricky part is getMember requires a compile time string, so we can't just pass it the command line argument directly. Instead, we build a switch to dispatch from the argument - almost just like you would by hand, but instead of writing all the names yourself, you let the loop handle it.

There's only two functions here, but it will scale to any number of them without needing to modify the main function.

See more comments inline:

import std.stdio;

// I'm grouping all the commands in a struct
// so it is easier to loop over them without
// other stuff getting in the way
struct Commands {
    // making them all static so we don't need to instantiate it
    // to call commands. This is often not the best way but it makes
    // for an easy demo and does work well a lot of the time.
    static:

    // Also assuming they all return void and have no arguments.
    // It is possible to handle other things, but it gets a lot
    // more involved. (I think my book example goes partially into
    // it, or something like my web.d goes all the way and generates
    // web/http and javascript/json apis from a full signature but that
    // code is pretty unreadable...)

    void func001() {
        writef("func001 called\n");
    }
    void func002() {
        writef("func002 called\n");
    }
}

void main(string[] args) {
    if(args.length > 1)
    // we switch on the runtime value..
    // the label will be used below
    outer: switch(args[1]) {
        // then loop through the compile time options to build
        // the cases. foreach with a compile time argument works
        // a bit differently than runtime - it is possible to build
        // switch cases with it.
        //
        // See also: http://dlang.org/traits.html#allMembers
        // and the sample chapter of my book
        foreach(memberName; __traits(allMembers, Commands)) {
            case memberName:
                // get the member by name with reflection,
                // and call it with the parenthesis at the end
                __traits(getMember, Commands, memberName)();

            // breaking from the labeled switch so we don't fallthrough
            // and also won't break the inner loop, which we don't want.
            break outer;
        }

        default: // default is required on most D switches
            writef("No such function, %s!\n", args[1]);
            break;
    }
    else { // insufficient args given
        writeln("Argument required. Options are:");
        // we can also loop to list names at runtime
        foreach(memberName; __traits(allMembers, Commands)) {
            writeln(memberName);
        }
    }
}

Upvotes: 12

Related Questions