linux template error: explicit specialization in non-namespace scope

来源:互联网 发布:天天向上网络 编辑:程序博客网 时间:2024/05/18 02:19
将Windows上的项目移植到linux上,发现C++ template的实现还是有些不同,总的来说,linux上的template实现和C++标准更一致,而Windows上的实现更宽松。有些细微的不同在移植时还会有不小的麻烦,下面就是一例:

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;
}

上面的代码在Windows上编译、运行都没有问题,但是移植到linux上,编译时报错:
error: explicit specialization in non-namespace scope 'class TestTemp::CBase'
原因是下面的代码在非命名空间中显式特化:
  template <>
  void CallMid<void>()
  {
      CallMe();
  }
很显然,这里绕不过去,必须对void型显式特化(否则main函数编译时会报错:不能定义void型变量)。对大部分项目来说,修改这个问题很容易:只要把特化的代码写在类定义的外面(但要写在namespace下)就搞定了,如第一段代码应该写成这样:

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;
}

0 0
原创粉丝点击