std::invoke与自己实现模板比较

一、自定义模板函数

在前面分析了自定义一个模板函数,用来实现类似JAVA探针的形式。但是在文末给了一个小问题,就是如果这个模板参数是一个类成员函数该怎么办?本来不急于想做这个分析,但后来在看STL中的std::invoke的源码时,发现二者有相通之处,便结合其源码,一起分析一下。
那么如果Fn是一个类成员函数应该怎么处理呢?这种解决方式有两种,一种是使用刚刚提到的std::invoke的内部处理方式;另外一种就可以通过使用std::function来绑定(std::bind)一个类成员函数,然后 再调用自定义模板函数即可。

二、std::invoke

先看一下std::invoke的源码实现:

 namespace detail
{
    template<class>
    constexpr bool is_reference_wrapper_v = false;
    template<class U>
    constexpr bool is_reference_wrapper_v<std::reference_wrapper<U>> = true;

    template<class C, class Pointed, class T1, class... Args>
    constexpr decltype(auto) invoke_memptr(Pointed C::* f, T1&& t1, Args&&... args)
    {
        if constexpr (std::is_function_v<Pointed>)
        {
            if constexpr (std::is_base_of_v<C, std::decay_t<T1>>)
                return (std::forward<T1>(t1).*f)(std::forward<Args>(args)...);
            else if constexpr (is_reference_wrapper_v<std::decay_t<T1>>)
                return (t1.get().*f)(std::forward<Args>(args)...);
            else
                return ((*std::forward<T1>(t1)).*f)(std::forward<Args>(args)...);
        }
        else
        {
            static_assert(std::is_object_v<Pointed> && sizeof...(args) == 0);
            if constexpr (std::is_base_of_v<C, std::decay_t<T1>>)
                return std::forward<T1>(t1).*f;
            else if constexpr (is_reference_wrapper_v<std::decay_t<T1>>)
                return t1.get().*f;
            else
                return (*std::forward<T1>(t1)).*f;
        }
    }
} // namespace detail

template<class F, class... Args>
constexpr std::invoke_result_t<F, Args...> invoke(F&& f, Args&&... args)
    noexcept(std::is_nothrow_invocable_v<F, Args...>)
{
    if constexpr (std::is_member_pointer_v<std::decay_t<F>>)
        return detail::invoke_memptr(f, std::forward<Args>(args)...);
    else
        return std::forward<F>(f)(std::forward<Args>(args)...);
}

这段代码其实是从STL的实现中进行了精简的,在STL中,__invoke_impl的版本有好几个,涉及到普通函数、成员函数及其它几种相关的调用。
重点来看一下其对类成员函数的处理:

      if constexpr (std::is_base_of_v<C, std::decay_t<T1>>)
          return (std::forward<T1>(t1).*f)(std::forward<Args>(args)...);
     else if constexpr (is_reference_wrapper_v<std::decay_t<T1>>)
          return (t1.get().*f)(std::forward<Args>(args)...);
      else
          return ((*std::forward<T1>(t1)).*f)(std::forward<Args>(args)...);

首先判断是否是C的子类(本身也算子类),如果不是则判断是否为T1的is_reference_wrapper_v对象,最后如果都不是直接转发。回头再看看自己定义的模板,是不是少很多呢。这个其实不重要,毕竟又不是写库。重点是对类成员变量函数的处理std::is_member_pointer_v及相关的几个元编程定义的相关接口。

三、自定义模板函数的绑定处理

这里仍然用前面的例子,前面用std::bind的方式来处理一下类内部成员函数:

template <typename Fn, typename... Args> void TestTime1(Fn &f, Args... args) {
  f(std::forward<Args>(args)...);
}
int main() {
  std::function<void(int)> fn =
      std::bind(&Ex::GetData, ex, std::placeholders::_1);
  TestTime1(fn, 1);
  return 0;
}

其实,使用std::bind和std::function来处理一下Fn和std::invoke的处理机制,基本相似。但是这样会显得有点不通用,所以可以借用一下std::invoke的实现机制来处理一下自定义的模板函数:

template <class C, class Pointed, class T1, class... Args>
constexpr decltype(auto) invoke_memptr(Pointed C::*f, T1 &&t1, Args &&...args) {
 if constexpr (std::is_function_v<Pointed>) {
   if constexpr (std::is_base_of_v<C, std::decay_t<T1>>)
     return (std::forward<T1>(t1).*f)(std::forward<Args>(args)...);
 }
}
template <typename Fn, typename... Args> void TestTime1(Fn &&f, Args... args) {
 // f(std::forward(args)...);
 if constexpr (std::is_member_pointer_v<std::decay_t<Fn>>)  //1
   return invoke_memptr(f, std::forward<Args>(args)...);
 else  //2
   return std::forward<Fn>(f)(std::forward<Args>(args)...);
}

class Ex {
public:
 void GetData(int t) { std::cout << "this value:" << t + 1 << std::endl; }
};

Ex ex;

void GetTestData(int a, int b) {
 std::cout << "a,b value is:" << a << " " << b << std::endl;
}
int main() {
//call 2
 std::function<void(int)> fn =
     std::bind(&Ex::GetData, ex, std::placeholders::_1);
 TestTime1(fn, 1);
//call 2
 TestTime1(GetTestData, 'a', '1');
//call 1 ==>invoke_memptr
 TestTime1(&Ex::GetData, ex, 3);
 return 0;
}

这样,就基本可用了。更丰富的迭代可以根据自己的需要不断的添加和改写。

四、总结

要学会站在别人的肩膀上前进。遇到问题,除了要关于思考,更要关于借助外力。最重要的是,要把外力整合为内力。上学的时候不是学过,“君子性非异也,善假于物也”。共勉!

你可能感兴趣的:(C++11,C++,c++)