Dynamic menu

  • Thread starter Francesco S. Carta
  • Start date
F

Francesco S. Carta

Hi there,
in it.comp.lang.c++ somebody asked for directions (for a tutorial,
actually) about creating a menu in C++, and I've answered mentioning
pointers to functions and creating a simple example that accepts only
pointers to void functions taking no parameters.

Then I've started fiddling with the idea and I've implemented a more
complex version that accepts pointers to functions that take parameters
too, allowing the client code to pass any kind of function - with a
single parameter but without type restrictions, as the "add_item()"
method is a template).

Here below I paste the complete code with an usage example, I wonder if
there is any simpler way of achieving the same purposes, maybe without
using inheritance.

Any other suggestion or correction about my coding style will be
welcome, I completely avoided to add comments because the code should be
self-explaining even if a bit convoluted.

Thanks for your attention.

//-------
#include <iostream>
#include <typeinfo>
#include <vector>
#include <list>

class MenuItem {
public:
MenuItem() {}
virtual ~MenuItem() {}
virtual void execute() {}
virtual void display() {}
virtual std::string caption() = 0;
virtual std::string command() = 0;
virtual MenuItem* parent() = 0;
};

class MenuItemContainer : public MenuItem {
std::string _caption;
std::string _command;
MenuItem* _parent;

protected:
friend class Menu;
std::vector<MenuItem*> _items;

public:

MenuItemContainer(std::string caption,
std::string command,
MenuItem* parent) :
_caption(caption),
_command(command),
_parent(parent) {}

std::string caption() {
return _caption;
}

std::string command() {
return _command;
}

MenuItem* parent() {
return _parent;
}
};

class MenuItemWithoutParam : public MenuItem {
std::string _caption;
std::string _command;
MenuItem* _parent;
void (*_callback)();

public:
MenuItemWithoutParam(std::string caption,
std::string command,
MenuItem* parent,
void (*callback)()) :
_caption(caption),
_command(command),
_parent(parent),
_callback(callback) {}

void execute() {
if (_callback) {
_callback();
}
}

std::string caption() {
return _caption;
}

std::string command() {
return _command;
}

MenuItem* parent() {
return _parent;
}

};


template<class T> class MenuItemWithParam : public MenuItem {
std::string _caption;
std::string _command;
MenuItem* _parent;
void (*_callback)(T*);
T* _param;

public:

MenuItemWithParam(std::string caption,
std::string command,
MenuItem* parent,
void (*callback)(T*),
T* param) :
_caption(caption),
_command(command),
_parent(parent),
_callback(callback),
_param(param) {}

void execute() {
if (_callback && _param) {
_callback(_param);
}
}

std::string caption() {
return _caption;
}

std::string command() {
return _command;
}

MenuItem* parent() {
return _parent;
}
};

class Menu {
MenuItemContainer _basecontainer;
std::istream& _input;
std::eek:stream& _output;
public:
Menu(std::istream& input,
std::eek:stream& output) :
_basecontainer("","",0),
_input(input),
_output(output) {}
template<class T>
MenuItem* add_item(std::string caption,
std::string command,
MenuItem* parent,
void (*callback)(T*),
T* param) {
MenuItem* item;
if (parent && typeid(*parent) == typeid(MenuItemContainer)) {
MenuItemContainer* container
= static_cast<MenuItemContainer*>(parent);
item = new MenuItemWithParam<T>(caption,
command,
parent,
callback,
param);
container->_items.push_back(item);
} else {
item = new MenuItemWithParam<T>(caption,
command,
&_basecontainer,
callback,
param);
_basecontainer._items.push_back(item);
}
return item;

}

MenuItem* add_item(std::string caption,
std::string command,
MenuItem* parent,
void (*callback)()) {
MenuItem* item;
if (parent && typeid(*parent) == typeid(MenuItemContainer)) {
MenuItemContainer* container
= static_cast<MenuItemContainer*>(parent);
item = new MenuItemWithoutParam(caption,
command,
parent,
callback);
container->_items.push_back(item);
} else {
_basecontainer._items.push_back(item);
}
return item;
}

MenuItem* add_item(std::string caption,
std::string command,
MenuItem* parent) {
MenuItem* item;
if (parent && typeid(*parent) == typeid(MenuItemContainer)) {
MenuItemContainer* container
= static_cast<MenuItemContainer*>(parent);
item = new MenuItemContainer(caption,
command,
parent);
container->_items.push_back(item);
} else {
item = new MenuItemContainer(caption,
command,
&_basecontainer);
_basecontainer._items.push_back(item);
}
return item;
}
void interact() {
std::string line;
MenuItemContainer* current = &_basecontainer;
MenuItem* selected;
for (;;) {
selected = 0;
_output << "\n-------" << std::endl;
for (size_t i = 0; i < current->_items.size(); ++i) {
_output << "[" << current->_items->command()
<< "] " << current->_items->caption()
<< std:: endl;
}
if (current->parent()) {
_output << "[up] Upper menu" << std::endl;
}
_output << "[exit] Exit program" << std::endl;
_output << "Insert command:" << std::endl << ">";
getline(_input, line);
if (line == "up" && current->parent()) {
current = static_cast<MenuItemContainer*>
(current->parent());
} else if (line == "exit") {
return;
} else {
for (size_t i = 0; i < current->_items.size(); ++i) {
if (current->_items->command() == line) {
selected = current->_items;
break;
}
}
}
_output << std::endl;
if (selected) {
if (typeid(*selected) == typeid(MenuItemContainer)) {
current = static_cast<MenuItemContainer*>
(selected);
} else {
selected->execute();
}
} else {
_output << "Unrecognized command" << std::endl;
}
}
}
};

// client section

using namespace std;

Menu menu(cin, cout);

void increment(int* i) {
++*i;
}

void print(int* i) {
cout << *i << endl;
}

void cite(string* str) {
cout << *str << endl;
}

void addquote(MenuItem* parent) {
static list<string> quotes;
string caption;
string command;
string quote;
cout << "Insert caption: " << endl;
cout << ">";
getline(cin, caption);
cout << "Insert command: " << endl;
cout << ">";
getline(cin, command);
cout << "Insert quote: " << endl;
cout << ">";
getline(cin, quote);
if (caption != "" && command != "" && quote != "") {
quotes.push_back(quote);
menu.add_item(caption, command, parent, cite, &quotes.back());
} else {
cout << "Unable to create quote"
<< " please retry and fill in all fields" << endl;
}
}

