functor map

之前有写过一个简单的信号和槽的实现,但这个实现的缺点是很明显的,不同类型的信号需要不同的实例。

今天,重新想了一下,尝试写了些实现,由于C++不支持反射,没办法在运行时动态的生成可调用对象,所以,比如下面的做法是不可能的:

Functor functor;
functor.bind("add", [](int lhs, int rhs) -> int { return lhs + rhs; });
functor.bind("concat", [](const std::string& lhs, const std::string& rhs) -> std::string { return lhs + rhs; });

std::cout << functor.call("add", 1, 2) << '\n'; // print 3
std::cout << functor.call("concat", "hello ", "world") << '\n'; // print "hello world"

虽然知道不可能像上面代码那样,但是可以通过类型擦除,尽量做到和上面的类似。所以下面是具体的实现:

namespace nm
{
  class FunctorMap
  {
    public:
      template<typename> struct Inspector;
      template<typename R, typename... Args>
      struct Inspector<R(Args...)>
      {
        using arg_t = std::tuple<Args...>;
        using res_t = R;
      };
      template<typename R, typename... Args>
      struct Inspector<R(*)(Args...)> : Inspector<R(Args...)> {};
      template<typename R, typename Object, typename... Args>
      struct Inspector<R(Object::*)(Args...)> : Inspector<R(Args...)> {};
      template<typename R, typename Object, typename... Args>
      struct Inspector<R(Object::*)(Args...) const> : Inspector<R(Args...)> {};
      template<typename R, typename Object, typename... Args>
      struct Inspector<R(Object::*)(Args...) volatile> : Inspector<R(Args...)> {};
      template<typename R, typename Object, typename... Args>
      struct Inspector<R(Object::*)(Args...) const volatile> : Inspector<R(Args...)> {};
      // functor like
      template<typename Lambda>
      struct Inspector : Inspector<decltype(&Lambda::operator())> {};
      template<typename Lambda>
      struct Inspector<Lambda&> : Inspector<decltype(&Lambda::operator())> {};
      template<typename Lambda>
      struct Inspector<Lambda&&> : Inspector<Lambda&> {};

    public:
      using Fp = std::function<void(void*, void*)>;

      FunctorMap() = default;

      template<typename F>
      void bind(const std::string& sig, F&& f)
      {
        auto fp = [f = std::forward<F>(f)](void* args, void* res) {
          using arg_t = typename Inspector<F>::arg_t;
          using res_t = typename Inspector<F>::res_t;
          using indices = std::make_index_sequence<std::tuple_size<arg_t>::value>;
          arg_t& arg = *static_cast<arg_t*>(args);
          *static_cast<res_t*>(res) = invoke<res_t, arg_t>(f, arg, indices{});
        };
        functors_[sig] = std::move(fp);
      }

      template<typename R, typename... Args>
      R call(const std::string& sig, Args&&... args)
      {
        auto params = std::make_tuple(std::forward<Args>(args)...);
        auto& fp = functors_[sig];
        R res;
        fp(&params, &res);
        return res;
      }

    private:
      std::map<std::string, Fp> functors_;
      
      template<typename R, typename TupleType, typename F, size_t... Is>
      static R invoke(F&& f, TupleType& args, std::index_sequence<Is...>)
      {
        return std::forward<F>(f)(std::forward<std::tuple_element_t<Is, std::remove_reference_t<TupleType>>>(std::get<Is>(args))...);
      }
  };
}

代码中的Inspector直接取自variant中的extract,针对可变模板参数稍作修改。

用一个map存放函数id和擦除类型后的functor。通过模板参数指定call的返回值,那么调用可以这样:

functor_mapping 非常幸运,这段代码是可以编译通过并得到正确结果的。

接着,尝试一个字符串拼接 concat_string 如你所见,失败了,并且抛出了一个异常,而且这个异常是由new产生的。

注意到,在绑定functor的时候的这段代码:

arg_t& arg = *static_cast<arg_t*>(args);

对于绑定为const std::string&调用为const char*的这种情况,这里的args的真实类型是

tuple<const char*, const char*>

arg_t则是

tuple<const std::string&, const std::string&>

这里强行将前一个转换为后一个,如果直接赋值的话,会经过构造函数,就不会出现问题。而这里,完全不知道void* argsargs的真实类型(根据call的传入参数决定)。

所以,为了避免发生错误,可以要求用户使用绑定时的类型进行调用,同时还需要修改代码

// change 
arg_t = tuple<Args...>;
// to 
arg_t = tuple<remove_cv_t<remove_reference_t<Args>>...>;

至少需要删除constvolatile
再次尝试运行
concat_string1 如你所见,正常了。

突然Déjà vu了,这个坑似乎之前就遇到过。。。

你也许会认为这样刻意模仿第一段代码的行为有什么意义,而且还加了各种限制。如果你细心的话会发现,这是个rpc项目中的例子,所以并不会有call<int>("add", 1, 2)这种需要返回值的情况,并且调用所需的参数都是传输数据结构(Json、MsgPack等)反序列化出来的,也就是说,反序列化后的字符串也不会是const char*

现有问题:
- 如果不需要返回值怎么办? (tag dispatch)
- 其它类型的functor呢?
- 没找到bind的functor怎么办?
- 传入参数是引用呢? (不支持引用,但你可以使用指针)
- 调用时的参数个数或者类型与bind时不一致会出现未定义行为 (2019-01-08增加,待解) 解法源于这里

functor_map.h