1. 程式人生 > >c++11-17 模板核心知識(十五)—— 解析模板之依賴型型別名稱與typename Dependent Names of Types

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

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