2015-06-29 47 views
1

這是代碼:SFINAE模板參數(enable_if)不變量符

#include <iostream> 
#include <type_traits> 


template <class T> 
typename std::enable_if<std::is_integral<T>::value,bool>::type 
    is_odd (T i) {return bool(i%2);} 

// 2. the second template argument is only valid if T is an integral type: 
template < class T, 
      class = typename std::enable_if<std::is_integral<T>::value>::type> 
bool is_even (T i) {return !bool(i%2);} 


int main() { 

    short int i = 1; // code does not compile if type of i is not integral 

    std::cout << std::boolalpha; 
    std::cout << "i is odd: " << is_odd(i) << std::endl; 
    std::cout << "i is even: " << is_even(i) << std::endl; 

    return 0; 
} 

我努力學習的enable_if正確使用,如果它用作返回類型說明我不解的是:編譯會忽略代碼。意思是,函數不會在二進制文件中。

如果它在模板參數中使用,我有點困惑。根據上面的代碼,它說有the second template argument is only valid if T is an integral type但我很困惑第二個參數的目的是什麼?

我刪除了它,並把它改爲:

template < class T> 
bool is_even (T i) {return !bool(i%2);} 

,它仍然能正常工作。有人能澄清我的真正目的是什麼?它上面也沒有變量說明符。

或者,也許它只是作爲一個檢查,如果生病做類似

template < class T, 
      class B= typename std::enable_if<std::is_integral<T>::value>::type> 

讓我在我的代碼中訪問B(可以是真或假的)?

回答

1

在這種情況下,enable_if的用途是在T的演繹模板參數不是整數類型時導致編譯錯誤。我們可以從cppreference/is_integral看到,整型是整數,字符及其有符號和無符號變體。

對於任何其他類型的你會得到看起來像一個錯誤:

main.cpp:21:32: error: no matching function for call to 'is_odd' 
    std::cout << "i is odd: " << is_odd(NotIntegral()) << std::endl; 
           ^~~~~~ 
main.cpp:6:25: note: candidate template ignored: disabled by 'enable_if' [with T = NotIntegral] 
typename std::enable_if<std::is_integral<T>::value,bool>::type 

我明白,如果它是用來作爲返回類型說明符是編譯器會忽略代碼

這是不正確的。返回類型的評估就像聲明的其他部分一樣。請參閱Why should I avoid std::enable_if in function signaturesstd::enable_if的選擇有其優點和缺點。

但我很困惑第二個參數的目的是什麼?

考慮一個例子,其中您有一個名爲foo的函數,它需要一些T

template<class T> void foo(T); 

你想限制其fooT「一個過載■找一個value成員等於1,並且想用另外的過載時T::value不等於1。如果我們簡單地這樣做:

template<class T> void foo(T); // overload for T::value == 1 
template<class T> void foo(T); // overload for T::value != 1 

這是如何傳遞給編譯器的,你希望對兩個單獨的事情使用兩個單獨的重載?它沒有。他們都是模棱兩可的函數調用:

template<std::size_t N> 
struct Widget : std::integral_constant<std::size_t, N> { }; 

int main() { 
    Widget<1> w1; 
    Widget<2> w2; 

    foo(w1); // Error! Ambiguous! 
    foo(w2); // Error! Ambiguous! 
} 

您將需要使用SFINAE根據我們的條件,拒絕模板:

template<class T, class = std::enable_if_t<T::value == 1>* = nullptr> 
void foo(T); // #1 

template<class T, class = std::enable_if_t<T::value != 1>* = nullptr> 
void foo(T); // #2 

現在正確的人被稱爲:

foo(w1); // OK! Chooses #1 
foo(w2); // OK! Chooses #2 

什麼使這項工作是std::enable_if的方式。如果第一個參數中的條件爲真,它將提供一個名爲type的成員typedef等於第二個參數(默認爲void)。否則,如果條件是錯誤的,它不會提供一個。一個合理的實現可能是:

template<bool, class R = void> 
struct enable_if { using type = R; }; 
template<class R> 
struct enable_if<false, R> { /* empty */ }; 

所以,如果條件不滿足,我們將試圖訪問一個::type成員不存在(請記住,std::enable_if_t是一個別名std::enable_if<...>::type)。代碼會形成錯誤,但不會導致嚴重錯誤,在重載解析期間,模板會被候選集拒絕,並且會使用其他重載(如果存在)。

這是SFINAE的簡單解釋。有關更多信息,請參閱cppreference page

在你的情況下,存在的is_oddis_even只有一個過載,因此,如果你通過NotIntegral給他們,他們將會失敗,因爲它們各自的重載取出的候選集,並因爲沒有更多的重載評價過載分辨率失敗,出現上述錯誤。相反,這可以用static_assert消息清理一下。

template<class T> 
bool is_even (T i) { 
    static_assert(std::is_integral<T>::value, "T must be an integral type"); 
    return !bool(i%2); 
} 

現在你不會再給出一個奇怪的看錯誤消息,但你自己定製的。這也排除了SFINAE,因爲static_assert是在模板參數被替換後進行評估的,因此您可以選擇採用哪條路徑。我會推薦這個斷言,因爲它更清晰,並且這裏不需要SFINAE。

+0

'class = std :: enable_if_t * = nullptr'我從此代碼中獲得「template argument1 is invalid」 –

+0

@CarloBrew刪除'class ='部分 – 0x499602D2

+0

爲什麼它必須是一類?我看到,我注意到的一件事就是。像這樣的事件是否意味着用在'consexpr'上? –

2

您正在使用它與整數類型。只有在使用非整數類型時纔會失敗。以下應無法編譯:

float f; 
is_even(f); 

注意,用C++編譯器14你can write

template <class T, 
      class = std::enable_if_t<std::is_integral<T>::value>> 

但是,您可能要在這裏使用static_assert,因爲功能可能不對於非整型類型是有意義的,並且它會在編譯時給出更好的錯誤消息。

SFINAE中額外參數的另一個用法是降低給定函數的偏好。具有更多模板參數的模板比使用較少模板參數的模板更不容易被選中。

+0

儘管我仍然沒有達到'B類= typename std :: enable_if :: value> :: type'的目的,它是否會影響第二個參數?或整個功能? –

+0

不知道'enable_if_t' - 在眼睛上更容易。 – kfsone

相關問題