How to call a function by its name (std::string) in C++?
Asked Answered
G

5

39

I wonder if there is a simple way to call a function from a string. I know a simple way, using 'if' and 'else'.

int function_1(int i, int j) {
    return i*j;
}

int function_2(int i, int j) {
    return i/j;
}

...
...
...

int function_N(int i, int j) {
    return i+j;
}

int main(int argc, char* argv[]) {
    int i = 4, j = 2;
    string function = "function_2";
    cout << callFunction(i, j, function) << endl;
    return 0;
}

This is the basic approach

int callFunction(int i, int j, string function) {
    if(function == "function_1") {
        return function_1(i, j);
    } else if(function == "function_2") {
        return function_2(i, j);
    } else if(...) {

    } ...
    ...
    ...
    ...
    return  function_1(i, j);
}

Is there something simpler?

/* New Approach */
int callFunction(int i, int j, string function) {
    /* I need something simple */
    return function(i, j);
}
Gosh answered 20/10, 2013 at 2:36 Comment(0)
C
69

What you have described is called reflection and C++ doesn't support it. However you might come with some work-around, for example in this very concrete case you might use an std::map that would map names of functions (std::string objects) to function pointers, which in case of functions with the very same prototype could be easier than it might seem:

#include <iostream>
#include <map>

int add(int i, int j) { return i+j; }
int sub(int i, int j) { return i-j; }

typedef int (*FnPtr)(int, int);

int main() {
    // initialization:
    std::map<std::string, FnPtr> myMap;
    myMap["add"] = add;
    myMap["sub"] = sub;

    // usage:
    std::string s("add");
    int res = myMap[s](2,3);
    std::cout << res;
}

Note that myMap[s](2,3) retrieves the function pointer mapped to string s and invokes this function, passing 2 and 3 to it, making the output of this example to be 5

Cobra answered 20/10, 2013 at 2:44 Comment(10)
You can improve that slightly using std::function and the initializer lists syntax.Pierrepierrepont
@LokiAstari: std::function is C++11 I guess.Cobra
@LihO: I like your approach. Thank you.Gosh
@LokiAstari: I'll check on std::functionGosh
@AlanValejo: It is possible that you don't have C++11 support available. Try #include <functional> and if it doesn't work, try #include <tr1/functional>Cobra
Thanks, I'll try that. But I've never see 'tr1', I need to check. 'tr1' works on linux?Gosh
@AlanValejo: tr1 stands for technical report, which might allow you to use some C++11 features even when C++11 is not yet supported. However it's possible that neither TR1 will be available.Cobra
If the function belongs to a class would: map["name"] = class->func?Gosh
@AlanValejo: It would be more something like obj->(*map["name"])()Cobra
See this post: #19480781Gosh
P
31

Using a map of standard string to standard functions.

#include <functional>
#include <map>
#include <string>
#include <iostream>

int add(int x, int y) {return x+y;}
int sub(int x, int y) {return x-y;}

int main()
{
    std::map<std::string, std::function<int(int,int)>>  funcMap =
         {{ "add", add},
          { "sub", sub}
         };

    std::cout << funcMap["add"](2,3) << "\n";
    std::cout << funcMap["sub"](5,2) << "\n";
}

Even better with Lambda:

#include <functional>
#include <map>
#include <string>
#include <iostream>

int main()
{
    std::map<std::string, std::function<int(int,int)>>  funcMap =
         {{ "add", [](int x, int y){return x+y;}},
          { "sub", [](int x, int y){return x-y;}}
         };

    std::cout << funcMap["add"](2,3) << "\n";
    std::cout << funcMap["sub"](5,2) << "\n";
}
Pierrepierrepont answered 20/10, 2013 at 2:57 Comment(3)
@AlanValejo: Of course it is :) Many things became far cleaner in C++11Cobra
Is it possible to use that with strings or boost::any?Pinite
@Pinite It is already using std::string not sure why you want to use boost::any in this context.Pierrepierrepont
O
7

You can also put your functions into a shared library. You will load such library dynamically with dlopen() and then just make the calls to the functions with a std::string. Here an example:

hello.cpp

#include <iostream>

extern "C" void hello() {
    std::cout << "hello" << '\n';
}

main.cpp

#include <iostream>
#include <dlfcn.h>

