Class to autocreate a menu

I am have been self teaching C++ for a few years(off and on). I do this by attempting to create simple programs. It has just occured to me that every program I write has a menu. I am sooooo tired of re-coding menus for every app.

So the question starts:
I want to be able to write a menu for any app like so...
1
2
3
4
5
6
7
8
9
10
11
12
#include "menu_maker.h"

int main()
{
menu_maker MyMenu;
MyMenu.title("Main Menu");//Displays name of menu
MyMenu.addOption(1,"Create player",Player.create_character());//Add option addOption(int position, string option, class or function to call)
MyMenu.addOption(2,"Choice two",player.foo2());
MyMenu.addOption(3,"Choice three",player.foo3());
MyMenu.addOption(4,"Choice four",player.foo4);
MyMenu.Display();
}

I want it to display something like this:

****Main Menu****
1)   Create player
2)   Choice two
3)   Choice three
4)   Choice four

Choose an option (1-4)

>1
[The program calls player.createcharacter() here]

I could rough a header file out. But I need to know how to be able to call a class member function and setup the addOption() member function in my menu_maker class to pass in the required info.
I hope this question isn't too involved for one post. Thanks ahead of time.
You write this by adding a collection of some sort to your menu maker class. Since you have several data items per option, the best is to also create a MenuOption class, and then make addOption() receive instances of the MenuOption class. Then the addOption() method would take that MenuOption object and add it to the collection.

You have a choice of several collections using the STL, but in this particular case, and assuming you provide operator<() for MenuOption, I'd recommend that you use a std::vector<MenuOption> to hold all items. You can use std::sort() (along with MenuOption::operator<()) to sort the menu options by position (or whatever order you like).
> how to be able to call a class member function

Look up pointers to member functions.
http://www.parashift.com/c++-faq-lite/pointers-to-members.html

You might want to make menu_maker in such a way that you can use it with more than just one class (player); so make it generic to work with any class.

Here's a rough outline:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
#include <string>
#include <vector>
#include <iostream>

template< typename T > struct menu_maker
{
    typedef void (T::*member_function)() ;

    menu_maker( T& obj, const std::string& t ) : object(obj), title(t) {}

    void add( const std::string& description, member_function function )
    { items.emplace_back(description,function ) ; }

    void display()
    {
        std::cout << title << '\n' << std::string( title.size(), '-' ) << '\n';
        int n = 0 ;
        for( const auto& p : items )
             std::cout << ++n << ". " << p.first << '\n' ;

        std::size_t opt ;
        std::cin >> std::skipws >> opt ;
        --opt ;
        if( opt < items.size() ) (object.*items[opt].second)() ;
        else
        {
            std::cerr << "no such option\n" ;
            display() ;
        }
    }

    private:
       T& object ;
       std::string title ;
       std::vector< std::pair<std::string,member_function> > items ;
};


It can be used like this:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
struct player
{
    void do_something() { std::cout << "done something\n" ; }
    void do_something_else() { std::cout << "done something else\n" ; }
};

int main()
{
    player grand_master ;
    menu_maker<player> menu( grand_master, "choices" ) ;
    menu.add( "some_thing", &player::do_something ) ;
    menu.add( "another_thing", &player::do_something_else ) ;
    menu.display() ;
    // ...
}


Get something simple like this working to start with. Once you have done that much, you could address the problems with the inflexibility of this design - all operations are member functions which take no arguments, without cv-qualifiers, invoked on the same object.

There, we could sink out teeth into some interesting aspects of C++ - polymorphic call-wrappers with lazy evaluation (std::function<>) and generic binders (std::bind<>()).


Last edited on
I compiled the class you made to see how it works so I could adapt it. I came up with some errors:


||=== LittleRPG, Debug ===|
F:\CodeBlocks-Programs\LittleRPG\LRPG_Functions.h||In member function 'void menu_maker<T>::display()':|
F:\CodeBlocks-Programs\LittleRPG\LRPG_Functions.h|24|warning: 'auto' will change meaning in C++0x; please remove it [-Wc++0x-compat]|
F:\CodeBlocks-Programs\LittleRPG\LRPG_Functions.h|24|error: ISO C++ forbids declaration of 'p' with no type [-fpermissive]|
F:\CodeBlocks-Programs\LittleRPG\LRPG_Functions.h|24|error: range-based-for loops are not allowed in C++98 mode|
F:\CodeBlocks-Programs\LittleRPG\LRPG_Functions.h|25|error: request for member 'first' in 'p', which is of non-class type 'const int'|
||=== Build finished: 3 errors, 1 warnings (0 minutes, 1 seconds) ===|


Did I do something wrong?
> Did I do something wrong?

No, you didn't do anything wrong. It's just that the code was C++11 and your compiler expects C++98.

To make it work with C++98, make the following changes:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// ...
    void add( const std::string& description, member_function function )
    {
        //items.emplace_back(description,function ) ; **********
        items.push_back( std::make_pair(description,function ) ) ;
    }

    void display()
    {
        std::cout << title << '\n' << std::string( title.size(), '-' ) << '\n';
        int n = 0 ;
        //for( const auto& p : items ) ***************
        //     std::cout << ++n << ". " << p.first << '\n' ; **************
        for( std::size_t i = 0 ; i < items.size() ; ++i )
             std::cout << ++n << ". " << items[i].first << '\n' ;
            // ... 
Topic archived. No new replies allowed.