在實現invoke之前,我們先看一下標准庫種invoke的使用方式
template< class F, class... Args>
std::invoke_result_t<F, Args...> invoke(F&& f, Args&&... args) noexcept(/* see below */);
(C++17 起)
// 摘自cppreference
#include <functional> #include <iostream> struct Foo { Foo(int num) : num_(num) {} void print_add(int i) const { std::cout << num_+i << '\n'; } int num_; }; void print_num(int i) { std::cout << i << '\n'; } struct PrintNum { void operator()(int i) const { std::cout << i << '\n'; } }; int main() { // 調用自由函數 std::invoke(print_num, -9); // 調用 lambda std::invoke([]() { print_num(42); }); // 調用成員函數 const Foo foo(314159); std::invoke(&Foo::print_add, foo, 1); // 調用(訪問)數據成員 std::cout << "num_: " << std::invoke(&Foo::num_, foo) << '\n'; // 調用函數對象 std::invoke(PrintNum(), 18); }
標准庫的invoke函數,可以支持普通函數,成員函數,訪問數據成員,lambda
#include <iostream> #include <functional> // ======================================================================== // 第三步 // 調用普通函數的版本 struct _InvokeFunction { template<typename _Callable, typename... _Types> static auto _Call(_Callable&& obj, _Types&&... argv) { return obj(std::forward<_Types>(argv)...); } }; // 調用成員函數版本 struct _InvokeMemFunc { template<typename _Callable, typename _Obj, typename... _Types> static auto _Call(_Callable&& fn, _Obj&& obj, _Types&&... argv) -> decltype((obj->*fn)(std::forward<_Types>(argv)...)) { return (obj->*fn)(std::forward<_Types>(argv)...); } // 這里和stl當中方法不一樣,這里采用使用SFINAE技術 // 編譯器會自動選擇兩者當中可調用的版本 template<typename _Callable, typename _Obj, typename... _Types> static auto _Call(_Callable&& fn, _Obj&& obj, _Types&&... argv) -> decltype((obj.*fn)(std::forward<_Types>(argv)...)) { return (obj.*fn)(std::forward<_Types>(argv)...); } }; // 調用成員變量 struct _InvokeMemObj { template<typename _Callable, typename _Obj> static auto _Call(_Callable&& fn, _Obj&& obj) -> decltype((obj->*fn)) { return (obj->*fn); } template<typename _Callable, typename _Obj> static auto _Call(_Callable&& fn, _Obj&& obj) -> decltype((obj.*fn)) { return (obj.*fn); } }; // ========================================================================= // 第二步 // 第二層,篩選多參數普通函數,成員函數,數據成員 // 暫時依賴標准庫的萃取技術 template<typename _Callable, typename _FirstTy, typename _Decayed = typename std::decay<_Callable>::type, bool _Is_MemFun = std::is_member_function_pointer<_Decayed>::value, bool _Is_MemObj = std::is_member_object_pointer<_Decayed>::value> struct _Invoke1; // 成員函數,標准庫當中傳遞 // _FirstTy的作用是用來判斷 _Callable的Class是否是_FirstTy的Class或者Parent Class // 這里為了簡化不再判斷 template<typename _Callable, typename _FirstTy, typename _Decayed> struct _Invoke1<_Callable, _FirstTy, _Decayed, true, false> : _InvokeMemFunc { }; // 成員變量 template<typename _Callable, typename _FirstTy, typename _Decayed> struct _Invoke1<_Callable, _FirstTy, _Decayed, false, true> : _InvokeMemObj { }; // 普通函數 template<typename _Callable, typename _FirstTy, typename _Decayed> struct _Invoke1<_Callable, _FirstTy, _Decayed, false, false> : _InvokeFunction { }; // ========================================================================= // 第一步 // 本層先把無參數的直接篩選出來了 template<typename _Callable, typename... _Types> struct _Invoke; // 無參數,必定是一個普通函數 template<typename _Callable> struct _Invoke<_Callable> : _InvokeFunction { }; // 有一個或多個參數,可能是普通函數,成員函數,數據成員 template<typename _Callable, typename _FirstTy, typename... _Types> struct _Invoke<_Callable, _FirstTy, _Types...> : _Invoke1<_Callable, _FirstTy> { }; // 通過Invoke函數進行一層封裝,使其使用更加貼合實際 template<typename _Callable, typename... _Types> auto Invoke(_Callable&& obj, _Types&&... argv) { return _Invoke<_Callable, _Types...>::_Call( std::forward<_Callable>(obj), std::forward<_Types>(argv)...); } // ======================================================================== // 測試代碼 void sum(int a, int b, int c) { std::cout << a + b + c<< std::endl; } struct Foo { Foo(int num) : num_(num) {} void print_add(int i) const { std::cout << num_ + i << '\n'; } int num_; }; int main() { Foo foo(123); Invoke(sum, 1, 2, 3); Invoke([=]() {}); Invoke(&Foo::print_add, foo, 1); Invoke(&Foo::print_add, &foo, 1); auto n = Invoke(&Foo::num_, &foo); auto n1 = Invoke(&Foo::num_, foo); return 0; }
至此一個簡單的invoke就實現完成了,cppreference上面有基於c++17的更簡單的實現,感興趣的可以去看,這里就不再啰嗦了。