c++11-17 模板核心知識(十五)—— 解析模板之依賴型類型名稱與typename Dependent Names of Types

上篇文章c++11-17 模板核心知識(十四)—— 解析模板之依賴型模板名稱 Dependent Names of Templates(.template/->template/::template) 介紹了依賴型模板名稱,提到關於模板解析有六個大方面:c++

  • 非模板中的上下文相關性 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)。git

模板名稱的問題及解決

模板中的名稱存在一個問題:它們有的時候不能被很好的分類,好比一個模板引用其餘模板的名稱,由於模板特化的存在,會讓問題變得複雜一些。例如:github

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(); }

若是你直接編譯,會報錯:express

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++11

C++規定,只有當加上typename關鍵字後,依賴型類型名稱纔會被當作類型,不然會被當作一個值。這裏typename的意義和聲明一個模板時使用的typename是兩個意思,因此不能用class來替換typename.code

typename規則

當一個名稱具有如下性質時,須要在名稱前面加typename:blog

  • 是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

image

(完)

朋友們能夠關注下個人公衆號,得到最及時的更新:

image

相關文章
相關標籤/搜索