C++11 SFINEA规则,在C++模板中,有一条著名的规则,即SFINEA - Substitution failure is not an error
,中文直译即是“匹配失败不是错误”。更为确切地说,这条规则表示的是对重载的模板的参数进行展开的时候,如果展开导致了一些类型不匹配,编译器并不会报错。我们可以具体地看看代码清单3-52所示的来自wikipedia的例子。
代码清单3-52
struct Test {
typedef int foo;
};
template <typename T>
void f(typename T::foo) {} // 第一个模板定义 - #1
template <typename T>
void f(T) {} // 第二个模板定义 - #2
int main() {
f<Test>(10); // 调用#1.
f<int>(10); // 调用#2. 由于SFINEA,虽然不存在类型int::foo,也不会发生编译错误
}
在代码清单3-52中,我们重载了函数模板f的定义。第一个模板f接受的参数类型为T::foo
,这里我们通过typename来使编译器知道T::foo
是一个类型。而第二个模板定义则接受一个T类型的参数。在main函数中,分别使用f<Test>
和f<int>
对模板进行实例化的时候会发现,对于f<int>
来讲,虽然不存在int::foo
这样的类型,编译器依然不会报错,相反地,编译器会找到第二个模板定义并对其进行实例化。这样一来,就保证了编译的正确性。
事实上,通过上面的例子我们可以发现,SFINAE规则的作用比起其拗口的定义而言更为直观。基本上,这是一个使得C++模板推导规则符合程序员想象的规则。通过SFINAE,我们能够使得模板匹配更为“精确”,即使得一些模板函数、模板类在实例化时使用特殊的模板版本,而另外一些则使用通用的版本,这样就大大增加了模板设计使用上的灵活性。而在现实生活中,这样的使用方式在标准库中使用非常普遍(当你在标准库中发现一大堆的__enable_if
,或者应该想起这是SFINAE)。因此也可以说,SFINAE也是C++模板推导中非常基础的一个特性。
不过在C++98中,标准对于SFINAE并没有完全清晰的描述,一些在模板参数中使用表达式的情况,并没有被主流编译器支持。我们可以看看代码清单3-53所示的这个来自于C++11标准提案中的例子。
代码清单3-53
template <int I> struct A {};
char xxx(int);
char xxx(float);
template <class T> A<sizeof(xxx((T)0))> f(T){}
int main() {
f(1);
}
在代码清单3-53中,我们在定义函数模板f的时候,其返回值则定义为一个以sizeof(xxx((T)0))
为参数的类模板A。这里值得注意的是,我们使用了sizeof表达式,以及强制的类型转换。事实上,这样的表达式是可以在模板实例化时被计算出的。不过由于实现上的复杂性,以及标准中并未明确地提及,大多数C++98编译器都会报出一个SFINEA失败信息。而事实上,这样灵活的用法却非常有用,比如本例中,程序员可以根据参数的长度而定义出不同返回值的模板函数f(一种是sizeof((int)0)
,一种则是sizeof((float)0))
。如果编译器拒绝了这样的使用方式,无疑会为泛型编程的应用带来一些限制。
在C++11中,标准对这样的状况,尤其是模板参数替换时使用了表达式的情况进行了明确规定,即表达式中没有出现“外部于表达式本身”的元素,比如说发生一些模板的实例化,或者隐式地产生一些拷贝构造函数的话,这样的模板推导都不会产生SFINAE失败(即编译器报错)。这样一来,C++11中的一些新特性将能够成功地进行广泛的应用,进一步地,新的STL也将因此受益。
酷客教程相关文章:
评论前必须登录!
注册