Reputation: 1563
I'm looking at the following problem:
I get strings that are formatted like this:
functionname_parameter1_parameter2_parameter3
otherfunctionname_parameter1_parameter2
.
.
.
and i would like to call the function with the given parameters. So let's say i have a function test:
void test(int x, float y, std::string z) {}
and i get a message:
test_5_2.0_abc
then i would like the function test to be automatically invoked like this:
test(5, 2.0, "abc");
Do you have any hints on how to accomplish this in C++?
Upvotes: 8
Views: 5731
Reputation: 377
I modified @Xeo's code to work with gcc properly, so it ensures the parameters are pulled in the right order. I'm only posting this since it took me a while to understand the original code and splice in the order-enforcement. Full credit should still go to @Xeo. If I find anything wrong with my implementation I'll come back and edit, but thus far in my testing I haven't seen any problems.
#include <map>
#include <string>
#include <iostream>
#include <sstream>
#include <functional>
#include <stdexcept>
#include <type_traits>
#include <tuple>
template<class...> struct types{};
// for proper evaluation of the stream extraction to the arguments
template<class ReturnType>
struct invoker {
ReturnType result;
template<class Function, class... Args>
invoker(Function&& f, Args&&... args) {
result = f(std::forward<Args>(args)...);
}
};
template<>
struct invoker<void> {
template<class Function, class... Args>
invoker(Function&& f, Args&&... args) {
f(std::forward<Args>(args)...);
}
};
template<class Function, class Sig>
struct StreamFunction;
template<class Function, class ReturnType, class... Args>
struct StreamFunction<Function, ReturnType(Args...)>
{
StreamFunction(Function f)
: _f(f) {}
void operator()(std::istream& args, std::string* out_opt) const
{
call(args, out_opt, std::is_void<ReturnType>());
}
private:
template<class T>
static T get(std::istream& args)
{
T t; // must be default constructible
if(!(args >> t))
{
args.clear();
throw std::invalid_argument("invalid argument to stream_function");
}
return t;
}
//must be mutable due to const of the class
mutable std::istream* _args;
// void return
void call(std::istream& args, std::string*, std::true_type) const
{
_args = &args;
_voidcall(types<Args...>{});
}
template<class Head, class... Tail, class... Collected>
void _voidcall(types<Head, Tail...>, Collected... c) const
{
_voidcall<Tail...>(types<Tail...>{}, c..., get<Head>(*_args));
}
template<class... Collected>
void _voidcall(types<>, Collected... c) const
{
invoker<void> {_f, c...};
}
// non-void return
void call(std::istream& args, std::string* out_opt, std::false_type) const {
if(!out_opt) // no return wanted, redirect
return call(args, nullptr, std::true_type());
_args = &args;
std::stringstream conv;
if(!(conv << _call(types<Args...>{})))
throw std::runtime_error("bad return in stream_function");
*out_opt = conv.str();
}
template<class Head, class... Tail, class... Collected>
ReturnType _call(types<Head, Tail...>, Collected... c) const
{
return _call<Tail...>(types<Tail...>{}, c..., get<Head>(*_args));
}
template<class... Collected>
ReturnType _call(types<>, Collected... c) const
{
return invoker<ReturnType> {_f, c...} .result;
}
Function _f;
};
template<class Sig, class Function>
StreamFunction<Function, Sig> CreateStreamFunction(Function f)
{
return {f};
}
typedef std::function<void(std::istream&, std::string*)> StreamFunctionCallType;
typedef std::map<std::string, StreamFunctionCallType> StreamFunctionDictionary;
This also works with Visual Studio 2013, have not tried earlier versions.
Upvotes: 0
Reputation: 131839
Update: Updated stream_function
to fix the argument-evaluation-order problem @Nawaz mentioned in the comments, and also removed the std::function
for improved efficiency. Note that the evaluation-order fix only works for Clang, as GCC doesn't follow the standard here. An example for GCC, with manual order-enforcement, can be found here.
This is generally not that easy to accomplish. I wrote a little wrapper class around std::function
once that extracts the arguments from a std::istream
. Here's an example using C++11:
#include <map>
#include <string>
#include <iostream>
#include <sstream>
#include <functional>
#include <stdexcept>
#include <type_traits>
// for proper evaluation of the stream extraction to the arguments
template<class R>
struct invoker{
R result;
template<class F, class... Args>
invoker(F&& f, Args&&... args)
: result(f(std::forward<Args>(args)...)) {}
};
template<>
struct invoker<void>{
template<class F, class... Args>
invoker(F&& f, Args&&... args)
{ f(std::forward<Args>(args)...); }
};
template<class F, class Sig>
struct stream_function_;
template<class F, class R, class... Args>
struct stream_function_<F, R(Args...)>{
stream_function_(F f)
: _f(f) {}
void operator()(std::istream& args, std::string* out_opt) const{
call(args, out_opt, std::is_void<R>());
}
private:
template<class T>
static T get(std::istream& args){
T t; // must be default constructible
if(!(args >> t)){
args.clear();
throw std::invalid_argument("invalid argument to stream_function");
}
return t;
}
// void return
void call(std::istream& args, std::string*, std::true_type) const{
invoker<void>{_f, get<Args>(args)...};
}
// non-void return
void call(std::istream& args, std::string* out_opt, std::false_type) const{
if(!out_opt) // no return wanted, redirect
return call(args, nullptr, std::true_type());
std::stringstream conv;
if(!(conv << invoker<R>{_f, get<Args>(args)...}.result))
throw std::runtime_error("bad return in stream_function");
*out_opt = conv.str();
}
F _f;
};
template<class Sig, class F>
stream_function_<F, Sig> stream_function(F f){ return {f}; }
typedef std::function<void(std::istream&, std::string*)> func_type;
typedef std::map<std::string, func_type> dict_type;
void print(){
std::cout << "print()\n";
}
int add(int a, int b){
return a + b;
}
int sub(int a, int b){
return a - b;
}
int main(){
dict_type func_dict;
func_dict["print"] = stream_function<void()>(print);
func_dict["add"] = stream_function<int(int,int)>(add);
func_dict["sub"] = stream_function<int(int,int)>(sub);
for(;;){
std::cout << "Which function should be called?\n";
std::string tmp;
std::cin >> tmp;
auto it = func_dict.find(tmp);
if(it == func_dict.end()){
std::cout << "Invalid function '" << tmp << "'\n";
continue;
}
tmp.clear();
try{
it->second(std::cin, &tmp);
}catch(std::exception const& e){
std::cout << "Error: '" << e.what() << "'\n";
std::cin.ignore();
continue;
}
std::cout << "Result: " << (tmp.empty()? "none" : tmp) << '\n';
}
}
Compiles under Clang 3.3 and works as expected (small live example).
Which function should be called?
a
Invalid function 'a'
Which function should be called?
add
2
d
Error: 'invalid argument to stream_function'
Which function should be called?
add
2
3
Result: 5
Which function should be called?
add 2 6
Result: 8
Which function should be called?
add 2
6
Result: 8
Which function should be called?
sub 8 2
Result: 6
It was fun to hack that class together again, hope you enjoy. Note that you need to modify the code a little to work for your example, since C++ IOstreams have whitespace as delimiter, so you'd need to replace all underscores in your message with spaces. Should be easy to do though, after that just construct a std::istringstream
from your message:
std::istringstream input(message_without_underscores);
// call and pass 'input'
Upvotes: 20
Reputation: 279345
You pretty much can't, C++ doesn't have any kind of reflection on functions.
The question then is how close you can get. An interface like this is pretty plausible, if it would suit:
string message = "test_5_2.0_abc";
string function_name = up_to_first_underscore(message);
registered_functions[function_name](message);
Where registered_functions
is a map<string,std::function<void,string>>
, and you have to explicitly do something like:
registered_functions["test"] = make_registration(test);
for each function that can be called in this way.
make_registration
would then be a fairly hairy template function that takes a function pointer as a parameter and returns a std::function
object that when called splits the string into chunks, checks that there are the right number there, converts each one to the correct parameter type with a boost::lexical_cast
, and finally calls the specified function. It would know the "correct type" from the template argument to make_registration
-- to accept arbitrarily many parameters this would have to be a C++11 variadic template, but you can fake it with:
std::function<void,string> make_registration(void(*fn)(void));
template <typename T>
std::function<void,string> make_registration(void(*fn)(T));
template <typename T, U>
std::function<void,string> make_registration(void(*fn)(T, U));
// etc...
Dealing with overloads and optional parameters would add further complication.
Although I don't know anything about them, I expect that there are C++ support frameworks out there for SOAP or other RPC protocols, that might contain some relevant code.
Upvotes: 3
Reputation: 3500
What you are looking for is reflection. And it is not possible in C++. C++ is designed with speed in mind. If you require inspection of a library or code and then identify the types in it and invoke methods associated with those types (usually classes) then I am afraid it is not possible in C++.
For further reference you can refer to this thread.
How can I add reflection to a C++ application?
http://en.wikibooks.org/wiki/C%2B%2B_Programming/RTTI
Why does C++ not have reflection?
Upvotes: 1
Reputation: 12276
You could parse the string, separate the arguments and send them to the function with no problem, but what you cannot do is reference the function with its name on a string, because the function doesn't have a name anymore at runtime.
You could have a if-else if chain that checks for the function name, and then parse the arguments and call the specific function.
Upvotes: 0