上面的代码在Windows上编译、运行都没有问题,但是移植到linux上,编译时报错: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
可惜,在C++标准中,函数模板不能偏特化(linux同标准,windows上没有这个限制),偏特化对能对class、struct这种类型模板...template <class R, class Dummy>
网上查了资料,所幸还有下面两种方法都能解决问题:
第一种是用重载机制实现:
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;
}