我們將玩一個遊戲。這是模板遊戲。我給你寫了一個函數模板,並告訴它它做了什麼。準備?走!
此功能有什麼作用?
template<typename T>
void foo() {
T myT{};
}
簡單!我們只需創建一個T型變量!
好!你能告訴我以下事情嗎?
template<typename T>
void foo() {
(void) T::thing();
}
我們稱之爲T的成員函數命名爲thing
?
是的!好的,我會嘗試使用具體類型調用該功能:
struct Bar {
using thing = int;
};
foo<Bar>();
你注意到了嗎?你認爲T::thing()
會調用一個函數,但現在它只是創建一個int!看看instanciated函數如何:
template<>
void foo<Bar>() {
// thing is not a function, but a member type!
(void) Bar::thing();
}
這很糟糕。看起來像訪問靜態變量和調用函數的東西與訪問成員類型或創建成員類型實例的語法相同!
這可能會導致一些嚴重的錯誤。當我們想要調用一個函數時,我們不想實例化一個類型,也不想在我們只想比較一些數字時調用一個模板函數(是的,這個語法也可能不明確!)。因此,編譯器將假定之後的事物::
不是成員類型或成員模板,而是普通成員。
如果編譯器假定訪問靜態數據成員但訪問成員類型,則會引發編譯時錯誤。
但是,當你真正的意思是用一個成員的類型,你可以告訴編譯器不要假設你訪問一個數據成員,但假設類型:
template<typename T>
void foo() {
// I really want T::thing to be a type!
(void) typename T::thing();
}
但是編譯器只需要處理這些假設使用從屬名稱解析表達式時的語法假設。一個從屬寶貝只是一個包含模板參數的名稱。
如果您想了解更多關於在何處以及何時放置這些明確消歧的關鍵字,請閱讀下面的問題:Where and why do I have to put the "template" and "typename" keywords?