__WFILE__ and generic __FILE__, __TFILE__
在 C/C++ 裡 debug 的時候,__FILE__ 和 __LINE__ 很好用,可以幫我們將錯誤發生的地點指出來。可是,因為 __FILE__ 對應的型別是 const char* (__LINE__ 對應的型別是 size_t),在使用 wide character I/O 的時候,便不能用。就算像 L"some string" 一樣,前面加一個 L 也不行,因為這樣子就變成了 L__FILE__,是另外一個 symbol 了。空一格也不行,因為 L"some string" 的 L 必須與引號直接連接。 還好,使用 ## 這個 preprocessor operator 可以解決這個問題:
#define WIDEN_(x) L##x#define WIDEN(x) WIDEN_(x)#define __WFILE__ WIDEN(__FILE__)
這樣子我們就可以依據 output 的環境,選擇 __FILE__ 或 __WFILE__ 來使用了:
void fout(ostream& os){ os << __FILE__ << endl;}
void wfout(wostream& os){ os << __WFILE__ << endl;}
int main(){ fout(cout); wfout(wcout); return 0;}
可是,如果是在 template class/function 裡面呢?Character/string 的真實型別,由 template parameter 決定,我們根本無從選擇該用 __FILE__ 還是 __WFILE__ 怎麼辦?這時候,如果有個 generic 版本的 __TFILE__,能夠自動選擇要用 __FILE__ 還是 __WFILE__ 的話,那該有多好?可惜 pre-processor 不支援 template resolution,無法做到 generic。
#define __TFILE__(char_type) \ ((sizeof(char_type) == sizeof(char)) ? __FILE__ : __WFILE__)
如上列程式使用 conditional operator 將 __FILE__ 與 __WFILE__ 的選擇,留待 compile-time 再來解決,是不可行的。原本的想法是,(sizeof(char_type) == sizeof(char))__FILE__ 與 __WFILE__ 的型別不同 (分別為 const char* 與 const wchar_t*),所以這樣子使用 conditional operator 是非法的。 山不轉路轉,「把 __FILE__ 與 __WFILE__ 的選擇,留待 compile-time 再來解決」的基本想法應該是對的,既然 conditional operator 不能用,那用 template specialization 如何?如下列程式:
是個 constant expression,所以 compiler 可以 optimize 選擇正確的版本。但因為 C++ standard 5.16.5 說,當 conditional operator 的第二個及第三個 operands 為 r-value 時,兩者的 type 必須匹配,除非可藉由 overload resolution 解決歧義。因為
#include <functional>
template <class CharT>struct tfile_sel : public ::std::binary_function<const char*, const wchar_t*, const CharT*>{ const CharT* operator () (const char* f, const wchar_t* wf) { return 0; }};
template <>inlineconst char* tfile_sel<char>::operator () (const char* f, const wchar_t* wf){ return f;}
template <>inlineconst wchar_t* tfile_sel<wchar_t>::operator () (const char* f, const wchar_t* wf){ return wf;}
#define __TFILE__(char_type) tfile_sel<char_type>()(__FILE__, __WFILE__)
利用 template specialization 選擇 __FILE__ 或 __WFILE__,然後因為 template specialization 以及 inline 的關係,最後的 run-time overhead 理論上也應該要蠻小的。就算有,也不大,反正大多在 debug mode 裡用,可以省略這一點點的 overhead。如此,完成的 __TFILE__ 可以這麼樣子用:
using namespace ::std;
template <class CharT>void tfout(basic_ostream<CharT>& os){ os << __TFILE__(CharT) << endl;}
int main(){ tfout(cout); tfout(wcout); return 0;}
總整理上面的 code,含測試程式如下:
#include <iostream>#include <string>#include <functional>
#define WIDEN_(x) L##x#define WIDEN(x) WIDEN_(x)#define __WFILE__ WIDEN(__FILE__)
using namespace ::std;
template <class CharT>struct tfile_sel : public ::std::binary_function<const char*, const wchar_t*, const CharT*>{ const CharT* operator () (const char* f, const wchar_t* wf) { return 0; }};
template <>inlineconst char*tfile_sel<char>::operator () (const char* f, const wchar_t* wf){ return f;}
template <>inlineconst wchar_t*tfile_sel<wchar_t>::operator () (const char* f, const wchar_t* wf){ return wf;}
#define __TFILE__(char_type) tfile_sel<char_type>()(__FILE__, __WFILE__)
template <class CharT>void tfout(basic_ostream<CharT>& os){ os << __TFILE__(CharT) << endl;}
int main(){ tfout(cout); tfout(wcout); return 0;}
以上技巧有待統整的地方很多,好比 WIDEN() 可以提出來變成一個獨立的模組以便再利用 (reuse)。另外,雙底線開頭的識別字,在 C/C++ 裡是保留給 standard 與 compiler vendor 用的,所以最好另外換個名字。最後,應該也可以參考 Boost 的 preprocessor library,也許可以有其他不一樣的作法也說不定。
One Backlink
http://www.jeffhung.net/blog/articles/jeffhung/237/
Post a Comment