1. 程式人生 > >linux template error: explicit specialization in non-namespace scope

linux template error: explicit specialization in non-namespace scope

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