int main() {
int i = 42;
MenuItem* program = menu.add_item("Program", "p", 0);
MenuItem* variable = menu.add_item("Variable", "v", program);
MenuItem* quotes = menu.add_item("Quotes", "q", program);
menu.add_item("Increment", "i", variable, increment, &i);
menu.add_item("Print", "p", variable, print, &i);
string hello = "Hello world!";
string panic = "Don't panic!";
menu.add_item("Add quote", "a", quotes, addquote, quotes);
menu.add_item("Hello", "h", quotes, cite, &hello);
menu.add_item("H2G2", "42", quotes, cite, &panic);
menu.interact();
return 0;
}
//-------
 
Ö

Öö Tiib

Hi there,
in it.comp.lang.c++ somebody asked for directions (for a tutorial,
actually) about creating a menu in C++, and I've answered mentioning
pointers to functions and creating a simple example that accepts only
pointers to void functions taking no parameters.

Then I've started fiddling with the idea and I've implemented a more
complex version that accepts pointers to functions that take parameters
too, allowing the client code to pass any kind of function - with a
single parameter but without type restrictions, as the "add_item()"
method is a template).

Here below I paste the complete code with an usage example, I wonder if
there is any simpler way of achieving the same purposes, maybe without
using inheritance.

Any other suggestion or correction about my coding style will be
welcome, I completely avoided to add comments because the code should be
self-explaining even if a bit convoluted.

1) Why so lot of copy-paste? For example caption(), command() and
parent() are worthlessly virtual, all derived classes implement them
same copy-paste.

2) Where is const correctness? For example the same functions should
be const. They do not change visible state of class.

Thanks for your attention.

//-------
#include <iostream>
#include <typeinfo>
#include <vector>
#include <list>

class MenuItem {
public:
     MenuItem() {}
     virtual ~MenuItem() {}
     virtual void execute() {}
     virtual void display() {}
     virtual std::string caption() = 0;
     virtual std::string command() = 0;
     virtual MenuItem* parent() = 0;

};



class MenuItemContainer : public MenuItem {
     std::string _caption;
     std::string _command;
     MenuItem* _parent;

protected:
     friend class Menu;
     std::vector<MenuItem*> _items;

public:

     MenuItemContainer(std::string caption,
                       std::string command,
                       MenuItem* parent) :
             _caption(caption),
             _command(command),
             _parent(parent) {}

     std::string caption() {
         return _caption;
     }

     std::string command() {
         return _command;
     }

     MenuItem* parent() {
         return _parent;
     }

};

class MenuItemWithoutParam : public MenuItem {
     std::string _caption;
     std::string _command;
     MenuItem* _parent;
     void (*_callback)();

public:
     MenuItemWithoutParam(std::string caption,
                          std::string command,
                          MenuItem* parent,
                          void (*callback)()) :
             _caption(caption),
             _command(command),
             _parent(parent),
             _callback(callback) {}

     void execute() {
         if (_callback) {
             _callback();
         }
     }

     std::string caption() {
         return _caption;
     }

     std::string command() {
         return _command;
     }

     MenuItem* parent() {
         return _parent;
     }

};

template<class T> class MenuItemWithParam : public MenuItem {
     std::string _caption;
     std::string _command;
     MenuItem* _parent;
     void (*_callback)(T*);
     T* _param;

public:

     MenuItemWithParam(std::string caption,
                       std::string command,
                       MenuItem* parent,
                       void (*callback)(T*),
                       T* param) :
             _caption(caption),
             _command(command),
             _parent(parent),
             _callback(callback),
             _param(param) {}

     void execute() {
         if (_callback && _param) {
             _callback(_param);
         }
     }

     std::string caption() {
         return _caption;
     }

     std::string command() {
         return _command;
     }

     MenuItem* parent() {
         return _parent;
     }

};

class Menu {
     MenuItemContainer _basecontainer;
     std::istream& _input;
     std::eek:stream& _output;
public:
     Menu(std::istream& input,
          std::eek:stream& output) :
             _basecontainer("","",0),
             _input(input),
             _output(output) {}
     template<class T>
     MenuItem* add_item(std::string caption,
                        std::string command,
                        MenuItem* parent,
                        void (*callback)(T*),
                        T* param) {
         MenuItem* item;
         if (parent && typeid(*parent) == typeid(MenuItemContainer)) {
             MenuItemContainer* container
             = static_cast<MenuItemContainer*>(parent);
             item = new MenuItemWithParam<T>(caption,
                                             command,
                                             parent,
                                             callback,
                                             param);
             container->_items.push_back(item);
         } else {
             item = new MenuItemWithParam<T>(caption,
                                             command,
                                             &_basecontainer,
                                             callback,
                                             param);
             _basecontainer._items.push_back(item);
         }
         return item;

     }

     MenuItem* add_item(std::string caption,
                        std::string command,
                        MenuItem* parent,
                        void (*callback)()) {
         MenuItem* item;
         if (parent && typeid(*parent) == typeid(MenuItemContainer)) {
             MenuItemContainer* container
             = static_cast<MenuItemContainer*>(parent);
             item = new MenuItemWithoutParam(caption,
                                             command,
                                             parent,
                                             callback);
             container->_items.push_back(item);
         } else {
             _basecontainer._items.push_back(item);
         }
         return item;
     }

     MenuItem* add_item(std::string caption,
                        std::string command,
                        MenuItem* parent) {
         MenuItem* item;
         if (parent && typeid(*parent) == typeid(MenuItemContainer)) {
             MenuItemContainer* container
             = static_cast<MenuItemContainer*>(parent);
             item = new MenuItemContainer(caption,
                                          command,
                                          parent);
             container->_items.push_back(item);
         } else {
             item = new MenuItemContainer(caption,
                                          command,
                                          &_basecontainer);
             _basecontainer._items.push_back(item);
         }
         return item;
     }
     void interact() {
         std::string line;
         MenuItemContainer* current = &_basecontainer;
         MenuItem* selected;
         for (;;) {
             selected = 0;
             _output << "\n-------" << std::endl;
             for (size_t i = 0; i < current->_items.size(); ++i) {
                 _output << "[" << current->_items->command()
                 << "] " << current->_items->caption()
                 << std:: endl;
             }
             if (current->parent()) {
                 _output << "[up] Upper menu" << std::endl;
             }
             _output << "[exit] Exit program" << std::endl;
             _output << "Insert command:" << std::endl << ">";
             getline(_input, line);
             if (line == "up" && current->parent()) {
                 current = static_cast<MenuItemContainer*>
                           (current->parent());
             } else if (line == "exit") {
                 return;
             } else {
                 for (size_t i = 0; i < current->_items.size(); ++i) {
                     if (current->_items->command() == line) {
                         selected = current->_items;
                         break;
                     }
                 }
             }
             _output << std::endl;
             if (selected) {
                 if (typeid(*selected) == typeid(MenuItemContainer)) {
                     current = static_cast<MenuItemContainer*>
                               (selected);
                 } else {
                     selected->execute();
                 }
             } else {
                 _output << "Unrecognized command" << std::endl;
             }
         }
     }

};