int main() {
    using std::cout;
    using std::cerr;

    cout << "C++ dlopen demo\n\n";

    // open the library
    cout << "Opening hello.so...\n";
    void* handle = dlopen("./hello.so", RTLD_LAZY);

    if (!handle) {
        cerr << "Cannot open library: " << dlerror() << '\n';
        return 1;
    }

    // load the symbol
    cout << "Loading symbol hello...\n";
    typedef void (*hello_t)();

    // reset errors
    dlerror();

    std::string yourfunc("hello"); // Here is your function

    hello_t hello = (hello_t) dlsym(handle, yourfunc.c_str());
    const char *dlsym_error = dlerror();
    if (dlsym_error) {
        cerr << "Cannot load symbol 'hello': " << dlsym_error <<
            '\n';
        dlclose(handle);
        return 1;
    }

    // use it to do the calculation
    cout << "Calling hello...\n";
    hello();

    // close the library
    cout << "Closing library...\n";
    dlclose(handle);
}

compilation:

g++ -fPIC -shared hello.cpp -o hello.so

and:

g++ main.cpp -o main -ldl

run:

C++ dlopen demo

Opening hello.so...
Loading symbol hello...
Calling hello...
hello
Closing library...

The example was stolen from here. There you can find more detailed explanation on dlopen() and c++

Obediah answered 13/2, 2019 at 11:5 Comment(1)
I suspect this is how Unreal Engine does; it as it creates a .dll of the game which probably contains the game-specific code that it can then reference. For example, in the input handling where you can bind a function by name as a string to an input action.Clercq
T
4

There is another possibility which hasn't been mentioned yet, which is true reflection.

An option for this is accessing functions exported from an executable or a shared library using operating system functions for resolving names to addresses. This has interesting uses like loading two 'contestant' dlls into an 'umpire' program, so that people can slug it out by having their actual codes fight each other (playing Reversi or Quake, whatever).

Another option is accessing the debug information created by the compiler. Under Windows this can be surprisingly easy for compilers that are compatible, since all the work can be off-loaded to system dlls or free dlls downloadable from Microsoft. Part of the functionality is already contained in the Windows API.

However, that falls more into the category of Systems Programming - regardless of language - and thus it pertains to C++ only insofar as it is the Systems Programming language par excellence.

Theis answered 11/11, 2014 at 13:43 Comment(2)
This seems almost like a missing part of my answer above, which is exactly what my answer does as well but in a different way: "using operating system functions for resolving names to addresses" except mine simply is direct and requires your own 'algorithm' to decode a string to an address.Useful
The point of the reflection idea is to access meta data created by the compiler instead of having to define tables with function names and pointers. Besides exported names and debug symbols there's also RTTI (with some compilers), and also hybrid approaches like processing the .map file created by the compiler for mapping between function names and addresses. The JEDI project does this for stack walks but it can also be used to find addresses for names instead. The idea is to use compiler-generated data that reflects the source code instead of having to define one's own tables.Theis
B
1

Similar to jav's answer, but loading functions from the current program:

#include <iostream>
#include <dlfcn.h>

extern "C"
void test()
{
    std::cout << __PRETTY_FUNCTION__ << "\n";
}

extern "C"
void test2()
{
    std::cout << __func__ << "\n";
}

void something_the_user_is_not_supposed_to_call()
{
    std::cout << "Oops...\n";
}

int main(int argc, char** argv)
{
    if (argc < 2)
    {
        std::cout << "Usage: " << argv[0] << " [function_name]\n";
        return 1;
    }

    void* self = dlopen(nullptr, RTLD_LAZY);

    if (!self)
    {
        std::cerr << dlerror() << "\n";
        return 1;
    }

    void* function = dlsym(self, argv[1]);
    
    if (!function)
    {
        std::cerr << dlerror() << "\n";
        return 1;
    }

    reinterpret_cast<void(*)()>(function)();

    dlclose(self);
}

Then compile with the -rdynamic option so all symbols are exported:

$ g++ test.cpp -o test -ldl -rdynamic

And it works:

$ ./test test
void test()
$ ./test test2
test2
$ ./test test3
./test: undefined symbol: test3

Of course, doing this is a terrible idea:

$ ./test _Z42something_the_user_is_not_supposed_to_callv
Oops...
$ ./test abort
Aborted (core dumped)
$ ./test sleep
^C
$ ./test main
Segmentation fault (core dumped)
Bimetallism answered 20/4, 2023 at 22:30 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.