上篇文章c++11-17 模板核心知識(十四)—— 解析模板之依賴型模板名稱 Dependent Names of Templates(.template/->template/::template) 介紹了依賴型模板名稱,提到關於模板解析有六個大方面:
- 非模板中的上下文相關性 Context Sensitivity in Nontemplates
- 依賴型型別名稱 Dependent Names of Types <-----
- 依賴型模板名稱 Dependent Names of Templates
- using-declaration中的依賴型名稱 Dependent Names in Using Declarations
- ADL和顯式模板實參 ADL and Explicit Template Arguments
- 依賴性表示式 Dependent Expressions
這篇文章介紹下依賴型型別名稱(Dependent Names of Types)。
模板名稱的問題及解決
模板中的名稱存在一個問題:它們有的時候不能被很好的分類,比如一個模板引用其他模板的名稱,因為模板特化的存在,會讓問題變得複雜一些。例如:
template <typename T> class Trap {
public:
enum { x }; // #1 x is not a type here
};
template <typename T> class Victim {
public:
int y;
void poof() {
Trap<T>::x *y; // #2 declaration or multiplication?
}
};
template <> class Trap<void> { // evil specialization!
public:
using x = int; // #3 x is a type here
};
void boom(Victim<void> &bomb) { bomb.poof(); }
如果你直接編譯,會報錯:
main.cc:30:14: error: unexpected type name 'x': expected expression
Trap<T>::x *y; // #2 declaration or multiplication?
^
main.cc:39:38: note: in instantiation of member function 'Victim<void>::poof' requested here
void boom(Victim<void> &bomb) { bomb.poof(); }
^
1 error generated.
這個問題和解決方案在c++11-17 模板核心知識(二)—— 類别範本涉及過,這篇文章再展開說一下相關規則。
回到上面的例子,當編譯器解析到#2處時,它需要決定Trap<T>::x
是一個型別還是一個值,這決定了Trap<T>::x *y
是宣告一個指標還是做乘法。
問題是,在Trap中,Trap<T>::x
是一個值,但是在全特化版本Trap<void>
中,Trap<T>::x
是一個型別。所以,這種情況實際是依賴模板引數T的,也就是依賴型型別名稱(Dependent Names of Types)。
C++規定,只有當加上typename關鍵字後,依賴型型別名稱才會被當做型別,否則會被當做一個值。這裡typename的意義和宣告一個模板時使用的typename是兩個意思,所以不能用class來替換typename.
typename規則
當一個名稱具備以下性質時,需要在名稱前面加typename:
- 是qualified name。
- 不是Elaborated type specifier的一部分(例如,以class、struct、union、enum為開頭的型別)
- 名稱不是用於指定基類繼承的列表中,也不是位於引入建構函式的成員初始化列表中。
- 依賴於模板引數。
例如:
template <typename(1) T>
struct S : typename(2) X<T>::Base {
S() : typename(3) X<T>::Base(typename(4) X<T>::Base(0)) {}
typename(5) X<T> f() {
typename(6) X<T>::C *p; // declaration of pointer p
X<T>::D *q; // multiplication!
}
typename(7) X<int>::C *s;
using Type = T;
using OtherType = typename(8) S<T>::Type;
};
下面逐一說下上面各個typename的使用場景(有的使用方式是錯誤的):
- 第一個typename代表一個模板引數,不在此文章討論範圍內。
- 第二和第三個typename是錯誤的使用方式,不需要新增,違反了上面的第3條規則。第二個出現在了指定基類繼承的列表中,第三個出現在了建構函式的成員初始化列表。如果加上typename編譯,會報如下錯誤:
main.cc:30:12: error: 'typename' is redundant; base classes are implicitly types
struct S : typename X<T>::Base {
^~~~~~~~~
- 第四個typename是必須的,它滿足上面第3條規則,且其他規則也滿足。
- 第五個typename是錯誤的,因為X
不是一個qualified name,如果加上typename編譯,會報:
main.cc:33:12: error: expected a qualified name after 'typename'
typename X<T> f() {
^
- 第六個typename是必須的,上面講過,代表一個型別。
- 第七個typename是可有可無的,因為
X<int>::C
不依賴模板引數,即不是Dependent Name. - 第八個typename也是可有可無的,因為它指向的是
current instantiation
,這個概念下篇文章會講到。
C++20 typename
是了,這一大堆亂七八糟的規則,誰也不想去記。C++20對typename的規則做了一些改善,有一些場景不再需要typename。詳情大家可以參考 : The typename disambiguator for dependent names
(完)
朋友們可以關注下我的公眾號,獲得最及時的更新: