I am trying to create a text-based menu class to give myself a way to create more dynamic menus in programs. Basically the menu class has a vector of option classes that contain the menu-option's name, function to call etc.
The problem is that I want to be able to associate any function with an option. For example, say that option 1 calls func1( int, string ) and option 2 calls func2( string, ostream&, int ) and so on. Right now I am passing a function pointer but I don't think it is possible to have variable types of arguments with a function pointer.
class Option
{
private:
int option_number; // for display and vector purposes
string name; // title for user to read
void (*func_pointer)( void ); // function to be executed
public:
Option( int option_number, string name, void (*func_name)() ); // constructor, fills option vars
void callFunction(); // calls function stored in option
// Getters
int getOptionNumber();
string getOptionName();
void (*getFunctionPointer())();
};
void MenuType::displayMenu()
{
for ( int i = 0 ; i < options.size() ; i++ )
{
cout<<options[i].getOptionNumber()<<". "<<options[i].getOptionName()<<endl;
}
cout<<"Enter a menu option: ";
int option_choice;
cin>>option_choice;
cin.ignore();
options[option_choice-1].callFunction();
}
/***** Used in a program like this *****/
int main()
{
MenuType first_menu;
first_menu.addOption( 1, "option 1", function ); // but here i would like to be able to put function( arg1, arg2 ). how?
first_menu.addOption( 2, "option 2", function2 ); // but be able to put function2( arg3, arg4, arg5 ) here
first_menu.addOption( 3, "option 3", function3 );
first_menu.displayMenu();
}
Is there a better way to do this? I'm sure there is.
laserlight
April 15th, 2008, 01:46 PM
Kindly post code in code tags.
How do you plan to actually call the function associated? Like how would you know that an int and a string is needed, and what they are for?
It seems to me that all these functions should be self-contained, so they really should not take any arguments.
Originally, I wanted to suggest the use of function objects instead, but then I ran into the problem of how would you call operator() with different arguments, or even anticipate what arguments are needed. You could store whatever initial values are needed by using a constructor, but that is different from calling a function with a set of arguments.
souldog
April 15th, 2008, 01:54 PM
yes that is the question.
Where are the arguments going to come from that are passed to the function
JamesSchumacher
April 15th, 2008, 02:17 PM
The best way to do this is a delegate system.
class IDelegate
{
public:
virtual void Invoke(void * lpArgs) = 0; // It's virtual, implementers hold any data they want, can spread out args from a structure, pass structure, etc...
};
For example. You can make multiple template classes to encompass any amount of options you want. Remember, it's designed around an abstract class. The thing is, you pass a structure with the data you need, either just reinterpret_cast to the appropriate structure type, or spread out args as necessary. You can even make a TCallback or something template class that doesn't use the arguments at all.
You could also add to the IDelegate interface an interface function that returned a void pointer (or a typed base class pointer) to the type of args that the delegate requires. The function that returns these args could be named something to the effect... "RetrieveUserDataArgs" and get the data from the user.
OR...
Here's an idea. Retrieve the data arguments from the user in the handler function itself.
rustyj110
April 15th, 2008, 02:23 PM
ok so this is for a computer science class where I am writing a gradebook program that deals with different students, teachers, and courses. This is not specifically part of the assignment but I thought it would be really cool/useful if it works.
I'm not sure exactly what you are asking but I'll try to explain further.
Say I want have a menu for viewing teachers profiles:
I'm not sure how to call the function with these arguments. And I don't know how I could consolidate the functions to have no arguments. For example, the printProfile( ostream&, TeacherType& ) function above really needs the teacher object so that it can print more than one teacher's profile.
Is there a way to tackle this problem without function pointers?
JamesSchumacher
April 15th, 2008, 02:29 PM
Is there a way to tackle this problem without function pointers?
Take a look at my proposed delegate system. It makes the functions pointers internal to an object. Therefore, another data structure holds the object through an IDelegate pointer, therefore... It calls Invoke() to invoke the function pointer through the IDelegate interface.
laserlight
April 15th, 2008, 02:38 PM
What I had in mind concerning function objects is something like this:
class MenuFunction
{
public:
virtual void operator()() = 0;
virtual ~MenuFunction() {}
};
class PrintProfile : public MenuFunction
{
public:
PrintProfile(std::ostream& fout, TeacherType& teacher)
: fout(fout), teacher(teacher) {}
void operator()()
{
fout << teacher; // or whatever
}
private:
std::ostream& fout;
TeacherType& teacher;
In this case I think it is applicable for what you want to do, but a more general solution would be to use something like what JamesSchumacher described. If you use raw pointers as in my example, remember to delete the MenuFunction pointers.
JamesSchumacher
April 15th, 2008, 03:21 PM
This is probably very close to what you are looking for. Take it and learn from it, and develop your own system.
rustyj110
April 15th, 2008, 04:35 PM
Wow -- thanks to both of you! I need some time to try to understand exactly what is going on here but I will let you know how it ends up!
rustyj110
April 15th, 2008, 08:03 PM
Ok so I did a lot of research into virtual functions and I think I understand them at least a little. I reformatted James' code to try to make it work for functions that are not a member of any class. I got it to work with no arguments, but once I try to add arguments everything gets off. It has a few compile errors right now.
Any ideas? I really appreciate all of your guys' help.
Second of all, I see that since you defined it like I suggested, when you defined <void,int> to intend on a void return value with an integer argument, you did not make the connection that it made it require a function that took an int *.
This is what is the other error is, I am sure of it.
void display(int p) // example function
{
cout<<"Number: "<<p<<endl; }
void print( ostream& fout ) // example function, using ostream&
{
fout<<"This works."<<endl;
}
int main()
{
MenuFunction1arg<void,int> first( display, 25 ); // This works
first.CallFunction();
MenuFunction1arg<void,ostream&> second( print, cout ); // This does not work
second.CallFunction();
}
Ok so I changed it so that the constructor makes a copy of the argument and saves it to an object Argtype fnArgs. This works for almost all arguments, but the problem occurs when I pass an ostream& into the constructor, because it is not able to make a copy of the ostream& object. I don't think it can copy object references.
I would really like to find some way to make this function because I need to pass ostream& objects to almost all of the functions I want to call in my program.
How else can I do this?
I guess I could declare the ostream objects globally and include some other type of indicator variable in the function to tell it which ostream to use... ? That doesn't seem like good coding.
JamesSchumacher
April 17th, 2008, 08:55 AM
Ok, so I ended up changing the code quite a bit. And it work! well.. for the most part... I have one more problem.
void display(int p) // example function
{
cout<<"Number: "<<p<<endl; }
void print( ostream& fout ) // example function, using ostream&
{
fout<<"This works."<<endl;
}
int main()
{
MenuFunction1arg<void,int> first( display, 25 ); // This works
first.CallFunction();
MenuFunction1arg<void,ostream&> second( print, cout ); // This does not work
second.CallFunction();
}
Ok so I changed it so that the constructor makes a copy of the argument and saves it to an object Argtype fnArgs. This works for almost all arguments, but the problem occurs when I pass an ostream& into the constructor, because it is not able to make a copy of the ostream& object. I don't think it can copy object references.
I would really like to find some way to make this function because I need to pass ostream& objects to almost all of the functions I want to call in my program.
How else can I do this?
I guess I could declare the ostream objects globally and include some other type of indicator variable in the function to tell it which ostream to use... ? That doesn't seem like good coding.
It will work if you initialize the ostream reference (&) in the initializer list of the constructor instead of the body of code for the constructor. References as member variables of a class have to be initialized in the constructor initializer list.
rustyj110
April 17th, 2008, 11:38 AM
Sweet! It feels so good when something finally works!
Thanks James!
For anyone who wants to see, this is what I ended up with:
template <typename Type, typename Argtype> class MenuFunction1arg
{
public:
typedef Type (*FunctionType)(Argtype);
MenuFunction1arg( FunctionType call_back, Argtype Arg1 ) : fnArgs( Arg1 ) // <-- added this
{
//fnArgs = Arg1; // <-- removed this
p_call_back = call_back;
}
Type CallFunction()
{
return (*p_call_back)(fnArgs);
}
private:
Argtype fnArgs;
FunctionType p_call_back;
};