linux template error: explicit specialization in non-namespace scope
namespace TestTemp
{
class CBase
{
public:
template <class R, class P1, class P2>
R UICall(const char *name, const P1 &p1, const P2 &p2)
{
return CallMid<R>();
}
template <class R>
R CallMid()
{
R ret;
CallMe(ret);
return ret;
}
template <>
void CallMid<void>()
{
CallMe();
}
void CallMe(std::string &ret)
{
// do something
ret = "hello";
}
......
void CallMe()
{
// do something
printf("void EndCall!\n");
}
};
}
using namespace TestTemp;
int main()
{
CBase oBase;
oBase.UICall<void>("test", 1, 2);
string strT = oBase.UICall<std::string>("test", 1, 2);
printf("size=%llu, str=%s\n", strT.size(), strT.c_str());
return 0;
}
error: explicit specialization in non-namespace scope 'class TestTemp::CBase'
原因是下面的程式碼在非名稱空間中顯式特化:
template <>很顯然,這裡繞不過去,必須對void型顯式特化(否則main函式編譯時會報錯:不能定義void型變數)。對大部分專案來說,修改這個問題很容易:只要把特化的程式碼寫在類定義的外面(但要寫在namespace下)就搞定了,如第一段程式碼應該寫成這樣:
void CallMid<void>()
{
CallMe();
}
namespace TestTemp
{
class CBase
{
public:
template <class R, class P1, class P2>
R UICall(const char *name, const P1 &p1, const P2 &p2)
{
return CallMid<R>();
}
template <class R>
R CallMid()
{
R ret;
CallMe(ret);
return ret;
}
void CallMe(std::string &ret)
{
// do something
ret = "hello";
}
......
void CallMe()
{
// do something
printf("void EndCall!\n");
}
};
template <>
void CBase::CallMid<void>()
{
CallMe();
}
}
using namespace TestTemp;
int main()
{
CBase oBase;
oBase.UICall<void>("test", 1, 2);
string strT = oBase.UICall<std::string>("test", 1, 2);
printf("size=%llu, str=%s\n", strT.size(), strT.c_str());
return 0;
}
上面的程式碼在VC和linux上都能順利編過並執行。但是在我的專案中悲劇了:上面的程式碼是庫程式碼,而且這是個標頭檔案,真正的工程專案很多檔案會include這個標頭檔案,這樣在工程專案連結時報錯:CBase::CallMid<void> muti defined!你會想到把特化程式碼移到庫的cpp檔案中,這樣不行,工程專案編譯時就報錯找不到宣告;或者你又想到特化程式碼先在標頭檔案中宣告,實現放到cpp檔案中,但還是不行,編譯庫程式碼時編譯器又已經發現你explicit specialization in non-namespace scope的意圖了...
好吧,你想到了偏特化,我增加一個無用的模板引數Dummy,然後將第一個模板引數R偏特化為void
template <class R, class Dummy>
可惜,在C++標準中,函式模板不能偏特化(linux同標準,windows上沒有這個限制),偏特化對能對class、struct這種型別模板...網上查了資料,所幸還有下面兩種方法都能解決問題:
第一種是用過載機制實現:
template <class R,class D>但是這種解法會造成未使用的複雜物件的構造和析構,產生額外的開銷。所以最終使用下面的方法,完美的解決了問題:
R CallMid(D/*Dummy*/)
{
R ret;
CallMe(ret);
return ret;
}
template <class D>
void CallMid<void,D>(void)
{
CallMe();
}
namespace TestTemp
{
template<typename T>
struct identity { typedef T type; };
class CBase
{
public:
template <class R, class P1, class P2>
R UICall(const char *name, const P1 &p1, const P2 &p2)
{
return CallMid<R>();
}
template <class R>
R CallMid()
{
return TCallType(identity<R>());
}
template <class R>
R TCallType(identity<R>)
{
R ret;
CallMe(ret);
return ret;
}
void TCallType(identity<void>)
{
CallMe();
}
void CallMe(std::string &ret)
{
// do something
ret = "hello";
}
......
void CallMe()
{
// do something
printf("void EndCall!\n");
}
};
}
using namespace TestTemp;
int main()
{
CBase oBase;
oBase.UICall<void>("test", 1, 2);
string strT = oBase.UICall<std::string>("test", 1, 2);
printf("size=%llu, str=%s\n", strT.size(), strT.c_str());
return 0;
}