4

Ok, so it's been a while since I wrote anything big in c++ and I've grown used to some of the niceties of more modern languages. This is one that's been nagging at me and I'm sure there's an answer out there. Is there any way to call a function specified as a string by a user at run-time? Without having to resort to some sort of massive switch/if block?

The situation I'm in boils down to this: I've got a whole whack-load of math-related problems I've solved in C++ and specified as "Problem1.cpp/Problem1.h", "Problem2.cpp/Problem2.h", etc. Each problem has a function called problemX() (where X is the number of the problem) that kicks off the solution. At the start of the program I'd like to ask the user "Which problem would you like to solve?" and they'd specify a number. I'd then like to call the appropriate problemX() function without having to resort a massive hard coded switch statement (or an if statement, or an indexed array of function pointers, etc).

I'm sure this has got to be possible, but I just can't remember how to go about it. Any ideas?

Coding Mash
  • 3,338
  • 5
  • 24
  • 45
Hoyt
  • 317
  • 3
  • 7

3 Answers3

5

unordered_map of strings to function pointers.

Tweak the user input to ensure it is all lower case (or upper IF YOU LIKE SHOUTING), then just lookup the function. If it exists call it, else error.

John3136
  • 28,809
  • 4
  • 51
  • 69
  • Yeah, the hash is a nice touch, but it still requires hardcoding a huge list of function pointers. Certainly doable (I've actually written a script to write the code for me, albeit in an array), it just seems so silly to have to go through all this when I've already got a system in place with such rigid naming conventions. I never thought I'd miss php but being able to say: $problem = "problem".$prolemNum; $problem(); sure is handy! – Hoyt Nov 02 '12 at 20:20
  • FWIW, this is what I would do. – Mike G Nov 02 '12 at 20:27
1

C++ has no automatic compile or run time reflection of its code in the language. Many library frameworks do have run time reflection of the symbols in a library.

So solution 1: Stick your problems into their own dynamic libraries, and have the main program dynamically load them and look up the symbol names they export.

Solution 2: Replace your raw C-style functions with named objects. So you might have:

class Problem;
void RegisterProblem( std::string name, Problem const* problem );
std::map< std::string, Problem const* >& GetProblems();
class Problem
{
protected:
  Problem( std::string name ): RegisterProblem( std::move(name), this ) {}
  virtual void operator() const = 0;
  virtual ~Problem() {}
};
class Problem1: public Problem
{
public:
  Problem1():Problem("Problem1") {}
  virtual void operator() const { /* implementation */ }
};

// in .cpp file:
Problem1 problem1Instance();


void RegisterProblem( std::string name, Problem const* problem )
{
  GetProblems()[name] = problem;
}

std::map< std::string, Problem const* >& GetProblems()
{
  static std::map< std::string, Problem const* > problemMap;
  return problemMap;
}

int main()
{
  // parse user input to get this string:
  std::string testInput = "Problem1";

  // run the problem determined by testInput:
  Problem* prob = GetProblems()[testInput];
  Assert(prob);
  (*prob)();
}

Above we have some horribly written spew of code that has self-registering Problems (who register in a static map), and a main() that executes whatever problem the string specifies.

A way I think would be cleaner is:

// In RegisterProblem.h:
// these two have obvious implementations:
std::map< std::string, std::function<void()> >& GetProblems(); 
bool RegisterProblem( std::string s, std::function<void()> ); // always returns true

// In problem1.cpp:
void Problem1(); // implement this!
bool bProblem1Registered = RegisterProblem( "Problem1", Problem1 );
// In problem2.cpp:
void Problem2(); // implement this!
bool bProblem2Registered = RegisterProblem( "Problem2", Problem2 );

// etc

// in main.cpp:
int main(int argc, char** argv)
{
  if (argc == 0)
    return -1; // and maybe print help
  auto it = GetProblems().find( argv[1] );
  if (it == GetProblems().end())
    return -1; // and maybe print help
  it->second(); // call the problem
}

where we do away with the needless class hierarchy and just maintain a map between string and void() functions. The maintenance of this map is distributed to each place where the functions are written, so there is no central list or if statement.

I wouldn't ship anything with code as crude as the above, but I hope you get the idea.

Yakk - Adam Nevraumont
  • 262,606
  • 27
  • 330
  • 524
  • Exactly what I was looking for! I was trying to think of a way to switch to an OO framework that would work, but couldn't come up with a way for the problems to self-register. Simple and effective. Well done! – Hoyt Nov 02 '12 at 20:27
  • Note that you do have to be careful. Initialization order problems can bite your ass off. Really, the "load libraries" technique is best (this is very roughly how programs like photoshop handle plugins). For small toy problems, the `std::function` one might be acceptable, but there is still a problem with the `std::map`'s lifetime being until the end of the program's lifetime, and making sure it exists before you try to register stuff in it (the `static` local variable trick attempts to ensure that it is created early enough). The OO version is the absolute worst: there is no need for it. – Yakk - Adam Nevraumont Nov 02 '12 at 20:57
  • In C++ you have std::function, which is a better abstraction for "handle to something that does something when I ask it" than some OO hierarchy. `class` should exist to serve you, you shouldn't exist to serve `class`. – Yakk - Adam Nevraumont Nov 02 '12 at 20:58
0

You should use an std::map<std::string,_function_pointer_defined_by_you> to store the names of the functions as keys, and the function pointers as values. You could also use an std::unordered_map<std::string,_function_pointer_defined_by_you>, which is something like std::hash_map. If you can use C++11, you will find the std::unordered_map at the header file <unordered_map>, and if you can't at <tr1/unordered_map>. Documentation about both map, and unordered_map can be found at:

Rontogiannis Aristofanis
  • 8,883
  • 8
  • 41
  • 58