1. 程式人生 > >Item 42:typename的兩種用法

Item 42:typename的兩種用法

Item 42: Understand the two meanings of typename.

時至今日還有人在論壇裡問模板引數前的typename和class有何區別:

template<typename T> class Widget;
template<class T> class Widget;

答案是沒有區別!有人覺得class寫起來方便就用class,有人覺得typename語義更正確就用typename。 然而typename和class對編譯器而言卻是不同的東西,這是本節的重點所在。

typename可以用來幫編譯器識別巢狀從屬型別名稱,基類列表和成員初始化列表除外。

宣告一個型別

typename的第一個作用在於宣告一個型別。為什麼型別還需要宣告呢?因為編譯器並不是總會知道哪個名稱是個型別。 下面的程式碼會編譯錯:

template<typename C>
void print2nd(const C& container){
    if(container.size() >= 2){
        C::const_iterator it(container.begin());
        ++it;
        int value = *it;  
        cout<<value;
    }
}

發生編譯錯誤是因為編譯器不知道C::const_iterator是個型別。萬一它是個變數呢? C::const_iterator的解析有著邏輯上的矛盾: 直到確定了C是什麼東西,編譯器才會知道C::const_iterator是不是一個型別; 然而當模板被解析時,C還是不確定的。這時我們宣告它為一個型別才能通過編譯:

typename C::const_iterator it(container.begin());

巢狀從屬名稱

事實上型別C::const_iterator依賴於模板引數C, 模板中依賴於模板引數的名稱稱為從屬名稱(dependent name), 當一個從屬名稱巢狀在一個類裡面時,稱為巢狀從屬名稱(nested dependent name)。 其實C::const_iterator還是一個巢狀從屬型別名稱(nested dependent type name)。

巢狀從屬名稱是需要用typename宣告的,其他的名稱是不可以用typename宣告的。比如下面是一個合法的宣告:

template<typename C>
void f(const C& container, typename C::iterator iter);

如果你把const C&也聲明瞭typename也是要編譯錯的哦:

template<typename C>
void f(typename const C& container, typename C::iterator iter);

錯誤輸出:

error: expected a qualified name after 'typename'

一個例外

模板中的巢狀從屬名稱是需要typename宣告的,然而有一個例外情況: 在派生子類的基類列表中,以及建構函式的基類初始化列表中,不允許typename宣告。 例如Derived繼承自Base::Nested:

template<typename T>
class Derived: public Base<T>::Nested{  // 繼承基類列表中不允許宣告`typename`
public:
    explicit Derived(int x): Base<T>::Nested(x){    // 基類初始化列表不允許宣告`typename`
        typename Base<T>::Nested tmp;   // 這裡是要宣告的
    }
};

traits

C++提供了一系列的traits模板,用來提供型別資訊。比如:

template<typename IterT>
void workWithIterator(IterT it){
    typename std::iterator_traits<IterT>::value_type tmp(*it);
}

其實上述模板方法也可以不同traits來實現,比如:

template<typename container>
void workWithIterator(typename container::iterator it){
    typename container::value_type tmp(*it);
}

但traits提供了更加一致的使用方式以及容器實現的靈活性,模板程式碼也簡潔了不少。 儘管如此,程式設計師還是懶惰的。我們傾向於用typedef來給這些巢狀從屬名稱起一些別名:

template<typename IterT>
void workWithIterator(IterT it){
    typedef typename std::iterator_traits<Iter>::value_type value_type;
    value_type tmp(*it);
}
雖然typedef typename看起來也很怪異,但你想敲很多遍typename std::iterator_traits<Iter>::value_type麼?