// client section

using namespace std;

Menu menu(cin, cout);

void increment(int* i) {
     ++*i;

}

void print(int* i) {
     cout << *i << endl;

}

void cite(string* str) {
     cout << *str << endl;

}

void addquote(MenuItem* parent) {
     static list<string> quotes;
     string caption;
     string command;
     string quote;
     cout << "Insert caption: " << endl;
     cout << ">";
     getline(cin, caption);
     cout << "Insert command: " << endl;
     cout << ">";
     getline(cin, command);
     cout << "Insert quote: " << endl;
     cout << ">";
     getline(cin, quote);
     if (caption != "" && command != "" && quote != "") {
         quotes.push_back(quote);
         menu.add_item(caption, command, parent, cite, &quotes.back());
     } else {
         cout << "Unable to create quote"
         << " please retry and fill in all fields" << endl;
     }

}

int main() {
     int i = 42;
     MenuItem* program = menu.add_item("Program", "p", 0);
     MenuItem* variable = menu.add_item("Variable", "v", program);
     MenuItem* quotes = menu.add_item("Quotes", "q", program);
     menu.add_item("Increment", "i", variable, increment, &i);
     menu.add_item("Print", "p", variable, print, &i);
     string hello = "Hello world!";
     string panic = "Don't panic!";
     menu.add_item("Add quote", "a", quotes, addquote, quotes);
     menu.add_item("Hello", "h", quotes, cite, &hello);
     menu.add_item("H2G2", "42", quotes, cite, &panic);
     menu.interact();
     return 0;}

//-------
 
F

Francesco S. Carta

1) Why so lot of copy-paste? For example caption(), command() and
parent() are worthlessly virtual, all derived classes implement them
same copy-paste.

Well, yes, but I didn't want to put data in the base class, I think I
could create another base class for the common data and functions.
2) Where is const correctness? For example the same functions should
be const. They do not change visible state of class.

Uhm yes, that's not among my habits to give too much attention to const
correctness in such cases: that's a toy program thrown together to test
the idea. Of course I should declare such functions as const, if it were
real code :)
 
V

Victor Bazarov

[..]
2) Where is const correctness? For example the same functions should
be const. They do not change visible state of class.

Uhm yes, that's not among my habits to give too much attention to const
correctness in such cases: that's a toy program thrown together to test
the idea. Of course I should declare such functions as const, if it were
real code :)

I see the smiley, it probably indicates that you don't really think that
the code's being not real has any bearing on how you should treat it WRT
const-correctness. For the beginners who are going to read this
discussion: it is easier to develop (and maintain) a *single* habit of
doing everything *right* instead of keeping *two habits* to do the "real
code" right and to recognize some code as "not real" where you are
allowed to be sloppy.

A long time ago I was given this advice: "Делай вÑÑ‘ хорошо, плохо Ñамо
получитÑÑ" (loosely translated, "do everything well, badly it will turn
out by itself"), which to me means that one should treat every target as
"real", so to speak. We don't have the luxury of living a draft life,
and then, when we like the result, live the final version. Do
everything well the first time.

Just my twopence worth.

V
 
Ö

Öö Tiib

Well, yes, but I didn't want to put data in the base class, I think I
could create another base class for the common data and functions.

Where it is written that base class should be pure abstract and not
implement or contain anything? OK. Pure interfaces should be pure
abstract. I usually when something is meant as interface then i also
make it look like interface. For example have protected destructors,
then someone who got that interface somewhere can not delete it. ;-)
Uhm yes, that's not among my habits to give too much attention to const
correctness in such cases: that's a toy program thrown together to test
the idea. Of course I should declare such functions as const, if it were
real code :)

Ok, with that you can excuse anything, but it is difficult to read.
private, protected, public order makes it also harder to read. Public
declarations that are most important are somewhere at bottom. If it is
toy test program then pure abstract interface (like you now say it)
feels also like overkill. Copy-paste makes code longer and so to scan
all classes to see what is the difference of these virtuals (just to
find that nothing) feels like waste of readers time. ;-)
 
F

Francesco S. Carta

[..]
2) Where is const correctness? For example the same functions should
be const. They do not change visible state of class.

Uhm yes, that's not among my habits to give too much attention to const
correctness in such cases: that's a toy program thrown together to test
the idea. Of course I should declare such functions as const, if it were
real code :)

I see the smiley, it probably indicates that you don't really think that
the code's being not real has any bearing on how you should treat it WRT
const-correctness. For the beginners who are going to read this
discussion: it is easier to develop (and maintain) a *single* habit of
doing everything *right* instead of keeping *two habits* to do the "real
code" right and to recognize some code as "not real" where you are
allowed to be sloppy.

A long time ago I was given this advice: "Делай вÑÑ‘ хорошо, плохо Ñамо
получитÑÑ" (loosely translated, "do everything well, badly it will turn
out by itself"), which to me means that one should treat every target as
"real", so to speak. We don't have the luxury of living a draft life,
and then, when we like the result, live the final version. Do everything
well the first time.

Just my twopence worth.

And an important twopence, I must say. You're both right, that's not a
good habit to be sloppy, thanks for putting me back on track.
 
F

Francesco S. Carta

Where it is written that base class should be pure abstract and not
implement or contain anything? OK. Pure interfaces should be pure
abstract. I usually when something is meant as interface then i also
make it look like interface. For example have protected destructors,
then someone who got that interface somewhere can not delete it. ;-)

Very good suggestions, the protected destructor in particular, thanks
for pointing them out.
Ok, with that you can excuse anything, but it is difficult to read.
private, protected, public order makes it also harder to read. Public
declarations that are most important are somewhere at bottom. If it is
toy test program then pure abstract interface (like you now say it)
feels also like overkill. Copy-paste makes code longer and so to scan
all classes to see what is the difference of these virtuals (just to
find that nothing) feels like waste of readers time. ;-)

Yes, that's the problem of being a hobbyist, nobody habitually reads my
code and puts me back on the right track, thanks for the further
suggestions, I'm refining my code.
 
F

Francesco S. Carta

Victor Bazarov wrote:



Put another way: "If you don't have time to do it right, when will you have
time to fix it?"

Of course ;-)
"There are two ways of constructing a software design. One way is to make
it so simple that there are obviously no deficiencies. And the other way
is to make it so complicated that there are no obvious deficiencies."
- C. A. R. Hoare

Well, the next time I'll make it more convoluted ;-)

Yes, I know, that quote was part of your signature and as such wasn't
specifically referred to this issue, but it fits quite well :)
 
F

Francesco S. Carta

Here is the new version of my code, after having refined it following
the suggestions of Öö and Victor, and now I have an question about the
protected dtor.

Before of that question, here are the changes I made to the code:

- added the common data and functions to the MenuItem base class, which
now can't be created nor destroyed by the client code.
- protected all the classes derived from MenuItem - neither those
classes can be created nor destroyed by the client.
- changed the order of public / protected / private sections.
- moved out from the Menu class the implementation of the add_item() and
interact() functions.
- added some checks to avoid duplicate commands when adding new items.
- added the const specifier wherever I thought I could.

Now the code is more readable even for myself :)

Since nobody questioned the overall structure, I suppose there is no way
to create something like this avoiding inheritance - and with "something
like this" I mean "a menu that accepts a pointer to function regardless
of the type of that function argument".

There is one thing that stopped me for a moment, and still stops me
about its understanding: if I omit to declare MenuItemContainer as a
friend of MenuItem, the compiler tells me I cannot delete a MenuItem*
from the MenuItemContainer dtor, because the dtor is protected.

Now, shouldn't a derived class be able to access the protected members
of its base class? What I'm missing?

Thanks again for your attention.

//-------
#include <iostream>
#include <typeinfo>
#include <vector>
#include <list>

/* ==========================================================
MenuItem base class - public interface
*/

class MenuItem {
public:
virtual void execute() const {}
std::string caption() const {
return _caption;
}
std::string command() const {
return _command;
}
MenuItem* parent() const {
return _parent;
}

protected:
friend class MenuItemContainer;
MenuItem(std::string caption,
std::string command,
MenuItem* parent) :
_caption(caption),
_command(command),
_parent(parent) {}

virtual ~MenuItem() {}

private:
std::string _caption;
std::string _command;
MenuItem* _parent;
};

/* ==========================================================
Protected classes to be used by Menu class
*/

class MenuItemContainer : protected MenuItem {
protected:
friend class Menu;
MenuItemContainer(std::string caption,
std::string command,
MenuItem* parent) :
MenuItem(caption, command, parent) {}
~MenuItemContainer() {
for (size_t i = 0; i < _items.size(); ++i) {
delete _items;
}
}
std::vector<MenuItem*> _items;
};

class MenuItemWithoutParam : protected MenuItem {
protected:
friend class Menu;
MenuItemWithoutParam(std::string caption,
std::string command,
MenuItem* parent,
void (*callback)()) :
MenuItem(caption, command, parent),
_callback(callback) {}
void execute() const {
if (_callback) {
_callback();
}
}

private:
void (*_callback)();
};

template<class T> class MenuItemWithParam : protected MenuItem {
protected:
friend class Menu;
MenuItemWithParam(std::string caption,
std::string command,
MenuItem* parent,
void (*callback)(T*),
T* param) :
MenuItem(caption, command, parent),
_callback(callback),
_param(param) {}

void execute() const {
if (_callback && _param) {
_callback(_param);
}
}

private:
void (*_callback)(T*);
T* _param;
};

/* ==========================================================
Menu class definition
*/

class Menu {
public:
struct DuplicateCommand {};
Menu(std::istream& input,
std::eek:stream& output) :
_basecontainer("","",0),
_input(input),
_output(output) {}

template<class T>
MenuItem* add_item(std::string caption,
std::string command,
MenuItem* parent,
void (*callback)(T*),
T* param);

MenuItem* add_item(std::string caption,
std::string command,
MenuItem* parent,
void (*callback)());

MenuItem* add_item(std::string caption,
std::string command,
MenuItem* parent);

void interact() const;

private:
MenuItemContainer _basecontainer;
std::istream& _input;
std::eek:stream& _output;
};

/* ==========================================================
Menu class members implementation
*/

template<class T>
MenuItem* Menu::add_item(std::string caption,
std::string command,
MenuItem* parent,
void (*callback)(T*),
T* param) {
if (!parent || typeid(*parent) != typeid(MenuItemContainer)) {
parent = &_basecontainer;
}
MenuItemContainer* cont = static_cast<MenuItemContainer*>(parent);
if (command == "up" || command == "exit") throw DuplicateCommand();
for (size_t i = 0; i < cont->_items.size(); ++i) {
if (command == cont->_items->command()) {
throw DuplicateCommand();
}
}
MenuItem* item = new MenuItemWithParam<T>(
caption,
command,
parent,
callback,
param);
cont->_items.push_back(item);
return item;
}

MenuItem* Menu::add_item(std::string caption,
std::string command,
MenuItem* parent,
void (*callback)()) {
if (!parent || typeid(*parent) != typeid(MenuItemContainer)) {
parent = &_basecontainer;
}
MenuItemContainer* cont = static_cast<MenuItemContainer*>(parent);
if (command == "up" || command == "exit") throw DuplicateCommand();
for (size_t i = 0; i < cont->_items.size(); ++i) {
if (command == cont->_items->command()) {
throw DuplicateCommand();
}
}
MenuItem* item = new MenuItemWithoutParam(
caption,
command,
parent,
callback);
cont->_items.push_back(item);
return item;
}

MenuItem* Menu::add_item(std::string caption,
std::string command,
MenuItem* parent) {
if (!parent || typeid(*parent) != typeid(MenuItemContainer)) {
parent = &_basecontainer;
}
MenuItemContainer* cont = static_cast<MenuItemContainer*>(parent);
if (command == "up" || command == "exit") throw DuplicateCommand();
for (size_t i = 0; i < cont->_items.size(); ++i) {
if (command == cont->_items->command()) {
throw DuplicateCommand();
}
}
MenuItem* item = new MenuItemContainer(
caption,
command,
parent);
cont->_items.push_back(item);
return item;
}

