模板程式設計中,編譯器在缺乏具體型別資訊時,無法判斷巢狀名稱是型別還是模板。C++ 引入 typename 與 template 關鍵字協助編譯器消除歧義。本文從基礎語法切入,深入剖析 STL allocator 的 rebind 機制實作細節。
template <typename T>
class Container {
public:
typedef T* ElemPtr; // 巢狀型別
};
template <typename Cont>
void fill(Cont& c, Cont::ElemPtr arr[], int n) { // 錯誤!
for (int i = 0; i < n; ++i) c.insert(arr[i]);
}
編譯器在 fill 模板定義階段不知道 Cont 具體是什麼,無法確認 Cont::ElemPtr 是型別,依標準預設視為非型別 → 編譯錯誤。
typename:宣告巢狀名稱為型別template <typename Cont>
void fill(Cont& c, typename Cont::ElemPtr arr[], int n) { // 正確
for (int i = 0; i < n; ++i) c.insert(arr[i]);
}
typename 僅用於模板定義內部,告知編譯器「後續限定名稱是型別」。Container::ElemPtr )編譯器已知,不可 加 typename。template:宣告巢狀名稱為模板template <typename M>
void populate() {
M m;
m.template get<GrossAmount>(); // 正確:告知 get 是模板
M* mp = &m;
mp->template get<Percentage>(); // 正確:指標存取同理
}
若無 template 關鍵字,< 會被解析為小於號而非模板參數列表開頭。
rebind 機制容器內部節點型別 Node 與元素型別 T 不同,需將配置器從 allocator<T> 重綁為 allocator<Node>:
template <class T>
class MyAlloc {
public:
template <class Other>
struct rebind {
typedef MyAlloc<Other> other; // 重綁結果型別
};
};
// 容器內部使用
template <typename T, typename A = std::allocator<T>>
class MyList {
struct Node { T value; Node* next; };
// 雙關鍵字消除歧義
typedef typename A::template rebind<Node>::other NodeAlloc;
// ^^^^^^^^ ^^^^^^^^
// 型別 模板
};
| 情境 | 正確寫法 | 錯誤寫法 | 說明 |
|---|---|---|---|
| 模板內存取巢狀型別 | typename T::Type | T::Type | 編譯器無法推導 |
| 模板內存取巢狀模板 | T::template Rebind<U> | T::Rebind<U> | < 被誤判為運算子 |
| 巢狀模板結果為型別 | typename A::template rebind<Node>::other | 缺任一關鍵字 | 雙重歧義需雙關鍵字 |
| 具體型別(非模板參數) | MyAlloc<int>::rebind<double>::other | 加 typename/template | 編譯器已知完整資訊,加反而錯誤 |
利用函式樣板靜態區域變數實現編譯期「延遲建立」的共享狀態:
class Monostate {
public:
template <typename T> T& get() {
static T member; // 編譯期首次使用時建立
return member;
}
};
// 使用
Monostate m1, m2;
m1.get<int>() = 12;
m2.get<int>(); // 返回 12,同一實體
m2.get<std::string>() = "Hej!"; // 另一型別獨立實體
進階版支援索引與命名:
template <typename T, int Idx> struct Name { typedef T Type; };
typedef Name<int, 86> GrossAmount;
typedef Name<double, 7> Percentage;
class NamedMonostate {
public:
template <class N> typename N::Type& get() {
static typename N::Type member;
return member;
}
};
// 使用
NamedMonostate nm1, nm2;
nm1.get<GrossAmount>() = 12;
nm2.get<Percentage>() = 12.2;
舊式編譯器(VC6、BCB5 等)常寬容缺少 typename/template 的程式碼,導致移植至標準相容編譯器(GCC、Clang、MSVC10+)時大量報錯。建議開發階段即使用高標準編譯器驗證,或啟用 -pedantic、/Za 等嚴格模式。