c++11-17 模板核心知識(十四)—— 解析模板之依賴型模板名稱 Dependent Names of Templates(.template/->template/::template)

張雅宸發表於2020-12-08

有時間的建議先看下上篇文章 : c++11-17 模板核心知識(十三)—— 名稱查詢與ADL

tokenization與parsing

絕大多數語言在編譯的時候都有兩個階段:

  • tokenization,或者叫scanning/lexing
  • parsing

tokenization階段會讀取原始碼並生成一系列token. 例如:int *p = 0;,tokenizer會生成關鍵字int、運算子*、識別符號p、運算子=、整數0、運算子;

接下來,parser會遞迴的減少標記,尋找已知的模式。例如:token 0是一個合法的表示式,*p組合也是一個合法的宣告,它和後面的=0組合也是一個合法初始化宣告。最後,int是一個已知的型別,後面跟著初始化宣告 : *p=0,所以,我們得到了一個初始化p的宣告

解析模板之型別的依賴名稱 Dependent Names of Templates

關於模板解析有六個大方面:

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

這裡有一個很重要的概念 :c++11-17 模板核心知識(十三)—— 名稱查詢與ADL中介紹過的Dependent Name:依賴於模板引數的名稱,也就是訪問運算子左面的表示式型別依賴於模板引數。例如:std::vector::iterator是一個 Dependent Name,但假如T是一個已知型別的別名(using T = int),那就不是Dependent Name。

通常而言, 編譯器會把模板名稱後面的<當做模板引數列表的開始,否則,<就是比較運算子。但是,當引用的模板名稱是Dependent Name時,編譯器不會假定它是一個模板名稱,除非顯示的使用template關鍵字來指明,模板程式碼中常見的->template.template::template就應用於這種場景中。

下面看幾個例子。

Example One

template<unsigned long N>
void printBitset (std::bitset<N> const& bs) {
    std::cout << bs.template to_string<char, std::char_traits<char>, std::allocator<char>>();
}

這裡,引數bs依賴於模板引數N。所以,我們必須通過template關鍵字讓編譯器知道bs是一個模板名稱,否則按照上面的規則,<會被當做比較符——小於號。

Example Two

The template keyword as qualifier (C++ only)中的例子:

#include <iostream>
using namespace std;

class X {
   public:
      template <int j> struct S {
         void h() {
            cout << "member template's member function: " << j << endl;
         }
      };
      template <int i> void f() {
        cout << "Primary: " << i << endl;
      }
};

template<> void X::f<20>() {
   cout << "Specialized, non-type argument = 20" << endl;
}

template<class T> void g(T* p) {
   p->template f<100>();
   p->template f<20>();
   typename T::template S<40> s; // use of scope operator on a member template
   s.h();
}

int main()
{
   X temp;
   g(&temp);
}

這裡,引數p依賴模板引數T。注意typename T::template S<40> s;的使用。

Example Three

template <typename T> class Shell {
public:
  template <int N> class In {
  public:
    template <int M> class Deep {
    public:
      virtual void f();
    };
  };
};

template <typename T, int N> class Weird {
public:
  void case1(typename Shell<T>::template In<N>::template Deep<N> *p) {
    p->template Deep<N>::f();      // inhibit virtual call
  }

  void case2(typename Shell<T>::template In<N>::template Deep<N> &p) {
    p.template Deep<N>::f();      // inhibit virtual call
  }
};

引數p依賴模板引數T。編譯器不會去判斷p.Deep是不是模板。如果不指定template,那麼p.Deep<N>::f()就會被解析成((p.Deep)<N)>f();<被當做比較符。

基於上面的例子,我們也可以知道,->template.template::template只存在於模板中,並且是在Dependent Name的場景下使用(依賴於模板引數)。

(完)

朋友們可以關注下我的公眾號,獲得最及時的更新:

image

相關文章