void Menu::interact() const {
std::string line;
const MenuItemContainer* current = &_basecontainer;
const MenuItem* selected;
for (;;) {
selected = 0;
_output << "\n-------" << std::endl;
for (size_t i = 0; i < current->_items.size(); ++i) {
_output << "[" << current->_items->command();
_output << "] " << current->_items->caption();
_output << std::endl;
}
_output << "-" << std::endl;
if (current->parent()) {
_output << "[up] Upper menu" << std::endl;
}
_output << "[exit] Exit program" << std::endl;
_output << "-" << std::endl;
_output << "Insert command:" << std::endl << ">";
getline(_input, line);
if (line == "up" && current->parent()) {
selected = current->parent();
} else if (line == "exit") {
return;
} else {
for (size_t i = 0; i < current->_items.size(); ++i) {
if (current->_items->command() == line) {
selected = current->_items;
break;
}
}
}
_output << std::endl;
if (selected) {
if (typeid(*selected) == typeid(MenuItemContainer)) {
current = static_cast<const MenuItemContainer*>
(selected);
} else {
selected->execute();
}
} else {
_output << "Unrecognized command" << std::endl;
}
}
}

/* ==========================================================
Client section
*/

using namespace std;

Menu menu(cin, cout);

void increment(int* i) {
if (i)
++*i;
}

void print(int* i) {
if (i)
cout << *i << endl;
}

void cite(string* str) {
if (str)
cout << *str << endl;
}

void addquote(MenuItem* parent) {
static list<string> quotes;
string caption;
string command;
string quote;
cout << "Insert caption: " << endl;
cout << ">";
getline(cin, caption);
cout << "Insert command: " << endl;
cout << ">";
getline(cin, command);
cout << "Insert quote: " << endl;
cout << ">";
getline(cin, quote);
if (caption != "" && command != "" && quote != "") {
quotes.push_back(quote);
try {
menu.add_item(caption,
command,
parent,
cite,
&quotes.back());
} catch (Menu::DuplicateCommand) {
cout << "!!! Unable to create quote: ";
cout << "duplicated command" << endl;
}
} else {
cout << "!!! Unable to create quote, ";
cout << "please retry filling in all fields" << endl;
}
}

int main() {
int i = 42;
MenuItem* program = menu.add_item("Program", "p", 0);
MenuItem* variable = menu.add_item("Variable", "v", program);
MenuItem* quotes = menu.add_item("Quotes", "q", program);
menu.add_item("Increment", "i", variable, increment, &i);
menu.add_item("Print", "p", variable, print, &i);
string hello = "Hello world!";
string panic = "Don't panic!";
menu.add_item("Add quote", "a", quotes, addquote, quotes);
menu.add_item("Hello", "h", quotes, cite, &hello);
menu.add_item("H2G2", "42", quotes, cite, &panic);
menu.interact();
return 0;
}
//-------
 
F

Francesco S. Carta

To avoid inheritance consider using TR1 (or boost) bind/function.

Didn't think that could help me in this case... I'll give it a shot,
thanks for pointing it out.
 
F

Francesco S. Carta

Didn't think that could help me in this case... I'll give it a shot,
thanks for pointing it out.

Uhm... I'm not really sure I'll be able to work this out all by myself...

This is the core of my current solution (i.e. the code I posted so far):

- I keep a vector of pointers to a common base class which point to
different derived classes, which in turn store either:

-- no function pointer (MenuItemContainer)

-- a function pointer (MenuItemWithoutParam)

-- a function pointer and a pointer to its parameter (MenuItemWithParam)

Then I call "execute()" on those "base pointers", and the runtime
polymorphic system resolves things for me, calling the appropriate
function of the appropriate derived class.

Now, if I want to avoid the inheritance tree I mentioned above, I should
create a vector holding what, related to tr1::bind or boost::bind?

The implementation of those templates is too esoteric for me, I'm not
able to work out which object or pointer I could store to be able to
call the function along with its parameter afterwards.

Any quick and dirty example will be really welcome, even just a pointer
would suffice if sharp enough.

Thanksalot
 
C

cpp4ever

[..]
2) Where is const correctness? For example the same functions should
be const. They do not change visible state of class.

Uhm yes, that's not among my habits to give too much attention to const
correctness in such cases: that's a toy program thrown together to test
the idea. Of course I should declare such functions as const, if it were
real code :)

I see the smiley, it probably indicates that you don't really think that
the code's being not real has any bearing on how you should treat it WRT
const-correctness. For the beginners who are going to read this
discussion: it is easier to develop (and maintain) a *single* habit of
doing everything *right* instead of keeping *two habits* to do the "real
code" right and to recognize some code as "not real" where you are
allowed to be sloppy.

A long time ago I was given this advice: "Делай вÑÑ‘ хорошо, плохо Ñамо
получитÑÑ" (loosely translated, "do everything well, badly it will turn
out by itself"), which to me means that one should treat every target as
"real", so to speak. We don't have the luxury of living a draft life,
and then, when we like the result, live the final version. Do
everything well the first time.

Just my twopence worth.

V

If you get it right first time, every time, you must be perfect, which I
doubt. So I'm guessing most folks have learnt from mistakes they've made
in the past. More to the point, as an engineer I'm well aware that there
maybe more than one right method/answer to a problem, and I would
recommend that being right is not always as straight forward as you may
think, based on the bigger picture and planned developments.

Penny change from your twopence

cpp4ever
 
V

Victor Bazarov

[..]
2) Where is const correctness? For example the same functions should
be const. They do not change visible state of class.

Uhm yes, that's not among my habits to give too much attention to const
correctness in such cases: that's a toy program thrown together to test
the idea. Of course I should declare such functions as const, if it were
real code :)

I see the smiley, it probably indicates that you don't really think that
the code's being not real has any bearing on how you should treat it WRT
const-correctness. For the beginners who are going to read this
discussion: it is easier to develop (and maintain) a *single* habit of
doing everything *right* instead of keeping *two habits* to do the "real
code" right and to recognize some code as "not real" where you are
allowed to be sloppy.

A long time ago I was given this advice: "Делай вÑÑ‘ хорошо, плохо Ñамо
получитÑÑ" (loosely translated, "do everything well, badly it will turn
out by itself"), which to me means that one should treat every target as
"real", so to speak. We don't have the luxury of living a draft life,
and then, when we like the result, live the final version. Do
everything well the first time.

Just my twopence worth.

V

If you get it right first time, every time, you must be perfect, which I
doubt.

