C++ 中的奇技淫巧大部分來源於模板技術,尤其是模版元編程技術(Template Meta-Programming, TMP)。TMP 通過將一部分計算任務放在編譯時完成,不僅提高了程序的性能,還能讓程序獲得一些用常見語法結構無法實現的功能。在這里,我總結了幾個利用 TMP 實現靜態反射的例子,這些功能得益於模板的特化或模板實例化時的 SFINAE 行為。(代碼默認包含 <iostream> 頭)
1. 類型判定
#define MakeIsType(Tp) \ template <typename T> \ class Is_##Tp { \ public: \ enum {value = false}; \ }; \ template <> \ class Is_##Tp<Tp> { \ public: \ enum {value = true}; \ }; // 生成 Is_void 類 MakeIsType(void); int main(int argc, char const *argv[]) { std::cout << Is_void<int>::value; // 0 std::cout << Is_void<void>::value; // 1 return 0; }
以上代碼用了簡單的特化,先定義一個 value 為 false 的基礎類,並為 Tp 類特化一個 value 為 true 的模板。
2. 判定指針是否能轉換
template <typename To, typename From> class IsConvertable { typedef char One; typedef struct { One _[2]; } Two; static One deduce(To*); static Two deduce(...); public: enum { value = sizeof(deduce((From*)0)) == sizeof(One) }; }; int main() { std::cout << IsConvertable<long int, long>::value; // 1 std::cout << IsConvertable<long int, double>::value; // 0 }
這里利用了成員函數的重載解析,如果 From 指針無法轉換為 To 指針(無論是同類,還是子類轉基類),那么第二個版本的 deduce 將被 sizeof 解析,完成判定的功能。( sizeof 並不會運行函數,但是會讓編譯器進行重載解析)
3. 成員名稱檢測
template <typename T> class Has_foo { typedef char One; typedef struct { One _[2]; } Two; struct Base { char foo; }; struct Mixin : public T, public Base {}; template <typename U, U> struct Matcher {}; template <typename U> static One deduce(U*, Matcher<char Base::* ,&U::foo>* = 0); static Two deduce(...); public: enum { value = sizeof(deduce((Mixin*)0)) == sizeof(Two) }; }; class A { public: int foo(int, double); }; class B { public: int bar(int, double); }; int main(int argc, char const *argv[]) { std::cout << Has_foo<A>::value; // 1 std::cout << Has_foo<B>::value; // 0 return 0; }
這里用到了 SFINAE 技術,如果 A 含有 foo 成員,那么 Mixin 中就會有兩個版本的 foo,一個來自 Base,一個來自 A,因為 Mixin 是一個多重繼承的子類,調用 &U::foo 在 U 為 Mixin 時將會產生二義性,所以這個版本的 deduce 將不會產生。sizeof將解析到 Two 這個版本。
4. 成員函數(包含參數和返回值類型)檢測
template<typename T, typename RESULT, typename ARG1, typename ARG2> class HasMethod_foo { template <typename U, RESULT (U::*)(ARG1, ARG2)> struct Matcher; template <typename U> static char deduce(Matcher<U, &U::foo> *); template <typename U> static int deduce(...); public: enum { value = sizeof(deduce<T>(0)) == sizeof(char) }; }; class A { public: int foo(int, double); }; int main(int argc, char const *argv[]) { std::cout << HasMethod_foo<A, int, int, int>::value; // 0 std::cout << HasMethod_foo<A, int, int, double>::value; // 1 return 0; }
這里采用了 SFINAE 技術,並利用了 C++ 中模板的非類型參數可以是成員指針這一性質,用一個 Matcher 類將 U 和某一類型的成員函數指針綁定在一起,如果一個類不存在這樣的成員函數,那么第一個版本的 deduce 將無法生成。於是 sizeof 將解析到第二個版本。
結論
關於更多模板元編程的技術,可以參考 C++ Template 這本書。stackoverflow 也是一個獲取這類奇技淫巧的好地方。