c++模板超程式設計:std::invoke原始碼分析及其實現
阿新 • • 發佈:2018-11-23
在實現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的更簡單的實現,感興趣的可以去看,這裡就不再囉嗦了。