Call function with parameters extracted from strin

2019-01-17 08:59发布

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++?

5条回答
虎瘦雄心在
2楼-- · 2019-01-17 09:21

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.

查看更多
一纸荒年 Trace。
3楼-- · 2019-01-17 09:22

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'
查看更多
地球回转人心会变
4楼-- · 2019-01-17 09:27

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.

查看更多
走好不送
5楼-- · 2019-01-17 09:32

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.

查看更多
冷血范
6楼-- · 2019-01-17 09:36

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?

查看更多
登录 后发表回答