使用从字符串中提取的参数调用函数

发布于 2024-12-20 23:01:09 字数 431 浏览 3 评论 0 原文

我正在考虑以下问题:

我得到格式如下的字符串:

functionname_parameter1_parameter2_parameter3
otherfunctionname_parameter1_parameter2
.
.
.

并且我想使用给定的参数调用该函数。 假设我有一个函数测试:

void test(int x, float y, std::string z) {}

并且我收到一条消息:

test_5_2.0_abc

然后我希望函数测试能够像这样自动调用:

test(5, 2.0, "abc");

您对如何在 C++ 中完成此操作有任何提示吗?

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

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(5

一片旧的回忆 2024-12-27 23:01:09

更新:更新了stream_function以修复@Nawaz在评论中提到的参数求值顺序问题,并且还删除了std::function提高效率。 请注意,评估顺序修复仅适用于 Clang,因为 GCC 不遵循此处的标准。 可以在 此处


这通常不是那么容易实现的。我围绕 std::function 编写了一个小包装类,从 std::istream 中提取参数。下面是一个使用 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';
  }
}

在 Clang 3.3 下编译并按预期工作(小型实例< /a>)。

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

再次组合这堂课很有趣,希望你喜欢。请注意,您需要稍微修改代码才能适用于您的示例,因为 C++ IOstream 使用空格作为分隔符,因此您需要将消息中的所有下划线替换为空格。不过应该很容易做到,之后只需从您的消息构造一个 std::istringstream 即可:

std::istringstream input(message_without_underscores);
// call and pass 'input'

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'
笑忘罢 2024-12-27 23:01:09

你几乎不能,C++ 对函数没有任何类型的反射。

接下来的问题是你能接近多远。如果适合的话,这样的接口是相当合理的:

string message = "test_5_2.0_abc";
string function_name = up_to_first_underscore(message);
registered_functions[function_name](message);

其中 registered_functionsmap>,并且您有显式地执行以下操作:

registered_functions["test"] = make_registration(test);

对于可以以这种方式调用的每个函数。

make_registration 将是一个相当复杂的模板函数,它将函数指针作为参数并返回一个 std::function 对象,该对象在调用时将字符串分割成块,并检查那里有正确的数字,使用boost::lexical_cast将每个数字转换为正确的参数类型,最后调用指定的函数。它会知道 make_registration 的模板参数中的“正确类型”——要接受任意多个参数,这必须是 C++11 可变参数模板,但您可以通过以下方式伪造它

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...

:重载和可选参数会增加进一步的复杂性。

虽然我对它们一无所知,但我希望有针对 SOAP 或其他 RPC 协议的 C++ 支持框架,其中可能包含一些相关代码。

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.

与君绝 2024-12-27 23:01:09

你正在寻找的是反思。而这在 C++ 中是不可能的。 C++ 的设计考虑了速度。如果您需要检查库或代码,然后识别其中的类型并调用与这些类型(通常是类)关联的方法,那么恐怕这在 C++ 中是不可能的。

如需进一步参考,您可以参考此线程。

如何向 C++ 应用程序添加反射?

http://en.wikibooks.org/wiki/C%2B%2B_Programming/RTTI

为什么C++没有反射?

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?

遇见了你 2024-12-27 23:01:09

您可以解析字符串,分离参数并将它们发送到函数,没有任何问题,但是您不能做的是在字符串上引用函数及其名称,因为该函数在运行时不再有名称。
您可以使用 if-else if 链来检查函数名称,然后解析参数并调用特定函数。

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.

伴随着你 2024-12-27 23:01:09

我修改了 @Xeo 的代码以正确使用 gcc,因此它确保以正确的顺序提取参数。我只是发布此内容,因为我花了一段时间才理解原始代码和命令执行中的拼接。全部功劳仍应归功于@Xeo。如果我发现我的实现有任何问题,我会回来进行编辑,但到目前为止,在我的测试中我还没有看到任何问题。

#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;

这也适用于 Visual Studio 2013,尚未尝试过早期版本。

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.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文