我在以下位置的stackoverflow上找到了这个有趣的代码:
Using a STL map of function pointers
template<typename T,typename... Args>
T searchAndCall(std::string s1, Args&&... args){
// ....
// auto typeCastedFun = reinterpret_cast<T(*)(Args ...)>(mapVal.first);
auto typeCastedFun = (T(*)(Args ...))(mapVal.first);
//compare the types is equal or not
assert(mapVal.second == std::type_index(typeid(typeCastedFun)));
return typeCastedFun(std::forward<Args>(args)...);
}
};
基本上,mapVal是一个转换为
void(*)(void)
的函数指针的映射,该指针将通过此函数转换回其原始类型。我想知道的是,当您不指定模板参数时如何推断typeCastedFun。例如,假设您有:
int f(const MyClass& a, MyClass b) {...}
... 如果你有:
MyClass first, second;
searchAndCall<int>(first, second);
什么
Args...
参数将被推导?如果我没记错的话,使用转换回具有与原始签名不同的签名的函数的函数应该会产生未定义的行为。还有其他选择吗?我想做的是一种将函数类型存储在某处并使用此信息进行正确的转换的方法。一切以最有效的方式。
谢谢
[edit1]
更具体地说,我正在尝试构建一种通用的函数分派(dispatch)器,出于效率的考虑,该分派(dispatch)器可以使用查找表调用具有不同签名的函数(带有枚举类值的模板)。没有提升::任何内部使用新
[edit2]不允许使用宏
最佳答案
关键问题在于,直接采用调用参数类型并尝试强制转换函数指针,您将丢失所有隐式转换。
您的功能签名必须完全匹配,否则,如果尝试调用它,将会得到UB。如果没有在调用站点手动指定签名,通常就无法从args获取签名。
尝试的一种解决方法是添加一个包装器lambda,该包装器使用标准化args并应用预先指定的隐式掩盖,例如T -> const T&
,可能还包括numeric types -> double
。
然后,当您查找该函数时,可以将其强制转换为使用这些标准化的args,并且调用的args将被隐式转换。
这将排除使用rvalue ref和非const引用的函数,但是对于您不知道其签名的函数,除非您想完全忽略const-correctness,否则我认为这是不合理的。
此外,其他隐式转换也不会发生,例如Derived& -> Base&
或char* -> std::string
,我认为没有任何简单的方法可以在不产生额外限制的情况下实现这一目标。
总的来说,在c++中绝对是一件棘手的事情,而且您尝试的任何事情都会变得很棘手。这种方式应该足够体面。一个额外的函数调用(可以内联)的性能开销,以及不可避免的RTTI检查,可能掩盖了一些无关的参数转换。
这是一个示例实现(also here on ideone):
#include <unordered_map>
#include <typeinfo>
#include <typeindex>
#include <string>
#include <type_traits>
#include <iostream>
#include <assert.h>
#include <cxxabi.h>
#include <sstream>
#include <stdexcept>
template <typename Func, Func f>
struct store_func_helper;
// unix-specific
std::string demangle(const std::string& val) {
int status;
char *realname;
std::string strname = realname = abi::__cxa_demangle(val.c_str(), 0, 0, &status);
free(realname);
return strname;
}
// args will be implicitly converted to arg<T>::type before calling function
// default: convert to const Arg&
template <typename Arg, typename snifae=void>
struct arg {
using type = const Arg&;
};
// numeric types: convert to double.
template <typename Arg>
struct arg <Arg, typename std::enable_if<std::is_arithmetic<Arg>::value, void>::type> {
using type = double;
};
// set more special arg types here.
// Functions stored in the map are first wrapped in a lambda with this signature.
template <typename Ret, typename... Arg>
using func_type = Ret(*)(typename arg<Arg>::type...);
class func_map {
template <typename Func, Func f>
friend class store_func_helper;
public:
template <typename Func, Func f>
void store(const std::string& name){
store_func_helper<Func, f>::call(this, name );
}
template<typename Ret, typename... Args>
Ret call(std::string func, Args... args){
using new_func_type = func_type<Ret, Args...>;
auto& mapVal = m_func_map.at(func);
if (mapVal.second != std::type_index(typeid(new_func_type))){
std::ostringstream ss;
ss << "Error calling function " << func << ", function type: "
<< demangle(mapVal.second.name())
<< ", attempted to call with " << demangle(typeid(new_func_type).name());
throw std::runtime_error(ss.str());
}
auto typeCastedFun = (new_func_type)(mapVal.first);
//args will be implicitly converted to match standardized args
return typeCastedFun(std::forward<Args>(args)...);
};
private:
std::unordered_map<std::string, std::pair<void(*)(),std::type_index> > m_func_map;
};
#define FUNC_MAP_STORE(map, func) (map).store<decltype(&func),&func>(#func);
template <typename Ret, typename... Args, Ret(*f)(Args...)>
struct store_func_helper<Ret(*)(Args...), f> {
static void call (func_map* map, const std::string& name) {
using new_func_type = func_type<Ret, Args...>;
// add a wrapper function, which takes standardized args.
new_func_type lambda = [](typename arg<Args>::type... args) -> Ret {
return (*f)(args...);
};
map->m_func_map.insert(std::make_pair(
name,
std::make_pair((void(*)()) lambda, std::type_index(typeid(lambda)))
));
}
};
//examples
long add (int i, long j){
return i + j;
}
int total_size(std::string arg1, const std::string& arg2) {
return arg1.size() + arg2.size();
}
int main() {
func_map map;
FUNC_MAP_STORE(map, total_size);
FUNC_MAP_STORE(map, add);
std::string arg1="hello", arg2="world";
std::cout << "total_size: " << map.call<int>("total_size", arg1, arg2) << std::endl;
std::cout << "add: " << map.call<long>("add", 3, 4) << std::endl;
}
关于c++ - 使用可变参数将函数转换回原始签名,我们在Stack Overflow上找到一个类似的问题:https://stackoverflow.com/questions/43087809/