不带变量说明符的模板参数上的SFINAE(enable_if)
SFINAE(enable_if) on template arguments without variable specifier
这是代码:
#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(可以是真的也可以是假的)?
您正在将它与整型一起使用。它只有在与非积分类型一起使用时才会失败。以下应该无法编译:
float f;
is_even(f);
请注意,使用C++14编译器,您可以编写:
template <class T,
class = std::enable_if_t<std::is_integral<T>::value>>
然而,您可能希望在这里使用static_assert,因为该函数对于非积分类型可能没有意义,并且它在编译时会给出更好的错误消息。
SFINAE中附加参数的另一个用途是降低对给定函数的偏好。与模板参数较少的模板相比,模板参数较多的模板不太可能被选中。
在这种情况下,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
我知道如果它被用作返回类型说明符,编译器将忽略代码
这不是真的。返回类型的计算方式与声明的任何其他部分一样。请参阅为什么我应该在函数签名中避免std::enable_if。std::enable_if
的位置选择有其优缺点。
但我很困惑第二个论点的目的是什么?
考虑以下示例,其中有一个名为foo
的函数,它需要一些T
。
template<class T> void foo(T);
您希望限制foo
的一个重载,其T
的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
的构建方式使它发挥了作用。如果其第一个参数中的条件为true,则它将提供一个名为type
的成员typedef,该成员与第二个参数(默认为void
)相等。否则,如果条件为false,则不提供条件。一个合理的实施方式可以是:
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页面。
在您的情况下,is_odd
和is_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。
- 如何将enable-if与模板参数和参数包一起使用
- 为什么使用SFINAE而不是函数重载
- 如何使用模板函数的函数签名进行SFINAE
- 数据成员SFINAE的C++17测试:gcc vs clang
- 使用在用于SFINAE的void_t中具有参数的方法
- 编译器如何在使用SFINAE的函数和标准函数之间确定两者是否可行
- 提供与TMP和SFINAE的通用接口
- "Inverse SFINAE"避免模棱两可的过载
- 表达式 SFINAE:如何根据类型是否包含具有一个或多个参数的函数来选择模板版本
- 检查一个类型是否直接派生自"enable if"上下文中的另一个类型(是其子类型)
- 如何在儿童类中使用SFINAE
- 使用 SFINAE 作为模板参数的编译时递归
- 使用 SFINAE 设计模板方法
- 与SFINAE支票交朋友
- C++许多 SFINAE 风格的过载
- 是否可以混合使用SFINAE和模板专业化?
- C++表达SFINAE和ostream操纵器
- SFINAE不能防止模棱两可的操作员过载吗?
- SFINAE是否取决于类型推断?
- SFINAE with boost enable if