【问题标题】:Call function with parameters extracted from string使用从字符串中提取的参数调用函数
【发布时间】:2011-12-12 15:52:21
【问题描述】:

我正在研究以下问题:

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

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++ 中完成此任务有任何提示吗?

【问题讨论】:

  • 您可以使用字符串标记器和变体对象,但这很丑。

标签: c++ parsing function reflection binding


【解决方案1】:

更新: 更新了stream_function 以修复cmets 中提到的参数评估顺序问题@Nawaz,并删除了std::function 以提高效率。 请注意,评估顺序修复仅适用于 Clang,因为 GCC 不遵循此处的标准。 可以在 here 找到手动执行顺序的 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 下编译并按预期工作 (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

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

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

【讨论】:

  • 哇!一段惊人的代码......但这又使事情变得静态,因为您必须将函数指针添加到字典中以便稍后调用,对吗?
  • @Ajaj:当然,要在运行时创建函数,您需要一种动态语言,而 C++ 不是。
  • @Xeo:我非常喜欢您使用可变参数模板方法的解决方案。你知道如何将它与成员函数一起使用吗?您可以使用 std::bind 并将其轻松传递给您的 stream_function,但我宁愿寻找一种解决方案,您只需将 &MyStruct::myFunc 作为参数传递(而不改变漂亮的模板格式,例如 很多!)
  • @verb-sap: 你必须使用bind,因为我没有适当的机制可以保存this 指针(这会将我的stream_function 绑定到特定类),我不打算这样做。这正是我选择std::function作为函数持有者的原因,所以用户可以传入任何他们想要的内容。
  • @Nawaz 这不是未指明的行为,这是可怕的命名
【解决方案2】:

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

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

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

其中registered_functionsmap&lt;string,std::function&lt;void,string&gt;&gt;,您必须明确执行以下操作:

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++ 支持框架,其中可能包含一些相关代码。

【讨论】:

    【解决方案3】:

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

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

    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?

    【讨论】:

      【解决方案4】:

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

      【讨论】:

        【解决方案5】:

        我修改了@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,没有尝试过早期版本。

        【讨论】:

          猜你喜欢
          • 2021-11-23
          • 2015-12-27
          • 1970-01-01
          • 2019-12-31
          • 2011-09-08
          • 2014-07-29
          • 2019-10-26
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多