在前面分析了自定义一个模板函数,用来实现类似JAVA探针的形式。但是在文末给了一个小问题,就是如果这个模板参数是一个类成员函数该怎么办?本来不急于想做这个分析,但后来在看STL中的std::invoke的源码时,发现二者有相通之处,便结合其源码,一起分析一下。
那么如果Fn是一个类成员函数应该怎么处理呢?这种解决方式有两种,一种是使用刚刚提到的std::invoke的内部处理方式;另外一种就可以通过使用std::function来绑定(std::bind)一个类成员函数,然后 再调用自定义模板函数即可。
先看一下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;
}
这样,就基本可用了。更丰富的迭代可以根据自己的需要不断的添加和改写。
要学会站在别人的肩膀上前进。遇到问题,除了要关于思考,更要关于借助外力。最重要的是,要把外力整合为内力。上学的时候不是学过,“君子性非异也,善假于物也”。共勉!