Of course. I don't *get* it right the first time, every time. But I
surely won't get it if I don't even try.
> So I'm guessing most folks have learnt from mistakes they've made
in the past. More to the point, as an engineer I'm well aware that there
maybe more than one right method/answer to a problem, and I would
recommend that being right is not always as straight forward as you may
think, based on the bigger picture and planned developments.

Penny change from your twopence

The advice is not absolute. You cannot "do it right or your job is
worthless", but if you don't even *move your finger* to do it _right_ in
the first place, they why do it at all? I think that's the point.

Learning from mistakes is OK, but the Monte Carlo method does not work
very well when trying to achieve the vast majority of goals, AFA any
production is concerned. You don't type random characters to learn C++
syntax from the compiler's report on syntax errors, do you? You don't
throw random statements together into a program in an attempt to
implement your logic, and redo it when it doesn't work, do you? So,
don't work intentionally sloppily just to "learn from mistakes" that
others point out.

Being right and aspiring to be right are two different things. My
advice involves the latter, don't confuse them.

That'll be a nickel.

V
 
C

cpp4ever

On 7/9/2010 8:31 AM, Francesco S. Carta wrote:
[..]
2) Where is const correctness? For example the same functions should
be const. They do not change visible state of class.

Uhm yes, that's not among my habits to give too much attention to const
correctness in such cases: that's a toy program thrown together to test
the idea. Of course I should declare such functions as const, if it
were
real code :)

I see the smiley, it probably indicates that you don't really think that
the code's being not real has any bearing on how you should treat it WRT
const-correctness. For the beginners who are going to read this
discussion: it is easier to develop (and maintain) a *single* habit of
doing everything *right* instead of keeping *two habits* to do the "real
code" right and to recognize some code as "not real" where you are
allowed to be sloppy.

A long time ago I was given this advice: "Делай вÑÑ‘ хорошо, плохо Ñамо
получитÑÑ" (loosely translated, "do everything well, badly it will turn
out by itself"), which to me means that one should treat every target as
"real", so to speak. We don't have the luxury of living a draft life,
and then, when we like the result, live the final version. Do
everything well the first time.

Just my twopence worth.

V

If you get it right first time, every time, you must be perfect, which I
doubt.

Of course. I don't *get* it right the first time, every time. But I
surely won't get it if I don't even try.
So I'm guessing most folks have learnt from mistakes they've made
in the past. More to the point, as an engineer I'm well aware that there
maybe more than one right method/answer to a problem, and I would
recommend that being right is not always as straight forward as you may
think, based on the bigger picture and planned developments.

Penny change from your twopence

The advice is not absolute. You cannot "do it right or your job is
worthless", but if you don't even *move your finger* to do it _right_ in
the first place, they why do it at all? I think that's the point.

Learning from mistakes is OK, but the Monte Carlo method does not work
very well when trying to achieve the vast majority of goals, AFA any
production is concerned. You don't type random characters to learn C++
syntax from the compiler's report on syntax errors, do you? You don't
throw random statements together into a program in an attempt to
implement your logic, and redo it when it doesn't work, do you? So,
don't work intentionally sloppily just to "learn from mistakes" that
others point out.

Being right and aspiring to be right are two different things. My
advice involves the latter, don't confuse them.

That'll be a nickel.

V

I've heard of Monte Carlo analysis applied to electronic circuit
component selection, but as a methodology for software development?
That is plainly impractical, as is absolute perfection, and as an
engineer most systems are not designed as one big machine, but as a set
of interconnected components. There is a skill in ensuring all those
components fit, and work together correctly, otherwise the perfect logic
of the components is wasted. Naturally I agree that trying to ensure you
write logically correct code is important, and this requires either a
precise definition of the logic, or good understanding of the required
logic before any coding is even started. I'm sure you must have heard of
the aphorism, if you don't learn from your mistakes, you are doomed to
repeat them. But perhaps repeating your mistakes is also OK? I don't
think so.

Throws over a Dime, keep the change

Doesn't give Nickel when a Dime will do.

cpp4ever
 
F

Francesco S. Carta

Francesco S. Carta said:
Uhm... I'm not really sure I'll be able to work this out all by myself...

This is the core of my current solution (i.e. the code I posted so far):

- I keep a vector of pointers to a common base class which point to
different derived classes, which in turn store either:

-- no function pointer (MenuItemContainer)

-- a function pointer (MenuItemWithoutParam)

-- a function pointer and a pointer to its parameter (MenuItemWithParam)

Then I call "execute()" on those "base pointers", and the runtime
polymorphic system resolves things for me, calling the appropriate
function of the appropriate derived class.

Now, if I want to avoid the inheritance tree I mentioned above, I
should create a vector holding what, related to tr1::bind or boost::bind?

The implementation of those templates is too esoteric for me, I'm not
able to work out which object or pointer I could store to be able to
call the function along with its parameter afterwards.

Any quick and dirty example will be really welcome, even just a
pointer would suffice if sharp enough.

Thanksalot

Example:

#include <vector>
#include <functional>

void func0() {}
void func1(int) {}

int main()
{
std::vector<std::tr1::function<void()> > v;
v.push_back(func0);
v.push_back(std::tr1::bind(func1, 42));
v[0]();
v[1]();
}

Ah, perfect, thanks a lot, now I understand it.
 
C

cpp4ever

Hi there,
in it.comp.lang.c++ somebody asked for directions (for a tutorial,
actually) about creating a menu in C++, and I've answered mentioning
pointers to functions and creating a simple example that accepts only
pointers to void functions taking no parameters.

Then I've started fiddling with the idea and I've implemented a more
complex version that accepts pointers to functions that take parameters
too, allowing the client code to pass any kind of function - with a
single parameter but without type restrictions, as the "add_item()"
method is a template).

Here below I paste the complete code with an usage example, I wonder if
there is any simpler way of achieving the same purposes, maybe without
using inheritance.

Any other suggestion or correction about my coding style will be
welcome, I completely avoided to add comments because the code should be
self-explaining even if a bit convoluted.

Thanks for your attention.

//-------
#include <iostream>
#include <typeinfo>
#include <vector>
#include <list>

class MenuItem {
public:
MenuItem() {}
virtual ~MenuItem() {}
virtual void execute() {}
virtual void display() {}
virtual std::string caption() = 0;
virtual std::string command() = 0;
virtual MenuItem* parent() = 0;
};

