SFINAE(enable_if)在没有变量说明符的模板参数上

时间:2015-06-29 22:26:19

标签: c++

这是代码:

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> 但我很困惑第二个论点的目的是什么?

我删除了它并将其更改为:

> bp
Source: local data frame [6 x 4]

        date amount accountId type
1 2015-06-11  101.2         1    a
2 2015-06-18  101.2         1    a
3 2015-06-24  101.2         1    b
4 2015-06-11  294.0         2    a
5 2015-06-18   48.0         2    a
6 2015-06-26   10.0         2    b

它仍然可以正常工作。有人可以澄清一下它的真正目的是什么?它上面也没有变量说明符。

或许它只是作为检查者,如果生病做

之类的事情
> nrow(bp)
[1] 3391874
>

允许我在我的代码上访问B(可以是真是假)?

2 个答案:

答案 0 :(得分: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中其他参数的另一种用法是降低给定函数的偏好。具有更多模板参数的模板比具有较少模板参数的模板更不可能被选中。

答案 1 :(得分: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 signatures。选择std::enable_if的位置有其优点和缺点。

  

但我很困惑第二个论点的目的是什么?

考虑一个示例,其中有一个名为foo的函数需要一些T

template<class T> void foo(T);

您希望限制foo T的{​​{1}}成员等于value的一个重载,并希望在{{1}时使用另一个重载}}不等于1。如果我们只是这样:

T::value

这如何向编译器传达您想要为两个单独的事物使用两个单独的重载?它没有。它们都是模糊的函数调用:

1

您需要使用SFINAE根据我们的条件拒绝模板:

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!
}

使这项工作成为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 的成员typedef等于第二个参数(默认为std::enable_if)。否则,如果条件为假,则不提供条件。合理的实施可能是:

type

因此,如果条件失败,我们将尝试访问不存在的void成员(请记住 template<bool, class R = void> struct enable_if { using type = R; }; template<class R> struct enable_if<false, R> { /* empty */ }; ::type的别名)。代码将是格式错误的,但是在重载解析期间,模板被简单地从候选集中拒绝而不是导致硬错误,而是使用其他重载(如果存在)。

这是对SFINAE的简单解释。有关详细信息,请参阅cppreference page

在您的情况下,std::enable_if_tstd::enable_if<...>::type只有一个重载,因此如果您将is_odd传递给它们,它们将会失败,因为它们各自的重载都会从候选集中取出,并且由于没有更多的重载来评估过载分辨率因上述错误而失败。可以使用is_even消息稍微清理一下。

NotIntegral

现在,您将不会再看到一个奇怪的错误消息,而是您自己的自定义错误消息。这也排除了SFINAE,因为在替换模板参数后评估static_assert,因此您可以选择采用哪条路径。我会推荐这个断言,因为它更干净,而且这里不需要SFINAE。