在說明之前先說說模板匹配的原則: 非模板函數具有最高優先權, 如果不存在匹配的非模板函數的話, 那麼最匹配的和最特化的具有最高的優先權.
C++中,函數模板與同名的非模板函數重載時,應遵循下列調用原則:
- 尋找一個參數完全匹配的函數,若找到就調用它。若參數完全匹配的函數多於一個,則這個調用是一個錯誤的調用。
- 尋找一個函數模板,若找到就將其實例化生成一個匹配的模板函數並調用它。
- 若上面兩條都失敗,則使用函數重載的方法,通過類型轉換產生參數匹配,若找到就調用它。
- 若上面三條都失敗,還沒有找都匹配的函數,則這個調用是一個錯誤的調用。
至於函數的選擇原則, 可以看看C++ Primer中的說明:
- 創建候選函數列表,其中包含與被調用函數名字相同的函數和模板函數。
- 使用候選函數列表創建可行的函數列表。這些都是參數數目正確的函數,並且有一個隱式的轉換序列(參數類型轉化),其中包括實參類型與相應的形參類型完全匹配情況。
- 確定是否有最佳的可行函數,有則調用它,沒有則報錯。
- 完全匹配,但常規函數優先於顯示定義模板函數,而顯示定義模板函數優先於模板函數。
- 提升轉換,即從小精度數據轉換爲高精度數據類型,如char/short 轉換爲int , int轉化爲long,float轉換爲double。
- 標準轉換,如int轉化爲char,long轉化爲double等
- 用戶自定義轉換。
下面先看看帶有默認值的模板函數特化和非特化的問題
-
template<typename T, bool
C = true>
-
struct if_ {
-
static const int value = 1;
-
};
-
-
template<typename T>
-
struct if_<T, true> {
-
static const int value = 2;
-
};
-
-
int main() {
-
printf("value: %d\n", if_<int>::value);
- }
上面的輸入結果是: value: 2. 編譯器在進行匹配的時候, 就如Prime上說的, 編譯器會先創建候選函數列表, 在創建候選列表的過程中C已經被賦予默認是true, 然後在進行匹配, 最高優先級是函數, 顯然這裏沒有. 然後是最匹配和最特化的模板函數, 所以, 就匹配到第二個函數了, 因此value等於2.
實際上, 上面的例子相當於
-
template<typename T, bool
C = true>
-
struct if_ {};
-
-
template<typename T>
-
struct if_<T, false> {
-
static const int value = 1;
- };
-
-
template<typename T>
-
struct if_<T, true> {
-
static const int value = 2;
- };
-
-
int main() {
-
printf("value: %d\n", if_<int>::value);
- }
因此, 編譯器總是先找到函數和模板, 然後根據默認值, 類型轉換進行匹配, 然後再根據優先級選擇最可行的.
-----------------------------------------華麗的分割線--------------------------------------------------
說到這裏應該大致明白重載函數, 函數模板, 特化和偏特化的一些匹配問題. (其實還有一個問題沒有說清楚, 後面會提到). 現在來看看SFINAE的一個應用.
要實現一個通用的序列化函數叫做toString, 它可以實現把任何類型序列化成字符串.
- template<typename T> std::string toString(const T &x);
現在有兩個類
-
class A {
-
public:
-
std::string toString() const {
-
return std::string("toString
from class A");
-
}
-
};
-
-
class B {
- };
這時代碼裏面有A::toString 就沒有問題, 但是編譯器找不到B::toString, 利用這個錯誤來跳過模板的匹配, 從而使得別的模板得以匹配.
-
template<typename T>
-
struct HasToStringFunction {
-
template<typename U, std::string (U::*)() const >
-
struct matcher;
-
-
template<typename U>
-
static char helper(matcher<U, &U::toString> *);
-
-
template<typename U>
-
static int helper(...);
-
-
enum { value = sizeof(helper<T>(NULL)) == 1 };
- };
利用這點就可以實現toString方法
-
template <bool>
-
struct ToStringWrapper {};
-
-
template<>
-
struct ToStringWrapper<true> {
-
template<typename T>
-
static std::string toString(T &x) {
-
return x.toString();
-
}
-
};
-
-
template<>
-
struct ToStringWrapper<false> {
-
template<typename T>
-
static std::string toString(T &x) {
-
return std::string(typeid(x).name());
-
}
-
};
-
-
template<typename T>
-
std::string toString(const
T &x) {
-
return ToStringWrapper<HasToStringFunction<T>::value>::toString(x);
-
}
-
-
int main() {
-
A a;
-
B b;
-
-
std::cout << toString(a) << std::endl;
-
std::cout << toString(b) << std::endl;
- }
這裏有兩個小技巧, 一個是sizeof()一個函數, 返回的是函數返回值的大小. 另外一個是U::*表示類成員函數指針. 比如std::string (*)() const 表明這是一個函數指針, 而std::string (U::*)() const表示這是一個類的成員函數.
這樣我們可以實現一個判斷類型是基本類型還是一個類的類模板is_class
-
template <typename T>
-
class is_class {
-
template <typename U>
-
static char helper(int U::*);
-
template <typename U>
-
static int helper(...);
-
public:
-
static const bool value = sizeof(helper<T>(0)) == 1;
- };
------------------------------------------------華麗的分割線--------------------------------------------
現在終於輪到大名鼎鼎的is_base_of出場
-
template <typename T1, typename T2>
-
struct is_same {
-
static const bool value = false;
-
};
-
-
template <typename T>
-
struct is_same<T, T> {
-
static const bool value = true;
-
};
-
-
template<typename Base, typename Derived, bool = (is_class<Base>::value && is_class<Derived>::value)>
-
class is_base_of {
-
template <typename T>
-
static char helper(Derived, T);
-
static int helper(Base, int);
-
struct Conv {
-
operator Derived();
-
operator Base() const;
-
};
-
public:
-
static const bool value = sizeof(helper(Conv(), 0)) == 1;
-
};
-
-
template <typename Base, typename Derived>
-
class is_base_of<Base, Derived, false> {
-
public:
-
static const bool value = is_same<Base, Derived>::value;
-
};
-
-
template <typename Base>
-
class is_base_of<Base, Base, true> {
-
public:
-
static const bool value = true;
- };
先來看看is_base_of模板的第三個參數, 如果前兩個類型都是類的話, 則爲true, 否則爲false. 下面一個個情況來分析:
第一種情況是有一個基本類型, 顯然, is_base_of第三個參數爲false, 按照上面說到的原則匹配到了第二個類模板. 在該情況下只有當兩個類型一致時is_base_of的value才爲true, 否則爲false.
第二種情況是Base和Derived是同一個類型, 則會匹配到第三個模板.
第三種情況是Base和Derived有繼承關係, 此時編譯器只能匹配第一個類模板. 在helper(Conv(), 0)中, 顯然沒有helper的第一個參數無法直接匹配, Conv()默認也無法轉換成Base或者是Derived. 因此需要調用自定義的轉換函數. 當試圖匹配int helper(Base, int)的時候, 編譯有兩個途徑, 一個是: Conv()->Derived()->Base(), 第三步是默認, 另一個是Conv()->const Conv()->Base(), 這實際上是一個SFINAE, 根據原則編譯器會繼續匹配下一個模板, 匹配成功, 因此value的值爲true.
第四種情況是Base和Derived沒有繼承關係, 當試圖匹配int helper(Base, int)的時候能通過Conv()->const Conv()->Base()匹配成功, 因爲兩者不是繼承關係, 因此Derived()->Base()的默認轉換不會成功, 因此該情況下此路徑不存在. 所以編譯器選擇的函數是int helper(Base, int), 最後, value的值爲false.