class MenuItemContainer : public MenuItem {
std::string _caption;
std::string _command;
MenuItem* _parent;

protected:
friend class Menu;
std::vector<MenuItem*> _items;

public:

MenuItemContainer(std::string caption,
std::string command,
MenuItem* parent) :
_caption(caption),
_command(command),
_parent(parent) {}

std::string caption() {
return _caption;
}

std::string command() {
return _command;
}

MenuItem* parent() {
return _parent;
}
};

class MenuItemWithoutParam : public MenuItem {
std::string _caption;
std::string _command;
MenuItem* _parent;
void (*_callback)();

public:
MenuItemWithoutParam(std::string caption,
std::string command,
MenuItem* parent,
void (*callback)()) :
_caption(caption),
_command(command),
_parent(parent),
_callback(callback) {}

void execute() {
if (_callback) {
_callback();
}
}

std::string caption() {
return _caption;
}

std::string command() {
return _command;
}

MenuItem* parent() {
return _parent;
}

};


template<class T> class MenuItemWithParam : public MenuItem {
std::string _caption;
std::string _command;
MenuItem* _parent;
void (*_callback)(T*);
T* _param;

public:

MenuItemWithParam(std::string caption,
std::string command,
MenuItem* parent,
void (*callback)(T*),
T* param) :
_caption(caption),
_command(command),
_parent(parent),
_callback(callback),
_param(param) {}

void execute() {
if (_callback && _param) {
_callback(_param);
}
}

std::string caption() {
return _caption;
}

std::string command() {
return _command;
}

MenuItem* parent() {
return _parent;
}
};

class Menu {
MenuItemContainer _basecontainer;
std::istream& _input;
std::eek:stream& _output;
public:
Menu(std::istream& input,
std::eek:stream& output) :
_basecontainer("","",0),
_input(input),
_output(output) {}
template<class T>
MenuItem* add_item(std::string caption,
std::string command,
MenuItem* parent,
void (*callback)(T*),
T* param) {
MenuItem* item;
if (parent && typeid(*parent) == typeid(MenuItemContainer)) {
MenuItemContainer* container
= static_cast<MenuItemContainer*>(parent);
item = new MenuItemWithParam<T>(caption,
command,
parent,
callback,
param);
container->_items.push_back(item);
} else {
item = new MenuItemWithParam<T>(caption,
command,
&_basecontainer,
callback,
param);
_basecontainer._items.push_back(item);
}
return item;

}

MenuItem* add_item(std::string caption,
std::string command,
MenuItem* parent,
void (*callback)()) {
MenuItem* item;
if (parent && typeid(*parent) == typeid(MenuItemContainer)) {
MenuItemContainer* container
= static_cast<MenuItemContainer*>(parent);
item = new MenuItemWithoutParam(caption,
command,
parent,
callback);
container->_items.push_back(item);
} else {
_basecontainer._items.push_back(item);
}
return item;
}

MenuItem* add_item(std::string caption,
std::string command,
MenuItem* parent) {
MenuItem* item;
if (parent && typeid(*parent) == typeid(MenuItemContainer)) {
MenuItemContainer* container
= static_cast<MenuItemContainer*>(parent);
item = new MenuItemContainer(caption,
command,
parent);
container->_items.push_back(item);
} else {
item = new MenuItemContainer(caption,
command,
&_basecontainer);
_basecontainer._items.push_back(item);
}
return item;
}
void interact() {
std::string line;
MenuItemContainer* current = &_basecontainer;
MenuItem* selected;
for (;;) {
selected = 0;
_output << "\n-------" << std::endl;
for (size_t i = 0; i < current->_items.size(); ++i) {
_output << "[" << current->_items->command()
<< "] " << current->_items->caption()
<< std:: endl;
}
if (current->parent()) {
_output << "[up] Upper menu" << std::endl;
}
_output << "[exit] Exit program" << std::endl;
_output << "Insert command:" << std::endl << ">";
getline(_input, line);
if (line == "up" && current->parent()) {
current = static_cast<MenuItemContainer*>
(current->parent());
} else if (line == "exit") {
return;
} else {
for (size_t i = 0; i < current->_items.size(); ++i) {
if (current->_items->command() == line) {
selected = current->_items;
break;
}
}
}
_output << std::endl;
if (selected) {
if (typeid(*selected) == typeid(MenuItemContainer)) {
current = static_cast<MenuItemContainer*>
(selected);
} else {
selected->execute();
}
} else {
_output << "Unrecognized command" << std::endl;
}
}
}
};

// client section

using namespace std;

Menu menu(cin, cout);

void increment(int* i) {
++*i;
}

void print(int* i) {
cout << *i << endl;
}

void cite(string* str) {
cout << *str << endl;
}

void addquote(MenuItem* parent) {
static list<string> quotes;
string caption;
string command;
string quote;
cout << "Insert caption: " << endl;
cout << ">";
getline(cin, caption);
cout << "Insert command: " << endl;
cout << ">";
getline(cin, command);
cout << "Insert quote: " << endl;
cout << ">";
getline(cin, quote);
if (caption != "" && command != "" && quote != "") {
quotes.push_back(quote);
menu.add_item(caption, command, parent, cite, &quotes.back());
} else {
cout << "Unable to create quote"
<< " please retry and fill in all fields" << endl;
}
}

int main() {
int i = 42;
MenuItem* program = menu.add_item("Program", "p", 0);
MenuItem* variable = menu.add_item("Variable", "v", program);
MenuItem* quotes = menu.add_item("Quotes", "q", program);
menu.add_item("Increment", "i", variable, increment, &i);
menu.add_item("Print", "p", variable, print, &i);
string hello = "Hello world!";
string panic = "Don't panic!";
menu.add_item("Add quote", "a", quotes, addquote, quotes);
menu.add_item("Hello", "h", quotes, cite, &hello);
menu.add_item("H2G2", "42", quotes, cite, &panic);
menu.interact();
return 0;
}
//-------


IMHO I think use of inheritance is better than using callback functions.
Each derived menuitem type then has it's own private properties that in
turn can be accessed by descendant classes via protected functions. This
provides far greater flexibility IMHO, and
is easier to implement and understand.

HTH

cpp4ever
 
F

Francesco S. Carta

Leigh Johnston said:
Francesco S. Carta said:
Since nobody questioned the overall structure, I suppose there is no
way to create something like this avoiding inheritance - and with
"something like this" I mean "a menu that accepts a pointer to
function regardless of the type of that function argument".

To avoid inheritance consider using TR1 (or boost) bind/function.

Didn't think that could help me in this case... I'll give it a shot,
thanks for pointing it out.


