[英]How does the impementation for std::is_function in libcxx work?
[英]How does Eric Niebler's implementation of std::is_function work?
上周,Eric Niebler在推特上發布了一個非常緊湊的std::is_function
traits類實現:
#include <type_traits>
template<int I> struct priority_tag : priority_tag<I - 1> {};
template<> struct priority_tag<0> {};
// Function types here:
template<typename T>
char(&is_function_impl_(priority_tag<0>))[1];
// Array types here:
template<typename T, typename = decltype((*(T*)0)[0])>
char(&is_function_impl_(priority_tag<1>))[2];
// Anything that can be returned from a function here (including
// void and reference types):
template<typename T, typename = T(*)()>
char(&is_function_impl_(priority_tag<2>))[3];
// Classes and unions (including abstract types) here:
template<typename T, typename = int T::*>
char(&is_function_impl_(priority_tag<3>))[4];
template <typename T>
struct is_function
: std::integral_constant<bool, sizeof(is_function_impl_<T>(priority_tag<3>{})) == 1>
{};
但它是如何工作的?
而不是列出所有有效的函數類型,例如cpprefereence.com上的示例實現 ,此實現列出了所有非函數的類型,然后只有在沒有匹配的情況下才解析為true
。
非功能類型列表包括(從下到上):
void
和引用類型) 與任何非函數類型都不匹配的類型是函數類型。 請注意, std::is_function
明確地將可調用類型(如lambdas)或具有函數調用運算符的類視為不是函數。
is_function_impl_
我們為每個可能的非函數類型提供了一個is_function_impl
函數的重載。 函數聲明可能有點難以解析,所以讓我們將其分解為類和聯合案例:
template<typename T, typename = int T::*>
char(&is_function_impl_(priority_tag<3>))[4];
該行聲明了一個函數模板is_function_impl_
,它接受一個類型為priority_tag<3>
參數,並返回對4個char
的數組的引用。 按照C的古代慣例,聲明語法因陣列類型的存在而變得非常復雜。
此函數模板采用兩個模板參數。 第一個是無約束的T
,但第二個是指向int
類型的T
成員的指針。 這里的int
部分並不重要,即。 這甚至適用於沒有任何int
類型成員的T
它的作用是它會導致T
s的語法錯誤,而不是類或聯合類型。 對於其他類型,嘗試實例化函數模板將導致替換失敗。
類似的技巧用於priority_tag<2>
和priority_tag<1>
重載,它們使用它們的第二個模板參數來形成僅為T
s編譯的表達式,它們分別是有效函數返回類型或數組類型。 只有priority_tag<0>
重載沒有這樣的約束第二模板參數,因此可以用任何T
實例化。
總而言之,我們為is_function_impl_
聲明了四個不同的重載,它們的輸入參數和返回類型不同。 它們中的每一個都使用不同的priority_tag
類型作為參數,並返回對不同唯一大小的char數組的引用。
is_function
標記調度 現在,在實例化is_function
,它用T
實例化is_function_impl
。 請注意,由於我們為此函數提供了四種不同的重載,因此必須在此處進行重載解析。 由於所有這些重載都是功能模板 ,這意味着SFINAE有機會參與其中。
因此,對於函數(並且僅函數),除了具有priority_tag<0>
的最常規的重載之外,所有重載都將失敗。 那么為什么實例化不總是解決那個重載,如果它是最普遍的那個? 由於我們重載函數的輸入參數。
請注意, priority_tag
的構造方式是priority_tag<N+1>
公開繼承priority_tag<N>
。 現在,因為is_function_impl
在這里使用priority_tag<3>
調用,所以該重載比其他重載更好地匹配重載解析,因此首先嘗試它。 只有當由於替換錯誤而失敗時,才會嘗試下一個最佳匹配,即priority_tag<2>
重載。 我們以這種方式繼續,直到我們找到可以實例化的重載或者我們達到priority_tag<0>
,這不受約束並且將始終有效。 由於較高的prio重載涵蓋了所有非函數類型,因此只能對函數類型進行此類操作。
我們現在檢查調用is_function_impl_
返回的類型的大小以評估結果。 請記住,每個重載都會返回對不同大小的char數組的引用。 因此,我們可以使用sizeof
來檢查選擇了哪個重載,並且只有在達到priority_tag<0>
重載時才將結果設置為true
。
Johannes Schaub在實施中發現了一個錯誤 。 不完整類類型的數組將被錯誤地分類為函數。 這是因為數組類型的當前檢測機制不適用於不完整類型。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.