首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> C語言 >> C++ >> 關於C++

C++ 模板 metaprogramming 進階:typename、template 雙關鍵字消除歧義與 rebind 機制深度解析

     編輯:關於C++

模板程式設計中,編譯器在缺乏具體型別資訊時,無法判斷巢狀名稱是型別還是模板。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 關鍵字,< 會被解析為小於號而非模板參數列表開頭。

綜合案例:STL Allocator 的 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::TypeT::Type編譯器無法推導
模板內存取巢狀模板T::template Rebind<U>T::Rebind<U>< 被誤判為運算子
巢狀模板結果為型別typename A::template rebind<Node>::other缺任一關鍵字雙重歧義需雙關鍵字
具體型別(非模板參數)MyAlloc<int>::rebind<double>::other加 typename/template編譯器已知完整資訊,加反而錯誤

進階技巧:Monostate 模式的模板化擴充

利用函式樣板靜態區域變數實現編譯期「延遲建立」的共享狀態:

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 等嚴格模式。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved