這是一位網友出的問題:要如何偵測某個型別是否可以套用大於運算式( > expression )呢?這裡假設是在 C++ 03 的標準之下。
熟悉 C++ 的網友應該都會想到可以靠 SFINAE 手法解決這類偵測問題。仿照 tr1 type_traits 的使用風格,典型的寫法大概像這樣:
template <typename T> struct is_gt_comparable { typedef char yes; typedef char no[2]; template <typename U> static U make(); template <typename U> static yes& test( int (*)[sizeof(make<U>() > make<U>())] ); template <typename U> static no& test(...); static const bool value = sizeof(test<T>(NULL)) != sizeof(no); }; struct MyClass { int data; }; cout << is_gt_comparable<int>::value << endl; cout << is_gt_comparable<string>::value << endl; cout << is_gt_comparable<MyClass>::value << endl;
在 g++ 4.5 完全如預期般的輸出 110,在 VC++ 2008 雖然可以編譯,但是輸出結果是 111。至於 VC 為何會認為 NULL 可以轉換為一個不存在型別的指標,就不是我可以理解的了。稍加修改避開指標類型:
... template <int N> struct helper { helper(int n); char a[N]; }; template <typename U> static yes& test( helper<sizeof(make<U>() / make<U>())> ); ...
做了上述修改後,好消息是 VC 總算可以正確偵測 + - * / % 之類的 expression 了!但是遇到 > < == 之類的 expression 仍然掛點,原因不免令人好奇 -- VC 似乎在無法充分檢查型態的情況下仍然假設 sizeof(make<U>() > make<U>())為 1!
於是我又做了一些修改,包括從 return type 方面下手,但都不成功。而且我並不那麼有把握這些完全合乎 C++ 03 標準,我甚至懶得查,畢竟在廣泛使用的編譯器上失敗就不算是令人滿意的實用方法。
要設計 VC 也可以接受的 test() 函數 signature 看起來不是那麼簡單,那麼換個方式來實作 SFINAE 吧。我想到還可以從 operator> 下手,像下面這種濫好人 operator> 一定要等到世界上沒有別的男人可以選才會獲得青睞
namespace compare_private { typedef char fail[777]; template <typename T> fail& operator>(const T& lhs, const T& rhs); template <typename T> struct test { static T makeT(); static const bool value = sizeof(makeT() > makeT()) != sizeof(fail); }; } // namespace compare_private template <typename T> struct is_gt_comparable { static const bool value = compare_private::test<T>::value; };
經測試在 VC++ 和 g++ 都跑出和預期一樣的結果。上面的 sizeof 是很破的招式,因為我們沒有辦法排除異想天開的使用者碰巧自訂了 operator> 傳回 size 為 777 的東西。在 C++ 11 當然要用 decltype 和 std::is_same (tr1 有) 比較才正確,不過要是有 decltype 可用,這個問題就不需要用這麼彆扭的方式解決。
主要問題看似解決了,但還是不能讓人滿意。雖然這個濫好人 operator> 的 overloading 順位比其他所有的 operator> 實作都還要低,但是比起「先轉型再套用 operator」還是高了那麼一點點,所以無法偵測經由轉型而獲得的 > expression,第一種方法就沒有這方面的問題。
我沒有美國時間繼續探索下去了,畢竟這不是我遇到的問題,有沒有網友要來解救一下呢?
留言列表