Uhm... I'm not really sure I'll be able to work this out all by
myself...

This is the core of my current solution (i.e. the code I posted so far):

- I keep a vector of pointers to a common base class which point to
different derived classes, which in turn store either:

-- no function pointer (MenuItemContainer)

-- a function pointer (MenuItemWithoutParam)

-- a function pointer and a pointer to its parameter (MenuItemWithParam)

Then I call "execute()" on those "base pointers", and the runtime
polymorphic system resolves things for me, calling the appropriate
function of the appropriate derived class.

Now, if I want to avoid the inheritance tree I mentioned above, I
should create a vector holding what, related to tr1::bind or
boost::bind?

The implementation of those templates is too esoteric for me, I'm not
able to work out which object or pointer I could store to be able to
call the function along with its parameter afterwards.

Any quick and dirty example will be really welcome, even just a
pointer would suffice if sharp enough.

Thanksalot

Example:

#include <vector>
#include <functional>

void func0() {}
void func1(int) {}

int main()
{
std::vector<std::tr1::function<void()> > v;
v.push_back(func0);
v.push_back(std::tr1::bind(func1, 42));
v[0]();
v[1]();
}

/Leigh

To handle the case where the menu item is a submenu and not a command
consider storing a variant of either a command or another menu in the
menu's item list (vector). I use my own variant class but there is
Boost.Variant. The alternative to using a variant would be to return to
a solution based on inheritance. :)

Ah, no problem for that, I wouldn't store the function<> directly in the
vector, I would just use it as a member of a class, then I would choose
between displaying a submenu or executing the callback depending on a
flag or something like that - more likely I'll make the choice depending
on whether an item has children or not - I'll post the new version here
once I'll have it ready, just for the records :)
 
F

Francesco S. Carta

IMHO I think use of inheritance is better than using callback functions.
Each derived menuitem type then has it's own private properties that in
turn can be accessed by descendant classes via protected functions. This
provides far greater flexibility IMHO, and
is easier to implement and understand.

I'm not so sure I understand your point: if I want to allow the client
code to pass functions pointers when creating the menu, so that the menu
can call those functions afterwards, I'm creating callbacks anyway,
regardless of whether I use inheritance or not - or am I
misunderstanding the meaning of "callback"?

I think I'll be able to create the menu I'm aiming to by using a single
class along with function<> and bind(), and I suspect that design will
be more concise and easier to maintain... I'll post the new code after
the dinner - it's 22:00 here, late dinner today :)
 
F

Francesco S. Carta

I'm not so sure I understand your point: if I want to allow the client
code to pass functions pointers when creating the menu, so that the menu
can call those functions afterwards, I'm creating callbacks anyway,
regardless of whether I use inheritance or not - or am I
misunderstanding the meaning of "callback"?

I think I'll be able to create the menu I'm aiming to by using a single
class along with function<> and bind(), and I suspect that design will
be more concise and easier to maintain... I'll post the new code after
the dinner - it's 22:00 here, late dinner today :)

And a long long dinner it was... yawn... all right.

Yes, decisively simpler and cleaner, thanks to Leigh's advice - just one
class and no need for any additional template.

Still susceptible of big improvements, but here it is:

//-------
#include <iostream>
#include <vector>
#include <tr1/functional>

class Menu {
public:
Menu(std::string command = "",
std::string caption = "",
Menu* parent = 0,
std::tr1::function<void()> callback = 0);

void operator()() const;

class Error {
public:
Error(std::string message = "") : message(message) {}
std::string what() const {
return message;
}
private:
std::string message;
};

static std::istream* input;
static std::eek:stream* output;

private:
std::string command;
std::string caption;
Menu* parent;
std::tr1::function<void()> callback;
std::vector<Menu*> children;

Menu(const Menu&);
Menu& operator=(const Menu&);
};

std::istream* Menu::input = 0;
std::eek:stream* Menu::eek:utput = 0;

Menu::Menu(std::string command,
std::string caption,
Menu* parent,
std::tr1::function<void()> callback)
: command(command),
caption(caption),
parent(parent),
callback(callback) {
if(parent) {
if (!parent->callback) {
for (size_t i = 0; i < parent->children.size(); ++i) {
if(command == parent->children->command) {
throw Error("!!! Duplicated command");
}
}
parent->children.push_back(this);
} else {
throw Error("!!! Parent item is not a container");
}
}
}

void Menu::eek:perator()() const {
using std::endl;
std::eek:stream& out = output ? *output : std::cout;
std::istream& in = input ? *input : std::cin;
if (callback) {
callback();
return;
}
if (parent) {
parent->operator()();
return;
}
const Menu* m = this;
for (;;) {
out << endl << "-------" << endl;
out << "exit - Exit menu" << endl;
if(m->parent) {
out << "up - Upper menu" << endl;
}
for (size_t i = 0; i < m->children.size(); ++i) {
out << m->children->command << " - ";
out << m->children->caption << endl;
}
out << "Insert command:" << endl;
out << ">";
std::string line;
getline(in, line);
if(line == "up" && m->parent) {
m = m->parent;
continue;
} else if (line == "exit") {
break;
} else {
bool found = false;
for (size_t i = 0; i < m->children.size(); ++i) {
if(line == m->children->command) {
found = true;
if(m->children->callback) {
out << endl;
m->children->callback();
} else {
m = m->children;
}
break;
}
}
if(found) continue;
out << endl;
out << "Unrecognized command" << endl;
}
}
}

using namespace std;
using namespace tr1;

void save() {
cout << "Saved" << endl;
}

void discard() {
cout << "Discarded" << endl;
}

void increase(int* i) {
if(i) {
++*i;
cout << "i increased" << endl;
}
}

void decrease(int* i) {
if(i) {
--*i;
cout << "i decreased" << endl;
}
}

void print(int* i) {
if(i) {
cout << "i == " << *i << endl;
}
}

int main() {
Menu menu;
Menu file("f", "File", &menu);
Menu edit("e", "Edit", &menu);

new Menu("s", "Save", &file, save);
new Menu("d", "Discard", &file, discard);

int i = 40;

Menu inc("i", "Increase i", &edit, bind(increase, &i));
new Menu("d", "Decrease i", &edit, bind(decrease, &i));
Menu pri("p", "Print i", &edit, bind(print, &i));

pri();
inc();
inc();
pri();

file();
}
//-------
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

No members online now.

Forum statistics

Threads
473,769
Messages
2,569,582
Members
45,070
Latest member
BiogenixGummies

Latest Threads

Top