contributed by < jeffrey.w
>
Template metaprogramming (TMP) 是一種從 template 的角度開發的 paradigm,也就是說他不是著眼在 int 或是 float 這種特定的 type,而是著眼在形而上的 type。原本我想用 concept
或是 generic
來說明,但由於 concept
和 generic
在 C++
另有其他的解釋,為避免混淆,所以這裡不會提到 concept
和 generic
。
C++11
從規格定義了 template,是學習 template 相當有用的第一手資料。本文是透過規格書學習 templates 的筆記,範圍包含 constexpr
、type traits
、partial specializations
和template constructor
,並且對於 compiler 在編譯時產生的 error 給出規格書上的解釋。
透過這份文件 N3377=12-0067
,可以知道 N3376 是最接近 C++11 規格的草案,所以這份筆記主要是以 N3376 為主。
先理解 C++11 規格書 提到的幾個知識點,對於後續內容的理解會有相當的幫助,例如知道 incomplete type 的定義,有助於在編譯錯誤時發現 incomplete type 的原因。
The void type is an incomplete type that cannot be completed.
A class that has been declared but not defined, or an array of unknown size or of incomplete element type, is an incompletely-defined object type.
依 C++11 規格, 3.9
的規範,class C 是 been declared but not defined
,屬於 incomplete type
。
用 -c
來看看操作 incomplete type 在 compile-time 會有什麼結果。
依 C++11 規格, 3.9
的規範,這個 arr 的 element 是 incomplete type。
所以編譯的時候也會產生錯誤
從 C++11 規格 [3.9.3
] 來看 cv-qualifiers
Each type which is a cv-unqualified complete or incomplete object type or is void(3.9) has three corresponding cv-qualified versions of its type: a const-qualified version, a volatile-qualified version, and a const-volatile-qualified version
也就是說,每一個 cv-unqualified 都有三個相對應的 cv-qualified:
同樣的,incomplete object type 和 void 也都會有上面所說的三個對應的 cv-qualified。
依 C++11 規格 [7.1.5/1
],constexpr
可以被用在 定義變數、宣告 function 或是 function template,也可以用在宣告 static data member,這裡先看其中兩個情況:
C++11 規格 [7.1.5/3
] 要求 constexpr function
要滿足以下的限制
舉一個例子來看最後一個限制 exactly one return statement
照 C++11 的限制,add 違反了這個限制 exactly one return statement
,所以在 compile-time 就會有 error。這裡使用 -S
這個參數 (Compile only),來突顯這個 error 是發生在 compile-time。
從 C++11 規格書 [20.9
] 來看 type traits,規格書是這麼說的:
… and perform type inference and transformation at compile time. It includes type classification traits, type property inspection traits, and type transformations.
…
The type property inspection traits allow important characteristics of types or of combinations of types to be inspected. The type transformations allow certain properties of types to be manipulated.
也就是說,type traits 是用來在 compile time 作型別檢查和型別轉換的。
C++11 規格書 [20.9.7.5
] 這麼說
If T has type “(possibly cv-qualified) pointer to T1” then the member typedef type shall name T1; otherwise, it shall name T.
依照 C++11 規格書 [20.9.7.5
],上述的三種 type 各自代表的型別如下:
在 variadic templates 還沒有納入 C++11
規格書之前,如果要模擬 variable-length templates 的效果,主要是透過 overloads 來實現 [link
]。也就是說,先假設 最多會有 N 個 template arguments,然後針對 0 ~ N 個 template arguments 分別撰寫 overloads 的 function templates。
這個方法顯然在維護或是擴充都是很辛苦的,但自從 variadic templates 納入 C++11 規格 [14.5.3
] 後,在維護和擴充上都獲得了顯著的改善,不用再針對不同個數的 template arguments 撰寫各自的版本了。
C++11
規格書在 §20.9
描述了在 compile-time 執行型別推論和型別轉換的 component [1
],例如在 compile-time
查詢 type 的 property (§20.9.5
)、兩個 type 之間的關係 (§20.9.6
)。
舉個例子來實驗看看,這個範例改寫自 C++11 規格書 §20.9.6
assert
是一個 function-like macro
,以上面的 source code 來說明什麼是 function-like macro
,像以下兩種 preprocessing token
,對 assert
的 argument
來說,是不一樣的
(std::is_base_of<B1, D>::value)
是一個 preprocessing token
assert(std::is_base_of<B1, D>::value)
沒有加括號,所以 preprocessing token
就會有兩個,第一個是 std::is_base_of<B1
,第二個是D>::value
,於是會有以下的 compile error。注意看第二行,因為沒有加括號,所以 std::is_base_of<B1, D>::value
被視為兩個 arguments
constructor
有三種: default constructor
、copy constructor
、move constructor
,先看 non-template
的 copy constructor
在 C++11 規格裡怎麼說:
[C++11, §12.8
]
A non-template constructor for class X is a copy constructor if its first parameter is of type X&, const X&, volatile X& or const volatile X&, and either there are no other parameters or else all other parameters have default arguments
這是 first parameter 是 const X& 的例子
編譯後,執行時會得到這個結果
如果把 copy constructor
改寫成 template 的話,會是如何運作的?
編譯後,執行時會得到這個結果
這裡寫一段 code 實驗一下 C++ 常見的 template pattern - Curiously Recurring Template Pattern (CRTP),這個 pattern 是由 James Coplien 在 1996 年的 C++ Gems 裡初次提出。
dynamic cast
C++11 5.2.7/1 對 dynamic_cast 有限制不能對 const 作轉型
The dynamic_cast operator shall not cast away constness
所以,這段 code 在 compile-time 會有 error
compile-time 所產生的 error conversion casts away constness
就是 C++11 規格 [5.2.7/1
] 所說的 shall not cast away constness
C++11 規格 [10.3 Virtual functions
] 是這樣定義多型的:
A class that declares or inherits a virtual function is called a polymorphic class
所以,class Base 和 class Derived 都屬於 polymorphic class
static_cast
,舉一個例子來看 CRTP從 C++11 規格來看以上這個例子在 compile-time 發生了什麼事?
以下這段 code 在直覺上會以為 a2.f()
是呼叫到 template<class T, int I> void A<T, I>::f() { };
但這個直覺是錯的,因為 C++11 規格, 14.5.5.3
這麼說
A class template specialization is a distinct template. The members of the class template partial specialization are unrelated to the members of the primary template.
也就是說,A<T, I>::f()
和 A<T, 2>::f()
是 unrelated。
所以,不會因為 A<T, 2>::f()
沒有定義就呼叫 A<T, I>::f()
,a2.f()
仍然是需要 A<T, 2>::f()
的定義。因此,上面那段 code 在 compile 的時候會出現以下的 error:
這是兩個不一樣的 template,這兩個 template 彼此之間沒有關係
所以,A<T, I>::f() 有定義實作,不表示 A<T, 2>::f() 也有定義實作
OS:
Compiler:
c++11
c++
N3376
c++ templates
compile-time
Template Metaprogramming
TMP
type traits
constructor template
partial specializations