Is there any way to retrieve information on what paramaters were bounded by boost::bind or does this need to be stored manually?
i.e.:
in .h
class MyClass
{
void foo(int a);
void foo2(double b);
void bar();
void execute();
int _myint;
double _mydouble;
}
in .cpp
MyClass::bar()
{
vector<boost::function<void(void)> myVector;
myVector.push_back(boost::bind(&MyClass::foo, this, MyClass::_myint);
myVector.push_back(boost::bind(&MyClass::foo2, this, MyClass::_mydouble);
}
MyClass::execute(char* param)
{
boost::function<void(void)> f = myVector[0];
//MAGIC goes here
//somehow know that an int parameter was bound
_myint = atoi(param);
//--------------------------------------
f();
}
Since it looks like you are just looking for ways to trigger functions in response to parsed text, I propose this Boost Spirit parser based example:
I want to be able to call pre-existing functions of various classes:
/exec <functionName> <param1> <param2>
Imagine your application has the following existing classes which represent services that the user should be able to call into using text commands:
struct Echo
{
void WriteLine(const std::string& s) { std::cout << "WriteLine('" << s << "');" << std::endl; }
void WriteStr (const std::string& s) { std::cout << "Write(string: '" << s << "');" << std::endl; }
void WriteInt (int i) { std::cout << "Write(int: " << i << ");" << std::endl; }
void WriteDbl (double d) { std::cout << "Write(double: " << d << ");" << std::endl; }
void NewLine () { std::cout << "NewLine();" << std::endl; }
} echoService;
struct Admin
{
void Shutdown(const std::string& reason, int retval)
{
std::cout << "Shutdown(reason: '" << reason << "', retval: " << retval << ")" << std::endl;
// exit(retval);
}
} adminService;
So how do we tie line-based input to this 'interface'? There are two jobs
You have been emphasizing on providing infrastructure for evaluation. However, you run into the problem of not knowing what parameters to pass. You know when parsing, of course. You would really like to avoid having to store the arguments in a generic container. (Of course, you could throw boost optionals, boost (recursive) variants, boost any at it all at once, but let's face it: that is still tedious busy-work).
This is an excellent opportunity for a parser with semantic actions. Lex/yacc, Coco/R C++, ANTLR and many more all support them. So does Boost Spirit Qi.
Without further ado, this is what a complete, minimalist line grammar could look like for the services described above:
parser = "/execute" > (
(lit("WriteLine") > stringlit)
| (lit("Write") >> +(double_ | int_ | stringlit))
| lit("NewLine")
| (lit("Shutdown") > (stringlit > -int_))
// stringlit is just a quoted string:
stringlit = lexeme[ '"' >> *~char_('"') >> '"' ];
Note: I decided to show you how you can accept arbitrary numbers of arguments of various types to the /execute Write
call
Adding the semantic actions to get tie this to our echoService
and adminService
objects, we get this REPL engine to parse and evaluate a single line:
void execute(const std::string& command)
{
typedef std::string::const_iterator It;
It f(command.begin()), l(command.end());
if (!phrase_parse(f,l, "/execute" > (
(lit("WriteLine")
> stringlit [ bind(&Echo::WriteLine, ref(echoService), _1) ])
| (lit("Write") >> +(
double_ [ bind(&Echo::WriteDbl, ref(echoService), _1) ]
| int_ [ bind(&Echo::WriteInt, ref(echoService), _1) ]
| stringlit [ bind(&Echo::WriteStr, ref(echoService), _1) ]
))
| (lit("NewLine") [ bind(&Echo::NewLine, ref(echoService)) ])
| (lit("Shutdown") > (stringlit > (int_ | attr(0)))
[ bind(&Admin::Shutdown, ref(adminService), _1, _2) ])
), space))
{
// handle error, see full code below
}
}
Now all that's left for us to do, is program a main loop:
int main()
{
std::string command;
while (std::getline(std::cin, command))
execute(command);
}
That's pretty simple, not?
I posted a complete working example of this program on github 1: https://gist.github.com/1314900
It has
All you need is Boost. I tested this with g++ 4.6.1 (no special options) and Boost 1.47. For the following test input (input.txt):
/execute WriteLine "bogus"
/execute Write "here comes the answer: "
/execute Write 42
/execute Write 31415e-4
/execute Write "that is the inverse of" 24 "and answers nothing"
/execute Shutdown "Bye" 9
/execute Shutdown "Test default value for retval"
The output of the demo program is
WriteLine('bogus');
Write(string: 'here comes the answer: ');
Write(double: 42);
Write(double: 3.1415);
Write(string: 'that is the inverse of');
Write(double: 24);
Write(string: 'and answers nothing');
Shutdown(reason: 'Bye', retval: 9)
Shutdown(reason: 'Test default value for retval', retval: 0)
exit(...)
call so I could demo how you could make the retval
parameter optional while supplying a default value (attr(0)
)31415e-4
is prints correctly as 3.1415
/execute Write
get translated in separate calls to echoService.Write(...)
depending on the actual type of the input parameter1 In the interest of posterity, should github cease to host my gist:
#include <boost/spirit/include/qi.hpp>
#include <boost/spirit/include/phoenix.hpp>
#include <fstream>
namespace qi = boost::spirit::qi;
namespace phx = boost::phoenix;
///////////////////////////////////
// 'domain classes' (scriptables)
struct Echo
{
void WriteLine(const std::string& s) { std::cout << "WriteLine('" << s << "');" << std::endl; }
void WriteStr (const std::string& s) { std::cout << "Write(string: '" << s << "');" << std::endl; }
void WriteInt (int i) { std::cout << "Write(int: " << i << ");" << std::endl; }
void WriteDbl (double d) { std::cout << "Write(double: " << d << ");" << std::endl; }
void NewLine () { std::cout << "NewLine();" << std::endl; }
} echoService;
struct Admin
{
void Shutdown(const std::string& reason, int retval)
{
std::cout << "Shutdown(reason: '" << reason << "', retval: " << retval << ")" << std::endl;
// exit(retval);
}
} adminService;
void execute(const std::string& command)
{
typedef std::string::const_iterator It;
It f(command.begin()), l(command.end());
using namespace qi;
using phx::bind;
using phx::ref;
rule<It, std::string(), space_type> stringlit = lexeme[ '"' >> *~char_('"') >> '"' ];
try
{
if (!phrase_parse(f,l, "/execute" > (
(lit("WriteLine")
> stringlit [ bind(&Echo::WriteLine, ref(echoService), _1) ])
| (lit("Write") >> +(
double_ [ bind(&Echo::WriteDbl, ref(echoService), _1) ] // the order matters
| int_ [ bind(&Echo::WriteInt, ref(echoService), _1) ]
| stringlit [ bind(&Echo::WriteStr, ref(echoService), _1) ]
))
| (lit("NewLine") [ bind(&Echo::NewLine, ref(echoService)) ])
| (lit("Shutdown") > (stringlit > (int_ | attr(0)))
[ bind(&Admin::Shutdown, ref(adminService), _1, _2) ])
), space))
{
if (f!=l) // allow whitespace only lines
std::cerr << "** (error interpreting command: " << command << ")" << std::endl;
}
}
catch (const expectation_failure<It>& e)
{
std::cerr << "** (unexpected input '" << std::string(e.first, std::min(e.first+10, e.last)) << "') " << std::endl;
}
if (f!=l)
std::cerr << "** (warning: skipping unhandled input '" << std::string(f,l) << "')" << std::endl;
}
int main()
{
std::ifstream ifs("input.txt");
std::string command;
while (std::getline(ifs/*std::cin*/, command))
execute(command);
}
As far as I know, you can't do what you want, as std::function
throws away (nearly) all type information of the passed function pointer, function object or lambda. It employs a technique called type erasure and on the surface completely forgets what was passed into it, as long as it is callable with the provided arguments. So, after binding, you're out of luck it seems.
However, you can supply that information yourself:
#include <functional>
#include <sstream>
#include <string>
#include <vector>
struct call_info{
std::function<void(void)> func;
std::string arg_type;
};
class MyClass{
std::vector<call_info> _myVec;
int _myInt;
double _myDouble;
public:
void foo1(int i);
void foo2(double d);
void bar();
void execute(char* param);
};
void MyClass::bar(){
call_info info;
info.func = std::bind(&MyClass::foo1, this, &MyClass::_myInt);
info.arg_type = "int";
_myVector.push_back(info);
info.func = std::bind(&MyClass::foo2, this, &MyClass::_myDouble);
info.arg_type = "double";
_myVector.push_back(info);
}
void MyClass::execute(char* param){
call_info& info = _myVector[0];
std::stringstream conv(param);
if(info.arg_type == "int")
conv >> _myInt;
else if(info.arg_type == "double")
conv >> _myDouble;
info.func();
}
Not nearly as clean as having it supplied automatically, but as far as I know there's no better way (except completely changing your implementation like sehe proposes).
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With