我正在考虑以下问题:
我得到格式如下的字符串:
functionname_parameter1_parameter2_parameter3
otherfunctionname_parameter1_parameter2
.
.
.
我想用给定的参数调用该函数。 所以,假设我有一个功能测试:
void test(int x, float y, std::string z) {}
我收到一条消息:
test_5_2.0_abc
然后我想像这样自动调用函数test:
test(5, 2.0, "abc");
您对如何在C ++中完成此操作有任何提示吗?
答案 0 :(得分:20)
更新:更新了stream_function
以修复评论中提到的参数评估订单问题@Nawaz,并删除了std::function
以提高效率。 请注意,评估订单修复仅适用于Clang,因为GCC不遵循此处的标准。可以找到GCC的示例,其中包含手动订单执行,here。< / p>
这通常不容易实现。我在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 ++ IOstreams将空格作为分隔符,因此您需要使用空格替换消息中的所有下划线。应该很容易做到,之后只需从你的消息构建std::istringstream
:
std::istringstream input(message_without_underscores);
// call and pass 'input'
答案 1 :(得分:3)
你几乎不可能,C ++对函数没有任何反思。
接下来的问题是你有多接近。像这样的界面非常合理,如果适合的话:
string message = "test_5_2.0_abc";
string function_name = up_to_first_underscore(message);
registered_functions[function_name](message);
其中registered_functions
是map<string,std::function<void,string>>
,您必须明确执行以下操作:
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 ++支持框架,可能包含一些相关的代码。
答案 2 :(得分:1)
您正在寻找的是反思。而且在C ++中是不可能的。 C ++的设计考虑了速度。如果您需要检查库或代码,然后识别其中的类型并调用与这些类型(通常是类)相关的方法,那么我担心它在C ++中是不可能的。
如需进一步参考,请参阅此主题。
How can I add reflection to a C++ application?
答案 3 :(得分:0)
你可以解析字符串,分离参数并将它们发送到函数没有问题,但你不能做的是在字符串上引用函数及其名称,因为函数在运行时不再具有名称。
您可以使用if-else if链检查函数名称,然后解析参数并调用特定函数。
答案 4 :(得分:0)
我修改了@ 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,尚未尝试过早期版本。