使用可变参数ar

时间:2017-03-29 08:06:30

标签: c++ c++11 templates casting variadic-templates

我在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] 更具体地说,我试图构建一种通用函数调度程序,能够出于效率原因使用查找表调用具有不同签名的函数(使用枚举类值进行模板化)。没有提升::任何内部使用新的

[edit2]不允许使用宏

1 个答案:

答案 0 :(得分:1)

关键问题是通过直接获取调用参数类型并尝试转换函数指针,您将失去所有隐式转换。

您的功能签名必须完全匹配,否则如果您尝试调用它,您将获得UB。并且通常无法从args获取签名而无需在呼叫站点手动指定它。

尝试的一种解决方法是添加一个包装器lambda,它采用带有预先指定的隐式覆盖的标准化args,例如: T -> const T&,可能还有numeric types -> double

然后,当你查找函数时,可以将其强制转换为使用这些标准化的args,并且将隐式转换调用args。

这将排除使用rvalue refs和非const引用的函数,但我不认为这对于你不知道签名的函数是不合理的,除非你想完全忽略const-correctness。 / p>

此外,其他隐式转换也不会发生,例如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;
}