views:

1955

answers:

2

I am writing a silly little app in C++ to test one of my libraries. I would like the app to display a list of commands to the user, allow the user to type a command, and then execute the action associated with that command. Sounds simple enough. In C# I would end up writing a list/map of commands like so:

    class MenuItem
    {
        public MenuItem(string cmd, string desc, Action action)
        {
            Command = cmd;
            Description = desc;
            Action = action;
        }

        public string Command { get; private set; }
        public string Description { get; private set; }
        public Action Action { get; private set; }
    }

    static void Main(string[] args)
    {
        var items = new List<MenuItem>();

        items.Add(new MenuItem(
            "add",
            "Adds 1 and 2",
            ()=> Console.WriteLine(1+2)));
    }

Any suggestions on how to achieve this in C++? I don't really want to define separate classes/functions for each command. I can use Boost, but not TR1.

+5  A: 

A very common technique is to use function pointers, or boost::function, indexed by the item name, or by having a vector of them and indexing by the item index for this job. Simple example using the item name:

void exit_me(); /* exits the program */
void help(); /* displays help */

std::map< std::string, boost::function<void()> > menu;
menu["exit"] = &exit_me;
menu["help"] = &help;

std::string choice;
for(;;) {
    std::cout << "Please choose: \n";
    std::map<std::string, boost::function<void()> >::iterator it = menu.begin();
    while(it != menu.end()) {
        std::cout << (it++)->first << std::endl;
    }

    std::cin >> choice;
    if(menu.find(choice) == menu.end()) {
        /* item isn't found */
        continue; /* next round */
    }   

    menu[choice](); /* executes the function */
}

C++ doesn't have a lambda feature yet, so you really have to use functions for this task, sadly. You can use boost::lambda, but note it is just simulating lambdas, and nowhere near as powerful as a native solution:

menu["help"] = cout << constant("This is my little program, you can use it really nicely");

Note the use of constant(...), since otherwise boost::lambda wouldn't notice that this is supposed to be a lambda expression: The compiler would try to output the string using std::cout, and assign the result (an std::ostream reference) to menu["help"]. You can still use boost::function, since it will accept everything returning void and taking no arguments - including function objects, which is what boost::lambda creates.

If you really don't want separate functions or boost::lambda, you can just take print out a vector of the item names, and then switch on the item number given by the user. This is probably the easiest and most straight forward way of doing it.

Johannes Schaub - litb
Close, but I still would have to define separate functions exit_me() and help()...Is there another way?
Filip
Thanks. I will stick with using separate functions and wait for TR1's lambdas to be incorporated into the standard.
Filip
lambdas will be a new language feature of the next c++. you will be able to do menu["help"] = []() { cout << "this is my little help"; }; then.
Johannes Schaub - litb
There is small mistake, there should bestd::cout << (*it++).first << std::endl;unless you have << overloaded for this map. Anyway, neat pice of code.
Nazgob
oops right. Thanks for reporting
Johannes Schaub - litb
A: 

Why not just port the C# code to C++? There's a little work to be done, but something like this should get most of your work done:

using std::string;
class MenuItem    
{        
    public:
        MenuItem(string cmd, string desc, boost::function<bool()> action):Command(cmd),
                                                                          Description(desc),
                                                                          Action(action) 
        {}
        boost::function<bool()> GetAction() { return Action; }
        string GetDescription() { return Description; }
        string GetCommand() { return Command; }
    private:
        string Command;
        string Description;
        boost::function<bool()> Action;
}

With that defined, your main() can use a std::list, and use a simple while() loop that checks the exit value of the MenuItem's Action to determine if it should exit.

Harper Shelby
The MenuItem is not the problem here. It's the lack of lambdas or closures. In today's C++ I have to define separate functions for each of my command (boost::function would "point" to them), instead of doing it all in one place. See litb's comments.
Filip