std::invoke_result的實現詳解
目錄
前言
本篇博文將詳細介紹一下libstdc++
中std::invoke_result
的實現過程,由於個人水平不足,可能最終的實現過程略有誤差,還請各位指正。
invoke_result
std::invoke_result
是C++17標準庫裡面的一個介面,它可以返回任何可調用的返回值,比如函數,函數對象,對象方法等。它的介面為[1]
template< class F, class... ArgTypes>
class invoke_result;
在C++17之前,std::invoke_result
有一個類似功能的模板,名字為std::result_of
,他們之間的差別只有在介面上有略微的差別,
template< class >
class result_of; // not defined
template< class F, class... ArgTypes >
class result_of<F(ArgTypes...)>;
std::result_of
於C++17標記為deprecated
,並在C++20中被移除。
標準庫中的invoke_result
我們首先看看一下標準庫中std::invoke_result
的實現,了解一下其大致的結構。
invoke_result[std::invoke_result];
result_of[std::result_of];
__invoke_result[std::__invoke_result];
invoke_result –> __invoke_result;
result_of –> __invoke_result;
__result_of_impl[std::__result_of_impl];
__invoke_result –> __result_of_impl;
__result_of_memobj[std::__result_of_memobj];
__result_of_memfun[std::__result_of_memfun];
__result_of_other_impl[std::__result_of_other_impl];
__result_of_impl –> __result_of_memobj;
__result_of_impl –> __result_of_memfun;
__result_of_impl –> __result_of_other_impl;
__S_test[“__S_test()”];
__result_of_other_impl –> __S_test;
__result_of_memobj_ref[std::__result_of_memobj_ref];
__result_of_memobj_deref[std::__result_of_memobj_deref];
__result_of_memobj –> __result_of_memobj_ref;
__result_of_memobj –> __result_of_memobj_deref;
__result_of_memfun_ref[std::__result_of_memfun_deref];
__result_of_memfun_deref[std::__result_of_memfun_deref];
__result_of_memfun –> __result_of_memfun_ref;
__result_of_memfun –> __result_of_memfun_deref;
__result_of_memfun_ref_impl[std::__result_of_memfun_ref_impl];
__result_of_memfun_deref_impl[std::__result_of_memfun_deref_impl];
__result_of_memobj_ref_impl[std::__result_of_memobj_ref_impl];
__result_of_memobj_deref_impl[std::__result_of_memobj_deref_impl];
__result_of_memfun_ref –> __result_of_memfun_ref_impl;
__result_of_memfun_deref –> __result_of_memfun_deref_impl;
__result_of_memobj_ref –> __result_of_memobj_ref_impl;
__result_of_memobj_deref –> __result_of_memobj_deref_impl;
__result_of_memfun_ref_impl –> __S_test;
__result_of_memfun_deref_impl –> __S_test;
__result_of_memobj_ref_impl –> __S_test;
__result_of_memobj_deref_impl –> __S_test;
可以看到,整個實現有兩層判斷(有兩個判斷是類似的,可以看作是一層),其中第一個層位於std::__result_of_impl
,用來判斷不同的調用方法,大致分為三種:
- 對象方法,比如以下程式碼中的
struct A
的invoke_mem_fun
,調用方法為obj.fun(args...)
。
struct A {
int invoke_mem_fun();
};
- 對象成員,比如以下程式碼中的
struct B
中的invoke_mem_obj
,調用方法為obj.fun
(其實就是一個簡單的引用成員)。
struct B {
int invoke_mem_obj;
};
- 其他,比如以下程式碼中的
invoke_other
,其調用方法為fun(args...)
,這一部分包括了函數對象和普通函數。
int invoke_other();
那麼std::__result_of_impl
是如何區分出了這三種不同的調用方式呢?
libstdc++
中實現了兩個類型判斷,is_member_object_pointer
和is_member_function_pointer
。通過這兩個判斷不同的輸出,來區分三種不同的調用方式。
這兩個函數的結構差不多,首先會對判斷輸入的類型是否為對象的成員,判斷方法為進行類似下面程式碼的模式匹配
template<typename>
struct __mem_and_obj_type: public std::__failure_type{};
template<typename _Tp, typename _Cp>
struct __mem_and_obj_type<_Tp _Cp::*> {
typedef _Tp mem_type;
typedef _Cp obj_type;
};
這一部分程式碼是從我實現的部分截取出來的,其中最主要的部分就是_Tp _Cp::*
,他分開成員類型和對象類型,如果能分開則為對象的成員。
第二步則是判斷上一步分離得到的成員類型是否為函數類型,如果是函數類型,那麼其為對象方法,否則為對象成員。
由此,通過那兩個類型的判斷,可以有三種不同的結果,true,false
,false,true
,false,false
,不同的結果對應著不同的調用方法。看到這裡,肯定會有人思考,有沒有可能結果都是true
呢?
首先從libstdc++
實現上這是不可能的,因為在實現第二步的時候,is_member_object_pointer
幾乎是直接對is_member_function_pointer
取反的(實現上取反)。所以,不可能同時出現兩個都為true
的情況。
而實際中,卻的確有可能出現這種情況(我也不知道是不是我那裡弄錯了,先寫下來吧)
struct STest {
double operator() (int, double) {
std::cout << "Test" << std::endl;
return 0;
}
};
struct CTest {
struct STest s;
int s_m(int, double){};
};
這種情況下,如果我們想獲得CTest::s
的返回值,是有一個很奇怪的情況的,我們是把它當作函數呢,還是一個成員呢?如果從調用的情況來看,CTest::s
和CTest::s_m
一樣,都是obj.fun(args...)
,但是實際中,std::invoke_result
會將它視為對象成員。所以如果我們使用std::invoke_result<decltype(&CTest::s), CTest, int, double>::type
是會報錯的,正確的方法是只能使用std::invoke_result<decltype(&CTest::s), CTest>::type
。由於沒有去查看文獻,所以不清楚這一部分是bug還是feature或者是UB,有時間再詳細考究一下吧。
好了,目前已經詳細說完了std::invoke_result
的第一層判斷,接下來我們來看看第二層判斷。
第二層判斷有兩個部分,我們只看其中的一個部分,因為實際上這兩個部分都是一樣的。
這一層的判斷位於對象成員和對象方法部分。即區分std::__result_of_memfun_deref
和std::__result_of_memfun_ref
。這兩個的不同之處在於,前者的對象為指針。區分的方法也很簡單粗暴,直接判斷是否參數裡面的對象類型與上文中使用_Tp _Cp::*
獲得的對象類型_Cp
是否一致或者為繼承關係,如果為是,則使用std::__result_of_memfun_ref
否則為std::__result_of_memfun_deref
。
通過上面兩層的判斷,已經成功的將不同的調用方法進行了分類,接下來就是使用decltype
來獲取返回值了。這一部分就很簡單了,由上面的幾次分類,分為了五種不同的調用(加上不同的對象)。
__invoke_memfun_ref
(std::declval<_Tp1>().*std::declval<_Fp>())(std::declval<_Args>()...)
__invoke_memfun_deref
((*std::declval<_Tp1>()).*std::declval<_Fp>())(std::declval<_Args>()...)
3. `__invoke_memobj_ref`
```C++
std::declval<_Tp1>().*std::declval<_Fp>()
__invoke_memobj_deref
(*std::declval<_Tp1>()).*std::declval<_Fp>()
__invoke_other
std::declval<_Fn>()(std::declval<_Args>()...)
但是比較好奇的是,程式沒有直接通過上面的方法獲得,而是構造了一個return_type _S_test(int)
,然後獲取的。除了返回的類型外,std::invoke_result
還保存了調用的類型,即之前提到的那五種,這一部分應該是為了實現std::invoke
而保存的。
我的實現
為了學習std::invoke_result
,我也實現了一個類似的ink::invoke_result
。層次結構類似,不過將_S_test()
刪去了,改成了直接的實現。
github gist: ink_invoke_result.cpp
後記
閱讀標準庫裡面的實現的確可以學到很多東西,即使以後不會寫類似的程式碼,但是在使用的時候也會對C++有更加清晰的理解。同時也發現了C++目前的一個缺陷,在實現這種介面的時候,免不了要進行多層包裝,而每一層的包裝,標準庫都是直接將其暴露在std
命名空間中,這導致了在使用程式碼提示的時候極大的降低了提示的效率和美觀(一大堆的以下劃線開頭的item真的非常非常非常難看,而且頭大)。
部落格原文: //www.cnblogs.com/ink19/p/cpp_invoke_result.html