2015-06-29 97 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

相关问题