,今天在研究RxCpp的代码时,发现了下面的函数on_exception:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 |
template<class F, class OnError> auto on_exception(const F& f, const OnError& c) -> typename std::enable_if<detail::is_on_error::value, typename detail::maybe_from_result::type>::type { typename detail::maybe_from_result::type r; try { r.reset(f()); } catch (...) { c(std::current_exception()); } return r; } ... ... template<class F> struct is_on_error { struct not_void {}; template<class CF> static auto check(int) -> decltype((*(CF*)nullptr)(*(std::exception_ptr*)nullptr)); template<class CF> static not_void check(...); static const bool value = std::is_same<decltype(check<rxu::decay_t<F>>(0)), void>::value; }; ... ... template<class T> using decay_t = typename std::decay<T>::type; template<class F> struct maybe_from_result { typedef decltype((*(F*)nullptr)()) decl_result_type; typedef rxu::decay_t<decl_result_type> result_type; typedef rxu::maybe<result_type> type; }; /*maybe可以简单理解为一个可以存null/一个T类型元素 的模板容器,利用reset(T)进行赋值*/ |
用途是运行函数f,当出现错误时使用c进行错误处理,相当于直接写下面的try…catch段.
这里有两个问题:
1.它是如何检验onError是它需要的错误处理函数的?
2.为什么要使用模板检验onError的类型,而不是直接在参数处限定类型呢?
对于问题1,它是通过函数返回值的类型推导,来对 onError 的类型进行限定的.首先从下面的catch代码,可以发现 onError 需要的函数格式为 void(std::exception_ptr) .在上面的代码中,唯一与这个格式相关的,是 is_on_error::check 函数.这个函数有两个定义,一个是 int 参数的定义,另一个是任意参数的定义. int 参数的定义通过 decltype 推导出了该函数在通常调用时的返回值(同时检验了参数是否吻合),之后再通过 std::is_same<T,U> 检验返回值是否为 void ,得出结果.根据C++模板推导的特性,如果带入后推导出的函数的类型是无效的,就放弃这种偏特化方式(尝试其他的方式,如果没有则报错,单纯一种推导失败并不会报错),当int的定义推导失败时,就会采用第二种定义,其返回值显然不是 void , is_on_error::value 显然也为 false 了.
在检验之后,又通过 std::enable_if<B,T> ,当第一个模板参数为false时会输出无效类型, 使函数返回值的有效性与检验的结果连接在了一起.
而问题2才是使用这种方法获得的主要好处,1只是实现的手段之一.该函数通过使用模板机制,使得参数可以接受的函数类型增加了(无论是对于t,还是c).这个效果主要分为两个方面:
这个函数可以将形如 int(int) 的函数类型,转换为 int(*)(int) ,后者则不做任何改变.同时可以移除输入类型的&,&&, const 等信息,方便后续比较.通过 std::decay<T> ,将这个函数运行的输入扩展了一种.
(2) decltype 推导
1 |
static auto check(int) -> decltype((*(CF*)nullptr)(*(std::exception_ptr*)nullptr)); |
可以注意到,上面通过对CF类型的函数调用,来获取到函数的返回值类型,而能通过()形式进行调用的,并不是只有函数的,还有 operator() .也就是说可以接受所有拥有 operator()(std::exception_ptr) 重载的对象!举一个比较普遍的例子,这个函数可以接受 std::function<R(Args…)> 对象, 也就是说可以方便地使用 std::bind<R(Args…)> 包装出函数了.
以上是今天对RxCpp库的研究记录,如果有什么错误,或疑问,欢迎评论进行讨论.
*PS:差点被”明明参数限定类型就好了为什么要用模板呢”问倒T T
*PS2:那么 is_on_error::check 的调用算不算重载呢…?