13

I am building a program in c++ where the user can set a function to be called when user defined conditions are reached. I am only a little experienced with c++.

I know how to do this in python. You would simply define functions and put the names of said functions into a structure (I always used a dictionary). When you go to use the function, you would make a call similar to:

methods = { "foo" : foo, "bar" : bar } 
choice = input("foo or bar? ")
methods[choice]()

Any ideas on how to pull this off in c++ without having to hardcode everything?

BЈовић
  • 62,405
  • 41
  • 173
  • 273
Tubbs
  • 665
  • 1
  • 7
  • 20
  • A lot of answers suggest function pointers, but you might find function objects more intuitive, like `std::function<>` or `boost::function<>` – Inverse Jun 03 '11 at 13:02
  • 2
    If you're satisfied with one of the answers, you should accept it by clicking the checkmark next to it – jalf Jun 03 '11 at 20:19

4 Answers4

22

You can use a map of function pointers:

void foo() { }
void bar() { }

typedef void (*FunctionPtr)();
typedef std::map<std::string, FunctionPtr> FunctionMap;

FunctionMap functions;
functions.insert(std::make_pair("foo", &foo));
functions.insert(std::make_pair("bar", &bar));

std::string method = get_method_however_you_want();

FunctionMap::const_iterator it(functions.find(method));
if (it != functions.end() && it->second)
    (it->second)();
James McNellis
  • 348,265
  • 75
  • 913
  • 977
  • Thanks! I don't understand any of it, but I know the direction I need to look! thanks! – Tubbs Jun 03 '11 at 03:45
  • 2
    Why `insert()` and `&foo` rather than `functions["foo"] = foo;`? They're essentially equivalent, but the latter is more readable. Also, testing `it->second` is only necessary if the map contains null entries, which seems thoroughly unlikely. And `it->second()` works just fine without the extra parentheses. – Jon Purdy Jun 03 '11 at 06:30
  • 2
    @Jon: First, `&foo` is IMHO clearer. I don't like the implicit conversion from function-names to function-pointer. For the question on `insert` vs `operator[]`, follow [this link](http://stackoverflow.com/questions/5692187/stl-map-insertion-efficiency-vs-insert/5692257#5692257). Next, the testing is perfectly ok, you never know what users of your code might do. – Xeo Jun 03 '11 at 06:52
  • 1
    @Xeo: Okay, the first is a matter of opinion, and I understand the dislike. `insert()` versus `operator[]` is more stylistic, as this is only going to be called once, and not in a performance-critical region. Last, you're right of course, but maybe then an `assert` is best, not just for `it->second`, but also possibly for `it != functions.end()`. – Jon Purdy Jun 03 '11 at 07:21
  • @Xeo: and yet, the python equivalent code behaves as `operator[]` (the user is not checking whether the identifier was present in the dictionary before inserting, so I would have just used: `functions["foo"] = &foo;`, the user is not checking whether the user input is valid in python either, but I think that is a must, so +1 for adding the check. – David Rodríguez - dribeas Jun 03 '11 at 07:25
  • Well, I couldn't care less about `foo` vs `&foo`; it really doesn't matter and I find neither of them less readable. I actually did not know that the extra parentheses are not required (or I did but I forgot); writing this answer I just figured that without them it would effectively be parsed as `second()` having to be a function call, which would be wrong, and which in hindsight doesn't make much sense anyway. As for `insert` vs `[]`, if you are _inserting_ objects you should use `insert`; if you are retrieving or replacing elements you should use `[]`; it's a matter of intent, that's all. – James McNellis Jun 03 '11 at 07:53
  • Finally, with respect to the error checking, it depends. I don't really know what the behavior of the OP's Python program is with respect to error handling because I have only brief experience with Python. My intent was to demonstrate the things you have to watch out for: when using `find()` you should be sure to check to make sure an element was found and before dereferencing a pointer you should be sure that it is valid. How these checks are done and whether explicit checks are necessary depends on the context in which the code appears; I've just tried to provide a decent example here. – James McNellis Jun 03 '11 at 07:56
  • Functions are more powerful in Python as they can convey state (closure). If you need this, either define you own base class with a virtual method (the good thing is that you may want to add some) or use std::function/boost::function instead of a plain function pointer. – ysdx Jun 03 '11 at 08:14
8

Your Python code actually translates to C++ pretty much directly:

# Python:
# Create a dictionary mapping strings to functions
methods = { "foo" : foo, "bar" : bar } 
// C++:
// create a map, mapping strings to functions (function pointers, specifically)
std::map<std::string, void(*)()> methods; 
methods["foo"] = foo;
methods["bar"] = bar;

# Python
choice = input("foo or bar? ")
// C++:
std::string choice;
std::cout << "foo or bar? ";
std::cin >> choice;

# Python:
methods[choice]()
// C++
methods[choice]();

Python's dictionary is similar to C++'s map. They're both associative containers, mapping a value from one type to a value of another (in our case, string to function). In C++, functions aren't quite first-class citizens, so you can't store a function in a map, but you can store a pointer to a function. Hence the map definition gets a bit hairy, because we have to specify that the value type is a "pointer to a function which takes no arguments and returns void".

On a side note, it is assumed that all your functions have the same signature. We can't store both functions that return void and functions that return an int in the same map without some additional trickery.

jalf
  • 243,077
  • 51
  • 345
  • 550
  • oh, nice! thats exactly the type of thing I was looking for! I am pretty sure all the functions that will be called will be void, and I'm fairly sure they will all carry no arguments. I think this will actually work perfectly! – Tubbs Jun 03 '11 at 06:40
6

You may have a look at function pointers:

http://www.newty.de/fpt/intro.html

loudandclear
  • 2,306
  • 1
  • 17
  • 21
-2

Another option is function objects + inheritance:

#include <string>
#include <iostream>
#include <conio>
#include <exception>
//---------------------------------------------------------------------------

struct Method{
    virtual ~Method(){}

    virtual
    void operator() (void)=0;
};
struct foo: public Method{
    virtual ~foo(){}

    virtual
    void operator() (void){
        std::cout << "this is foo\n";
    }
};
struct bar: public Method{
    virtual ~bar(){}

    virtual
    void operator() (void){
        std::cout << "this is bar\n";
    }
};

Method* getMethodByName(std::string methName){
    if( methName == "foo" )
        return new foo();
    else if( methName == "bar" )
        return new bar();

    throw invalid_argument("Unknown method");
}
//---------------------------------------------------------------------------

int main(int argc, char* argv[])
{
    std::string choice;

    std::cout << "foo or bar?\n";
    std::cin >> choice;

    boost::shared_ptr<Method> method = getMethodByName(choice);
    (*method)();

    getch();
    return 0;
}

Though this requires boost's smart pointer lib. With vanilla C++:

    Method* method = getMethodByName( choice );
    try{
        (*method)();
        delete method;
    }
    catch(...){
        delete method;
    }

    getch();
    return 0;
Alexander Malakhov
  • 3,383
  • 2
  • 33
  • 58
  • Virtual functions, and virtual base deleter invocation, with no virtual destruction? – Puppy Jun 03 '11 at 06:22
  • @DeadMG: Yes, I've checked and it's really leaky. I thought it would be OK, because classes have no data. It's been a while since I've did C++. Thanks! – Alexander Malakhov Jun 03 '11 at 06:48
  • @downvoter. Would be nice you tell what's wrong, so I can improve the post. (yes, today code should be different, but keep in mind I've posted this when C++11 wasn't even officially published) – Alexander Malakhov Oct 08 '19 at 11:01