C++ Template1
SFINEA
in C++
SFINAE(substitution failure is not a error) 主要用于模板函数,它是指,编译器在使用具体类型来替换模板类型参数,对模板进行实例化(展开模板)时,如果发生替换失败,那么并不会直接引发编译错误(Error),而只是简单地把这个模板从重载候选者中去除掉。
还是看看代码吧(一个在SFINAE中常遇到的例子):
代码段1:
template <typenameT>
bool
is_class(int
T::*)
{
return
true;
}
template <typename
T>
bool
is_class(...)
{
return
false;
}
struct
Test
{
};
intmain(void)
{
std::cout<<is_class<Test>(0)<<endl;
std::cout<<is_class<int>(0)<<endl;
}
运行的结果是输出:
1
0
这表明,如果传给
is_class 的模板参数是一个类,那么返回 true 的那个版本就会被先中,否则false的那个版本会被选中。就是因为SFINAE在起作用。
为什么要提SFINAE?
仅仅从程序员的角度来看,程序段1中,对相应函数选择的结果是非常符合直观的预期,与普通函数重载是很相似的感觉。
例如,对于下面这两个函数:
int max(inta,
int
b)
{return
a>b?a:b}
float max(float
a,
float
b)
{returna>b?a:b}
int
main(void)
{
float
x1=3.4f,x2=3.6f;
cout<<max(x1, x2);
}
对于
float 型的参数,float版本的重载自然会很被选中。在外观上看,程序段1是一样的。那么为什么程序段1就需要特别的
SFNIAE 呢?
我想,对于普通函数的重载而言,由于这些函数的所有信息都已经完备,在发生调用之前,编译器已经可以完成对这些函数的编译,这些函数也不可能再被增加任何新的信息,可以直接产生执行代码。在函数的调用点上,编译器只需要根据参数信息选择一个合适函数的地址就可以了。
但是,对于模板函数重载,情况就不一样了。我们分析下程序段1中,is_class<int>(0)
这个调用,在第一步的选择中,无论从模板参数的个数、函数参数的个数来看,两个 is_class 的实现都可能匹配,由于
int T::*
(类成员指针)的匹配优先级比 …
的要高,所以编译器会先试图使用第一个版本进行展开。但编译展开的结果时发现int::*
是不合法的,于是编译器就放弃展开这个函数,而取另一个函数进行展开,并得到正确的调用。
所以,在真正发生调用(应该说真正需要被展开)之前,模板函数中的信息是不完备的,编译器无法为这些模板函数生成真正的执行代码,而只是进行一些很基本、简单的检查。所有的模板都不是“真正的代码”,它们是编译器用来生成代码的工具。在需要展开的时候,编译器从合适的候选者中选出优先级最高的一个来进行实例化(展开)。在展开后的代码如果不能正确被编译(像上面例子中
int::*
这种情况),编译器只是简单地放弃这次展开,转而寻找其它的模板。试想,如果编译器在展开失败后,直接产生一个编译错误的话,其它的函数就没有机会了,这是非常不合理的,因为:1.本次展开失败并不意味着被展开的模板代码就有问题,因为用其它类型的话还是有可能展开成功的。2.本次展开失败并不代表用于展开的类型无法找到合适的模板,其它模板可能合用。
所以,SFINEA
的意义就是:
编译器在每个调用点上,只为当前需要实例化的类型寻找一个合适的模板进行展开,而不会为某一次实例化而展开所有可能合适的重载模板(函数)。
这是编译器“智能”选择模板的表现。普通函数重载则不一样,无论是否被调用,或是无论调用点需要的是什么类型的重载,编译器会将所有参与了重载的函数一个不落的全部编译。如果对模板也采用同样的方式,那么模板将受到巨大的局限而失去意义。
有了
SFINEA
,当我们在写模板代码的时候,就不需要担心这些模板在使用某些类型进行展开的时候会失败,从而造成程序编译错误,因为我们知道编译器只会在能展开的情况展开它们,展开失败的情况下,这些代码并不会真正进入你的程序中。
好了,在结束本文之前,我们再看看
SFINEA “知名”的一个例子:
程序段2:
template <typename
T>
class
is_class
{
typedef
char one;
typedef
struct {chara[2];}
two;
template
<typenameC>
static
one
test(int
C::*);
template
<typenameC>
static
two
test(...);
public:
enum
{value
= sizeof(test<T>(0))==
sizeof(one)};
};
这是模板圣经《C++
templates》中的一个例子(原程序可能不完全一样),与程序段 1 不同的是,is_class<T>::value
是一个编译期的 bool
值,而程序段 1
,ture 或是
false
是在运行期才得到的结果。is_class<T>::value
这样的“装置”(device)经常出现在模板编译中,用于根据类型的某种特性(比如,是不是一个类?)来选择不同的模板。boost
中的提供了很多类似的 device,再配合 boost::enable_if
来完成威力巨大的模板编程。
可以说,SFINEA
几乎是随处可见的,不可或缺的重要“原则”。:)
本文完。
|