這個(gè)概念我也是最近才聽(tīng)說(shuō)到的,來(lái)源是《Extended STL, Volume 1: Collections and Iterators》,在其第13章。
SFINAE,即Substitution Failure Is Not an Error!可以理解為匹配失敗不是錯(cuò)誤,更嚴(yán)格的說(shuō)應(yīng)該是參數(shù)匹配失敗不是一個(gè)編譯時(shí)錯(cuò)誤。
光看這些解釋我想除了少部分C++專(zhuān)家之外基本上都會(huì)迷糊,當(dāng)然我不是專(zhuān)家,所以我迷糊了。
本著不知道就Google的態(tài)度,我Google了,不過(guò)得到的資料并不多,在維基百科上面找到點(diǎn)資料但是看過(guò)之后并不是很明白。當(dāng)然,不明白不等于不去探索了,這有違我的行為準(zhǔn)則,后來(lái)我在其它的書(shū)上找到了類(lèi)似的東西,現(xiàn)在跟大家分享??催@樣的一段代碼:
#include <iostream>
using namespace std;
void print( int iNum ) {
cout<<"int print( int )"<< endl;
}
template < typename _Ty >
void print( _Ty tt ){
typename _Ty::value_type vt_someval;
cout<<"template < typename _Ty >"<< endl;
}
int main()
{
short siNum = 10;
print( siNum );-
return 0;
}
這段代碼能否通過(guò)編譯呢?實(shí)踐證明,這段代碼無(wú)法通過(guò)編譯,比如說(shuō)我在Visual Studio 2008 SP1下面的錯(cuò)誤:
1>e:\documents\visual studio 2008\projects\baidu\main.cpp(13) : error C2825: '_Ty': 當(dāng)后面跟“::”時(shí)必須為類(lèi)或命名空間
1> e:\documents\visual studio 2008\projects\baidu\main.cpp(20): 參見(jiàn)對(duì)正在編譯的函數(shù) 模板 實(shí)例化“void print<short>(_Ty)”的引用
1> with
1> [
1> _Ty=short1> ]
1>e:\documents\visual studio 2008\projects\baidu\main.cpp(13) : error C2039: “value_type”: 不是“`global namespace'”的成員
1>e:\documents\visual studio 2008\projects\baidu\main.cpp(13) : error C2146: 語(yǔ)法錯(cuò)誤 : 缺少“;”(在標(biāo)識(shí)符“vt_someval”的前面)
1>e:\documents\visual studio 2008\projects\baidu\main.cpp(13) : error C2065: “vt_someval”: 未聲明的標(biāo)識(shí)符
如何解決這個(gè)編譯錯(cuò)誤呢?有很多方法,比如說(shuō)我們可以特化short類(lèi)型:
template <>
void print<short>( short st )
{
cout<<"short st"<<endl;
}
實(shí)踐證明,這可以解決這個(gè)錯(cuò)誤,但是我們這里討論的是SFINAE??聪旅孢@段代碼:
#include <iostream>
using namespace std;
void print( int iNum )
{
cout<<"int print( int )"<< endl;
}
template < typename _Ty >
void print( _Ty tt, typename _Ty::value_type* pvt_dummy = NULL )
{
typename _Ty::value_type vt_someval;
cout<<"template < typename _Ty >"<< endl;
}
int main() {
short siNum = 10;
print( siNum );-
return 0;
}
如果你的編譯器沒(méi)有問(wèn)題,那么這段程序是可以通過(guò)編譯的。為什么編譯器會(huì)放棄使用short去實(shí)例化print模板而選擇提升short為int去執(zhí)行第一個(gè)print呢?
這個(gè)就是SFINAE,C++的一個(gè)特性。
SFINAE最早由Daveed Vandevorde和Nicolai Josuttis提出,它意味著寧可對(duì)有問(wèn)題的類(lèi)型不考慮函數(shù)的重載也不要產(chǎn)生一個(gè)編譯時(shí)錯(cuò)誤。如果這里參數(shù)的類(lèi)型中有一個(gè)value_type的嵌套類(lèi)型,那么它就是重載決議集合的一部分。
有點(diǎn)暈?OMG,其實(shí)我也暈,換句話說(shuō):編譯器在辨認(rèn)函數(shù)模板時(shí),假如有一個(gè)特化會(huì)導(dǎo)致編譯時(shí)錯(cuò)誤(即出現(xiàn)編譯失敗),只要還有別的選擇可以被選擇,那么就無(wú)視這個(gè)特化錯(cuò)誤而去選擇另外的可選選擇。比如下面這樣使用:
class Test {};
int main()
{
Test a;
print( a );
-return 0;
}
那么編譯器給出的錯(cuò)誤是: error C2664: “print”: 不能將參數(shù) 1 從“Test”轉(zhuǎn)換為“int”, 而不是與模板特化相關(guān)的問(wèn)題。
這是C++一個(gè)非常重要的特性,如果沒(méi)有這個(gè)特性會(huì)對(duì)早期的很多代碼造成破壞(因?yàn)楫?dāng)時(shí)沒(méi)有模板),而且還會(huì)產(chǎn)生很多難以理解的代碼。
回過(guò)頭去看為什么把value_type作為參數(shù)的一部分或者是返回值編譯器就可以發(fā)覺(jué)我們提供的類(lèi)型不能適合語(yǔ)義而最初的代碼又不能發(fā)覺(jué)呢?這取決于模板實(shí)例化的過(guò)程,如果返回值和參數(shù)都可以匹配那么這個(gè)實(shí)例化就等同于成功了,此時(shí)此刻就表示編譯器已經(jīng)選擇了模板特化而不是其它選擇;當(dāng)約束位于參數(shù)或者是返回值的時(shí)候,模板參數(shù)匹配的適合就會(huì)失敗,這就產(chǎn)生了一個(gè)編譯時(shí)錯(cuò)誤,這個(gè)時(shí)候編譯器就會(huì)按照SFINAE原則去看是否還有其它選擇。
得益于泛型編程和模板元編程的飛速發(fā)展,在boost庫(kù)的enable_if、mpl、type_traits中為我們提供了很多非常好的解決方案。如果有興趣的話可以看看這些書(shū)《C++模板元編程》(英文版:《C++.Template.Metaprogramming》)、《超越c++標(biāo)準(zhǔn)庫(kù)——boost程序庫(kù)導(dǎo)論》(英文版:《Beyond the C++ Standard Library: An Introduction to Boost》),當(dāng)然還有Boost的文檔。