最近為了port一段小程式,竟然被幾個小問題給卡了大半天,有點尷尬的是這些都是 n 年前就 well known 的問題了。

第一個問題有點囧,那就是ISO C++當中竟然沒有 copy_if 和 copy_n,而且我最近才知道。(更精確的說,我一直都知道手邊有些STL實作沒有copy_if 和 copy_n,但最近才確認這是標準)

不要笑!當然我有一些藉口。因為我開始學STL的時間比較早,那時候參考的 SGI manual 明明就有。並且我常使用的 STL 實作之一確實就有 copy_if 和 copy_n,所以我很容易把其他我常用、但沒有此二者的STL實作當成是「不合標準」的實作。再說STL當中有很多類似的idiom,所以有這樣的想法應該是自然而然才對。

我必須說 copy_if 和 copy_n 雖然寫起來五、六行就可以搞定,不過如果程式庫裡面內建不是更方便嗎?這一定是ISO C++委員會的錯誤。

另一個小小的意外是,下面是某些教科書示範計算有多少個英文字母、大寫轉小寫的方法,在VC是沒問題的,但是gcc底下「很容易」編譯失敗

string s = "You're doing it WRONG!";
int n = count_if(s.begin(), s.end(), isalpha);
transform(s.begin(), s.end(), s.begin(), tolower);

很多人的第一個想法應該是古時候 isalpha 和 tolower 常常用 macro 實作,所以會面臨macro展開出怪東西的窘境。不過我知道這不是問題,因為只要確定引用<cctype>標頭檔,就一定會得到真正的函數,而非古老 <ctype.h> 所流傳下來的macro版。

真正的問題在於 std namespace 底下實際上有多個 tolower 函數的 overload,其他 <cctype> 底下的函數也都是。其中之一是位於<cctype>當中的int (int)版本,另外則是<locale>底下的function template。如果引用了<locale>,那麼 transform 就會不知道該使用<cctype>當中的 tolower 還是<locale>底下的 tolower。

雖然大部分的人根本就不知道<locale>到底能幹嘛,也不會想去引用,不過<iostream>會引用。若是沒有直接、間接含入<locale>,那麼上面的程式碼是不會有問題的。但<iostream>畢竟還是很常用的header,所以還是得有其他的解決方案才好。這是我實驗過在gcc下可用的方式:

1.利用namespace區隔。

transform(s.begin(), s.end(), s.begin(), ::tolower);

這裡用::運算符取得的最global的範圍,所以不會和 std 下的東西搞混。不過這個作法最大的缺點是,他抓到的是傳統<ctype.h>的版本,如前所述,在其他編譯器上很可能是macro而非函數,所以根本得不到函數,只會得到macro展開的怪東西。

 

2.指名型態

transform(s.begin(), s.end(), s.begin(), (int(*)(int)) std::tolower);

缺點是看起來很倒胃口。

 

3.自己寫函數包裝tolower。這可能是最完善的做法。

 


 

追根究柢,問題還是出在 gcc 的 overload 解析。下面的程式碼在VC編譯不會有任何問題,但是gcc不給過:

int F(int n) {return n;}
template <class T>
T F(T n) {return n;}
template <class Fn>
void Ftest(Fn f) {}
int main()
{
    Ftest(F);
}

我根本懶得去確定 ISO 如何規定這類解析規則,根據我的經驗,這種東西即使規定了也完全不能依賴,各編譯器的作法可能有所出入。如果一般人寫出這樣的程式碼,根本是和自己的睡眠時間以及休閒生活過不去。

不過我還是有點好奇,gcc和VC誰才是所謂「正確」的實作呢?我猜是VC啦。有沒有善心人士能證實一下

arrow
arrow
    全站熱搜

    novus 發表在 痞客邦 留言(3) 人氣()