---
tags: C++
---
* 參考:
* forwarding(universal) reference 的各種奇葩事
* https://stackoverflow.com/questions/38814939/why-adding-const-makes-the-universal-reference-as-rvalue
* C++14 **variable** template
# C++ Primer Chapter 16 Templates and Generic programming
* Both OOP and generic programming deal with types *that are not known at the time the program is written.*
* OOP deals with types that are not known ***until run time,**
* whereas in generic programming the **types become known during *compilation*.**
* When we write a generic program,
* **we write the code in a way that is independent of any particular type.**
* type 是 user code 提供
* 你寫的 template,同時也代表著提供的型態必須滿足什麼需求,只要滿足就可以使用 template
* named requirements
* A template is a **blueprint or formula** for creating classes or functions.
* supply the information needed to **transform that blueprint into a specific class or function**.
* That transformation **happens during compilation.**
## 16.1 Defining a Template
* template 想解決的問題: 幾乎同樣的 code 寫很多遍,只差在型別不同
* 例如 compare 兩個 value,原本我們會想要寫多種不同型態的版本:
```cpp
// returns 0 if the values are equal, -1 if v1 is smaller, 1 if v2 is smaller
int compare(const string &v1, const string &v2) {
if (v1 < v2) return -1;
if (v2 < v1) return 1;
return 0;
}
int compare(const double &v1, const double &v2) {
if (v1 < v2) return -1;
if (v2 < v1) return 1;
return 0;
}
```
* 長的 87% 像
* 某種程度上就是 repeat yourself
* 而且如果要這樣寫,我們一定得知道要比較的 type 是什麼
* 如果 user code 需要使用我們的 code,但是是想要比較他自己寫的 type,就不能用了
### 16.1.1 Function Templates
* A function template is a *formula* from which we can **generate type-specific versions of that function.**
* 上面的 compare 可以變成這樣:
```cpp
template <typename T>
int compare(const T &v1, const T &v2) {
if (v1 < v2)
return -1;
if (v2 < v1)
return 1;
return 0;
}
```
* keyword `template`,後面跟著 *template parameter list*
* 是一或多個 *template parameters*,用角括號包起來
* 注意 list 不能為空
* temp para list 跟 funcion para list 有點像
* funcion para list 定義 function parameter 的型態,但沒有說如何初始化
* 在 run time 的時候才會由 function call 給定的 initializer 初始化這些變數
* 而 temp para list 代表了使用在 function(或 class) template 內會用到的 type 或 value
* 當我們使用 template 時,我們會提供 **template *arguments*** -*不管是直接還是間接提供*-
* 這些 arguments 會被 bind 到 template parameters
* 我們的 `compare` function template 定義了一個 template parameter `T`
* 在 template 內,我們直接把 `T` 當成 type 來用
* 至於 `T` 最後到底會代表什麼,端看 user code 怎麼使用 template,**而且會在 compile time 時決定**
#### **Instantiating** a Function Template
* 當我們*呼叫* function template 時,compiler 會用你提供的 function argument(s) **的型態** 來推斷 template arguments 是什麼
* 例如下面的 call:
```cpp
cout << compare(1, 0) << endl; // Tis int
```
* arguments 型態是 `int`,compiler 會推斷 `compare` 定義的 `T` 是 `int`,然後把 `int` bind 到 `T` 上
* compiler 最後會在編譯時利用上面推斷出來的 `T` **實例化 instantiate** 出一個 function,
* 下面是使用 template 的 code:
```cpp
// instantiates int compare(const int&, const int&)
cout << compare(1, 0) << endl; // T is int
// instantiates int compare(const vector<int>&, const vector<int>&)
vector<int> vec1{1, 2, 3}, vec2{4, 5, 6};
cout << compare(vec1, vec2) << endl; // T is vector<int>
```
* compiler 會實例化出兩個不同版本的 `compare` functions
* 第一個會把 `T` 替換成 `int`:
```cpp
int compare(const int &v1, const int &v2) {
if (v1 < v2) return -1;
if (v2 < v1) return 1;
return 0;
}
```
* 第二個會把 `T` 替換成 `vector<int>`,就不再打一份了
* *注意 `const` 是原本 template 定義時就有的,而不是包含在 compiler 推斷的 type 內*
* 術語上會說這些 compiler 生出來的 function 是 **an instantiation of the template**
#### Template Type Parameters
* template parameters 又有分兩種,type parameter 跟 nontype parameter,先介紹第一種
* 上面的 `compare` 的 `T` 就屬於這種
* template type parameters 可以拿來放在 template 內**需要 type** 的地方,
* 講得更精簡就是,可以在 template 內直接把 type parameter 當作 type specifier 來用
* 例如:
* 宣告變數時
* 宣告 function template 時 function paramter 的型態
* return type
```cpp
// ok: same type used for the return type and parameter
template <typename T> T foo(T* p) {
T tmp = *p; // tmp will have the type to which p points
// ...
return tmp;
}
```
* 上面的 code 三種都用到了
* 在 temp para list 內要宣告 type parameter 時,必須在 parameter 前面加上 keyword `typename` 或 `class`
* 他們兩個本質上沒有差別,都是代表要宣告 type parameter
```cpp
// error: must precede U with either typename or class
template <typename T, U> T calc(const T&, const U&);
```
* 用 `typename` 比較清楚就是,只是這是比較晚期的標準才有的,早期 code 都只有用 `class`
#### Nontype Template Parameters
* 另一種類型的 templare parameters 就是 **nontype** template parameters
* A nontype parameter **represents a (*constant expression*) value rather than a type.**
* 宣告的時候不是用 `typename/class` 宣告,而是真正的 type:
* 在 template 實例化時,nontype temp para 出現的地方,會被替換成 user 提供的 value,或者由 compiler 推斷(之後細講 compiler 怎麼推斷)
* **既然這個 value 是在實例化時,也就是 compile time 時,就要決定的,換句話說它要是 constant expression**
* 我們可以再寫一個 `compare` 的 function template:
```cpp
template<unsigned N, unsigned M>
int compare(const char (&p1)[N], const char (&p2)[M]) {
return strcmp(p1, p2);
}
```
* 注意 `N M` 是用 `unsigned` 宣告的而不是 `typename/class`
* 這個 `compare` 是要拿來處理 C string literals 時用的
* string literal,也就是 `const char` array
* 我們希望可以用這個 compare 來比較任何不同長度的 string literals,所以我們就用 nontype temp para `M N` 來代表長度,`N`/`M` 代表第一/二個 literal 的長度
* 而 compiler 會在你使用這個 template 時由 function argument 推斷 `N M`
* 當我們這樣 call:
```cpp
compare("hi", "mom")
```
* 會生出這種 function:
```cpp
int compare(const char (&p1)[3], const char (&p2)[4])
```
* 這時 compiler 就會由 literal 的長度去推斷 `N M` 了
* 注意 string literal 有 null character,所以 size 會 + 1
* 題外話,其實你可以用兩個不同的 type parameter 也可以做到一樣的事情,不一定要用 nontype parameter:
```cpp
template <typename T, typename U> int comp(T &a1, U &a2) {
return strcmp(a1, a2);
}
```
* 注意 `T` 跟 `U` 一樣要有 reference,推斷出來的 function 參數型別才會跟上面 nontype 版本的一樣;如果沒有 reference 則會推出 `const char*`
* 但還是會動就是了
* 題外話,個人覺得 16.1.1 練習寫 `std::begin` 很有趣
* 可以看 cppreference,看 `std::begin` 該怎麼宣告
* nonarray 跟 built-in array 不同
* 再來講什麼樣的型別可以拿來宣告 nontype temp para:
* integral type
* pointer
* (lvalue) ref
* 另外再說一次,**要綁到 nontype temp para 的 arguments,一定要是 constant expression**
* integral type 比較好懂
* ptr/ref 則是要綁到有 static lifetime 的物件,例如 global variable, static variable,或 function
* *位置* 不會變
* ptr 可以用 `nullptr` 或 0 來實例化
* 你不信你可以綁個 nonstatic 的試試看,compiler 推斷它不是 constant expression 之後就不知道怎麼實例化,就會噴你 error
* 因為只有 static lifetime 的物件才有辦法在 compile time 時就決定要擺哪裡
* 包括 global 變數,function 內的 `static` 變數,`static` member
:::warning
* Template arguments used for nontype template parameters **must be constant expressions.**
:::
#### `inline` and `constexpr` Function Templates
* function template 一樣可以宣告成這兩種,格式如下:
```cpp
// ok: inline specifier follows the template parameter list
template <typename T> inline T min(const T&, const T&);
// error: incorrect placement of the inline specifier
inline template <typename T> T min(const T&, const T&);
```
* `inline/constexpr` 宣告在 template parameter list 角括號後面
#### **Writing Type-Independent Code**
* `compare` function illustrates two important principles for writing generic code:
* The *function parameters* in the template are references to `const`.
* The tests in the body **use only `operator<()` comparisons.**
* 用 `const` ref,我們的 code 可以用在不能被 copy 的物件
* (copyable)物件如果很大的話也不會 copy
* 其實就跟寫一般的純讀取 function 的 principle 一樣
* 再來是 `operator<()`:
* 一開始可能會覺得雖然看得懂 `compare`,可是有點奇怪,為什麼不再用個 `operaot>()` 呢?
```cpp
// expected comparison
if (v1 < v2) return -1;
if (v1 > v2) return 1;
return 0;
```
* 原因很簡單,只要參數的型別有定義 `operator<()` 就可以用這個 template
* 如果寫成上面那樣子,參數型別還要定義 `operator>()`
* 如果要讓這個 `compare` 更 general,更 portable,我們甚至不該用小於,要用 `less`(14.8.2):
```cpp
// version of compare that will be correct even if used on pointers; see § 14.8.2 (p. 575)
template <typename T> int compare(const T &v1, const T &v2) {
if (less<T>()(v1, v2)) return -1;
if (less<T>()(v2, v1)) return 1;
return 0;
}
```
* ~~這樣連 pointer 都可以用~~
* 但還是不要隨便比較 pointer,不管是否指向同個 range
:::info
* Best Practice: Template programs should **try to minimize the number of requirements** placed on the argument types.
:::
#### Template Compilation
* 當 compiler 看到 template definition 時,**它不會 gen code;**
* 只有 user code 使用 template 時,compiler 才會根據 user code 提供的 argument 來 gen code
* 也就是實例化一個特定版本的 function instantiation
* The fact that code is generated only when we use a template (and not when we define it) **affects how we organize our source code and when errors are detected.**
* 之後會看到 template 應該怎麼*宣告*和*定義*,並且該擺放在 source code 的哪個地方
* 以下是一般 function 和 class 的使用狀況:
* 原本在呼叫一般的 function 時,compiler 只需要看到 function **declaration** 就可以了,definition 不需要看到
* class member 也是類似,使用 class 時,class definition 必須要有,但是 member function(s) 的 definition 不需要看到,只需要 declaration
* 因為這樣,我們通常會把 function declaration 跟 class definition 放在 header,而 function definition 跟 member function definition 放在 cpp 檔案(source file)內
* 題外話,`inline` (member) function 除外,`i`nline 必須看到 definition,必須放在 header
* 但是 template 不同!:
* 就想成,為了要實例化一個 function,compiler 當然要能看到 function template 的定義才有辦法生出一個完整的 function;
* member function 也是一樣,例如 `vector<T>::push_back` 這種,它一定要能看到 `push_back` 的 template definition 的定義才能生出對應的 member function
* 所以如果定義 template,通常會把 template 的宣告**跟定義**都放在 header 內
:::info
#### KEY CONCEPT: TEMPLATES AND HEADERS
* 整份 template *內* 的 names 可以分為兩種
* 沒有依賴 template parameters
* 有依賴 template parameters
* template provider,也就是實作 template 的人,必須要保證:
1. 那些在 template 內被用到的,但是沒有依賴 template parameters 的 names,當 user code 使用 template 時,必須是可見的 (visible)
* visible 就是看得到名字的宣告就好,不需要看到這種名字的定義
2. 還有 template 的 definition,包刮 function template 的 definition,class template 的 member function 的 definition,也要在 template 被使用時是可見的
* 而 user code 必須要保證:
* 提供給 template 的 template arguments,只要跟這些 arguments 有關的,且需要被 template 使用到的 function declarations,operators,types,在 template 實例化時,是要可見的
* 比方說 template 需要用到 user code 給的 template parameter type 的 `operator+`,那 user code 提供的型別的 `operator+` 在 template 實例化時是要可見的
* 上面的兩點只要有按照 C++ pracite 做 code organization,都很容易達成
* template provider 要提供一個同時有宣告跟定義 template 的 header
* user 則是在使用 template 時,要把丟給 template 的參數有關的型別都 include,使得 template defnition 看的到這個型別相關(相依)的 function/operators/types
:::
#### **Compilation Errors Are Mostly Reported during Instantiation**
* 是一位很愛編譯時期決定的朋友呢!!
* C++20 有 concept 之後狀況會好很多
* **learn about compilation errors in the code inside the template**
* compiler 編譯 template 的 code 分很多階段:
* The first stage is when we compile the template itself.
* *generally can’t find many errors at this stage.*
* something like syntax error
* The second error-detection time is when the compiler sees a use of the template.
* *still not much the compiler can check.*
* typically will check that the number of the arguments is appropriate
* also detect whether two arguments that are supposed to have the same type do so.
* 例如你 template parameter 都是給 T,結果兩個參數型別卻不一樣
```cpp
template <typename T> int f(T t1, T t2);
f(1, 0.87); // error
```
* 但也只能檢查這些了
* **The third time when errors are detected is during instantiation.**
* It is only then that **type-related errors** can be found.
* Depending on how the compiler manages instantiation, *these errors may be reported at link time.*
* 我們在寫 template 時,我們要越 generic 越好,可是還是會對傳入的 type 有一些假設(requirements):
* 例如我們的 `compare` 就假設 type 要支援 `operator<`
* 還可以把 `compare` 的 code 一行一行來看它依賴什麼假設:
```cpp
if (v1 < v2) return -1; // requires < on objects of type T
if (v2 < v1) return 1; // requires < on objects of type T
return 0; // returns int; not dependent on T
```
* 還有可以想為什麼 compiler 在編譯 template 本身時無法做太多 error checking,例如上面使用到的 `<`
* 它一定要知道 `T` 的型別才能知道 `T` 支不支援 `operator<()`,所以它無法在編譯 template 時驗證 `v1 < v2` 是否合法
* 如果這樣寫:
```cpp
Sales_data data1, data2;
cout << compare(data1, data2) << endl; // error: no < on Sales_data
```
* compiler 就會在實例化的時候發現 `Sales_data` 沒有定義 `operator<()`,然後噴 error
:::warning
* It is up to the caller to guarantee that **the arguments passed to the template support any operations that template uses**,
* and that those operations **behave correctly** in the context in which the template uses them.
* behave correctly,例如你傳進去給 `sort` 的 predicate 要滿足 strict weak ordering 一樣;如果你只是把 API 接起來(亦即,傳入一個 signature 一樣的 function)卻不滿足 `sort` 要的條件,行為就會是 UB
:::
### 16.1.2 Class Templates
* blueprint for generating classes.
* differ from function templates in that the *compiler cannot deduce the template parameter type(s) for a class template.*
* to use a class template we **must supply additional information inside angle brackets**
* `vector<int>`
#### Defining a Class Template
* 直接把之前寫的把 `StrBlob` 擴展成 `Blob`:
* 為什麼好死不死要用一個 pointerlike 的型態來講解 QQ,自幹個 vector 不好嗎
* As with the library containers, our users will have to specify the element type when they use a `Blob`.
```cpp
template <typename T> class Blob {
public:
typedef T value_type;
typedef typename std::vector<T>::size_type size_type; // 這行 typedef 中間的 typename 之後會講解
// constructors
Blob();
Blob(std::initializer_list<T> il);
// number of elements in the Blob
size_type size() const { return data->size(); }
bool empty() const { return data->empty(); }
// add and remove elements
void push_back(const T &t) {data->push_back(t);}
// move version; see § 13.6.3 (p. 548)
void push_back(T &&t) { data->push_back(std::move(t)); }
void pop_back();
// element access T& back();
T& operator[](size_type i); // defined in § 14.5 (p. 566)
private:
std::shared_ptr<std::vector<T>> data; // throws msg if data[i] isn’t valid
void check(size_type i, const std::string &msg) const;
};
```
* 只會吃一個 template type parameter `T`
* 跟 function template 一樣,`T` 可以用在很多地方
* 注意 `size_type` 的 `typedef` 那一行的 `typename` 是必須的,之後會講語法,(16.1.3 p.669 最下方)
* We **use the type parameter anywhere we refer to the element type** that the `Blob` holds.
* 例如 `back` 跟 `operator[]` 都是回傳 `T&`
* When a user instantiates a `Blob`, these uses of `T` will be replaced by the specified template argument type.
* 其實長得跟 `StrBlob` 很像,只是多了 `template` 宣告,然後 `string` 的地方都替換成 `T`
#### Instantiating a Class Template
* need to supply **explicit template arguments** that are bound to the template’s parameters.
* The compiler uses these template arguments to **instantiate a specific class** from the template.
* 要用 `Blob` 時就跟用 `vector` 一樣要提供 element type:
```cpp
Blob<int> ia; // empty Blob<int>
Blob<int> ia2 = {0,1,2,3,4}; // Blob<int> with five elements
```
* 上面兩個都會用 `Blob<int>` 這個被 template 實例化出來的 class(下面是 pseudo code):
```cpp
template <> class Blob<int> {
public:
typedef int value_type;
typedef typename std::vector<int>::size_type size_type;
Blob();
Blob(std::initializer_list<int> il);
// ...
int& operator[](size_type i);
private:
std::shared_ptr<std::vector<int>> data;
void check(size_type i, const std::string &msg) const;
};
```
* rewrites the `Blob` template, replacing each instance of the template parameter `T` by the given template argument, which in this case is `int`.
* The compiler *generates a different class* for each element type we specify:
```cpp
// these definitions instantiate two distinct Blob types
Blob<string> names; // Blob that holds strings
Blob<double> prices; // different element type
```
* Note: Each instantiation of a class template *constitutes an **independent** class.*
* The type `Blob<string>` has no relationship to, or any special access to, the members of any other `Blob` type.
#### References to a Template Type in the Scope of the Template
* 題外話,標題的 "reference" 就只是普通英文的 reference
* In order to read template class code, it can be helpful to remember that **the name of a class template is *not* the name of a type** (§ 3.3, p. 97).
* `template_name` 本身不是 type,`template_name<T>` 才是
* 但是通常 template 內部實務上很少直接用 `template_name<T>`,比較長單獨用 `T`
* 例如 `Blob` 內:
* `std::shared_ptr<std::vector<T>> data;`
* 直接把 `Blob` template parameter,**當成 STL 的 template argument**
#### Member Functions of Class Templates
* 跟一般 class 一樣,class templates 可以把 member function 定義在 class template body 裡面或外面
* 定義在 body 內的一樣是 implicitly `inline`
* class template member function 還是屬於某個 class template instantiation,所以定義在 body 外面時,前面也要加上 `template` keyword,就如同定義 function template 那樣
* 跟一般 member function 一樣定義在外面時要加 class scope,class template member function 也要:
```cpp
ret-type StrBlob::member_name(parm_list)
```
* 上面是一般 class member function 定義在 class 外的格式
```cpp
template <typename T>
ret-type Blob<T>::member_name(parm_list)
```
* 這是 class template member function;**注意 class template name 不是 type name 這個觀念**,加上 template arguments 才是(例如,`Blob` 不是 type,`Blob<int>` 才是)
* 所以我們應該是要在 scope operator 前面加 `Blob<T>` 而不只是 `Blob`
#### The `check` and Element Access Members
* 長得跟 `StrBlob` 原本定義的很像
```cpp
template <typename T>
void Blob<T>::check(size_type i, const std::string &msg) const {
if (i >= data->size()) throw std::out_of_range(msg);
}
```
* 一樣,`operator[]` 跟 `back` 會使用 `check` 這個 utility
* `operator[]` 跟 `back` *把 return type 改成 template paramter*:
```cpp
template <typename T>
T& Blob<T>::back() {
check(0, "back on empty Blob");
return data->back();
}
template <typename T>
T& Blob<T>::operator[](size_type i) {
// if i is too big, check will throw, preventing access to a non existent element
check(i, "subscript out of range");
return (*data)[i];
}
```
* `pop_back` 就更像 `StrBlob` 定義的,因為他連 return 都沒有:
```cpp
template <typename T> void Blob<T>::pop_back() {
check(0, "pop_back on empty Blob");
data->pop_back();
}
```
* 除了這些,`operator[]` 跟 `back` 還有 `const` overload,留在習題定義;還有 `front`
#### `Blob` Constructors
* 跟一般 member function 一樣,如果定義在 body 外也要加一些 template 的格式:
```cpp
template <typename T> Blob<T>::Blob():
data(std::make_shared<std::vector<T>>()) { }
```
* 注意是寫成 `Blob<T>::Blob()`
* 原因是因為,`Blob<T>` 是 class,`Blob<T>::Blob` 是 member function,他就只是個 member function,不需要加 template parameter
* 其他 member function 也同理,`T` 只有寫一次
* 吃 `initializer_list` 的 ctor:
```cpp
template <typename T> Blob<T>::Blob(std::initializer_list<T> il):
data(std::make_shared<std::vector<T>>(il)) { }
```
#### Instantiation of Class-Template Member Functions
* **By default, a member function of a class template is instantiated only if the program uses that member function.**
* compiler 很懶,你用到了某個 class-template 的 member function 之後它才會實例化這個 member function
* 其實就跟你一般的 member function 一樣,只要沒用到,就算沒有 definition 也沒關係
* 例如下面的 code:
```cpp
// instantiates:
// Blob<int> class definition
// and the initializer_list<int> constructor
Blob<int> squares = {0,1,2,3,4,5,6,7,8,9};
// instantiates Blob<int>::size() const
for (size_t i = 0; i != squares.size(); ++i)
squares[i] = i*i; // instantiates Blob<int>::operator[](size_t)
```
* compiler 編譯完這段 code 之後,compiler 只會實例化 `Blob<int>`, 還有它的三個 member functions: `size`, `operator[]`, `ctor(il)`
* compiler 其實不是很懶,這種「沒用到就不實例化的機制」有個好處
* 提供給 template 的 type,就算沒有符合 class template 的某些 member functions 的要求
* 只要 user code 沒有使用那些 member functions,還是可以用這個 type 實例化 class
* 例如使用 `std::vector<Sales_data>`,只要不對這種 vector 做比大小都不會有事
#### Simplifying Use of a Template Class Name inside Class Code
* 之前說要在用 class template 時一定要提供 type argument(s) 其實有例外:
* 當你已經在 class template scope 內時,就可以不用提供
* 用 `BlobPtr` 舉例:
```cpp
// BlobPtr throws an exception on attempts to access a nonexistent element
template <typename T> class BlobPtr {
public:
BlobPtr(): curr(0) { }
BlobPtr(Blob<T> &a, size_t sz = 0):
wptr(a.data), curr(sz) { }
T& operator*() const {
auto p = check(curr, "dereference past end");
return (*p)[curr]; // (*p)is the vector to which this object points
}
// increment and decrement
BlobPtr& operator++();
BlobPtr& operator--();
// prefix operators
private: // check returns a shared_ptr to the vector if the check succeeds
std::shared_ptr<std::vector<T>>
check(std::size_t, const std::string&) const;
// store a weak_ptr, which means the underlying vector might be destroyed
std::weak_ptr<std::vector<T>> wptr;
std::size_t curr; // current position within the array
};
```
* 仔細看 `operator++/--` 的 return type 是 `BlobPtr`
* ctor 也沒有寫 `<T>`
* 當你在 class template scope 內時,**compiler 會把 reference to class template itself 當成我們已經提供 template arguments 的樣子**:
```cpp
BlobPtr<T>& operator++();
BlobPtr<T>& operator--();
```
* 不知道實務上偏好哪個...,你要把 `<T>` 寫出來也合法就是
#### Using a Class Template Name outside the Class Template Body
* We must remember that we are not in the scope of the class *until the class name is seen*
```cpp
// postfix: increment/decrement the object but return the unchanged value
template <typename T>
BlobPtr<T> BlobPtr<T>::operator++(int) {
//^ not in scope ^ in scope
// no check needed here; the call to prefix increment will do the check
BlobPtr ret = *this; // save the current value
++*this; // advance one element; prefix ++ checks the increment
return ret; // return the saved state
}
```
* 寫 return type 時還沒在 scope 內,所以 `<T>` 要寫出來
* 在定義 `ret` 時已經在 scope 內,所以宣告時的 type specifier,也就是 `BlobPtr`,不用加 `<T>`,如同下面這樣:
```cpp
BlobPtr<T> ret = *this;
```
* 結論: 在 scope 內,我們直接使用 template 名字時可以不用加 template arguments
#### Class Templates and Friends
* friendship 的任一方都可以是 template or not
* 普通 class 有普通 friend
* class template 有普通 friend
* class 有 template friend
* class template 有 template friend
* 如何給 friendship 取決於 class (template) 本身
* 一個 class template 如果有 nontemplate `friend`s,這個 `friend` 該 class template 的所有 instantiations 都有這個 `friend`
* 一個 class template 如果某 friend 也是 template,**class 可以決定哪些 instantiations 是 friend**
#### One-to-One Friendship
* 在 class 跟 friend 都是 templates 的情況下,最常見的 friendship 就是,用 `T` 實例化的 class template instantiation 有一個用 `T` 實例化的 friend instantiation
* 例如希望 `Blob` 可以跟 `BlobPtr` 還有 `operator==` 成為 friend
* 可是這樣講太攏統,因為他們三個都是 template
* one-to-one friendship 就是說,**可以具體指定,他們三個各自用相同 type 實例化出來的 instantiations 互為 friend**
* `Blob<int>` 有 `BlobPtr<int>` 還有 `operator==<int>` 這兩個 `friend`s
* 不過如果要在 class template 內說某 template 的某個 instantiation 是我的 friend,我們必須**在定義該 class template 之前先*宣告*這個 friend template**
* template 也可以只單純宣告,不提供定義
* 畢竟你要說某 template 的 instantiation 是 friend,你就要給定 template argument 才能得到 instantiation
* 要給定 template argument 就至少要知道被使用的 friend 是 template,所以就要看到 template 宣告
#### template declaration
* A template declaration includes the template’s template parameter list:
```cpp
// **forward declarations** needed for friend declarations in Blob
template <typename> class BlobPtr;
template <typename> class Blob; // needed for parameters in operator==
template <typename T>
bool operator==(const Blob<T>&, const Blob<T>&);
template <typename T> class Blob {
// each instantiation of Blob **grants access to** the version of
// BlobPtr and the equality operator **instantiated with the same type**
friend class BlobPtr<T>;
friend bool operator==<T> (const Blob<T>&, const Blob<T>&);
// other members as in § 12.1.1 (p. 456)
};
```
* 一個一個看每個 template 的**宣告**是為了用在哪裡:
* 首先如果只是宣告 template,可以在 parameter list 那邊(也就是腳括號裡面)只打 keyword `template` 而不用給 parameter name
* 就跟 function declaration 類似
* `BlobPtr` 的宣告是為了用在 `Blob` template 內的 friend declaration
* `Blob` 的宣告是為了用在 `operator==` template 內的 function parameter 內
* `operator==` 的宣告是為了用在 `Blob` template 內的 friend declaration
* 再來看 `Blob` 內的 friend declaration,到底在說明哪個 instantiation(s) 跟哪個 instantiation(s) 是 friend:
* 在 `Blob` 內,不管是 `BlobPtr` 還是 `operator==` 都是用了 `Blob` 的 template parameter,也就是 `<T>`,當作 `BlobPtr` 跟 `operator==` 的 template argument 來宣告
* 這意味著只有用相同的 type 來實例化的 `BlobPtr` 跟 `operator==` 才會是 `Blob` 的 friend
* 看例子:
```cpp
Blob<char> ca; // BlobPtr<char> and operator==<char> are friends
Blob<int> ia; // BlobPtr<int> and operator==<int> are friends
```
* `BlobPtr<int>` 還有 `operator==<int>` 跟 `Blob<int>` 是朋友,可是跟 `Blob<char>` 就不是
#### General and Specific Template Friendship
* 上面 demo 讓同 type 的 instantiations 才有 friendship
* 這裡來看更 general 的情況
* 亦即一個 class 可以讓某 template 的所有 instantiations 都成為 friend
* 或者只讓部分成為 friend
```cpp
// forward declaration necessary to be friend a specific instantiation of a template
template <typename T> class Pal;
class C { // C is an ordinary, nontemplate class
friend class Pal<C>; // Pal instantiated with class C is a friend to C
// all instances of Pal2 are friends to C;
// no forward declaration required when we befriend all instantiations
template <typename T> friend class Pal2;
};
template <typename T> class C2 { // C2 is itself a class template
// each instantiation of C2 has the same instance of Pal as a friend
friend class Pal<T>;
// a template declaration for Pal must be in scope
// all instances of Pal2 are friends of each instance of C2, prior declaration "not"(Primer 好像寫錯?? 自己測 code 是不需要先宣告 Pal2) needed
template <typename X> friend class Pal2;
// Pal3 is a nontemplate class that is a friend of every instance of C2
friend class Pal3; // prior declaration for Pal3 not needed
};
```
* 一樣慢慢來看:
* `Pal` 要先宣告的原因,是因為 `class C` 要用它來宣告特定的 instantiation(也就是用 `C` 實例化的 `Pal`)成為 friend
* 注意 C 是一般的 class
* 如果你希望某個 template 的所有 instantiations 都跟我是 friend,你就不用先宣告它是 template 了
* 例如在 `class C` 內宣告成 `friend` 的 `Pal2`
* 還有在 class template `C2` 內宣告成 `friend` 的 `Pal2`
* 阿在 `C2` 內的 `Pal3` 就只是一般的 class 而已,要成為 friend 也不用先宣告
* 還有一點要注意
* 如果希望在 class template 內,某 template 的所有 instantiations 都跟我是 friend,**在 class template body 內 friend declaration 時給定的 template parameter(s) 要跟 class template 的 template parameter(s) 的名字不一樣!**
* 例如上面在 class template `C2` 內宣告的 `Pal2`,它用的 template parameters 是 `X`,跟 `C2` 用的 `T` 不一樣
* 還有在 `C` 裡面宣告的 `Pal2` 用的是 `T`,可是 `C` 只是一般 class 所以也沒衝突
* 再來 `C` 內宣告的 `Pal<C>` 只是某個特定 instantiation,不是 template 宣告,所以沒有「讓所有 instantiations 都變成 friend」的需求
* 總結,你的 template friendship 可以一對一(同 type 對同 type),一對全部(同 type 對所有 types),還有一對特定(自己指定,例如上面 `C` 裡面的 `Pal<C>`)
* 最常見的還是一對一,其他的用到再查
#### Befriending the Template’s Own Type Parameter
* C++11 可以直接把 type parameter 當成 `friend`
```cpp
template <typename Type> class Bar {
friend Type; // grants access to the type used to instantiate Bar
// ...
};
```
* 不知道具體使用時機,只知道你給定什麼 type 到 template,這個 type 就有能力 access template implementation,很詭異
* 至少 STL container 都不能這樣使用
* For some type named `Foo`, `Foo` would be a `friend` of `Bar<Foo>`, `Sales_data` a `friend` of `Bar<Sales_data>`, and so on.
#### Template Type Aliases
* 把某個 template instantiation alias 成一個 type:
```cpp
typedef Blob<string> StrBlob;
```
* 你不能 `typedef` 一個 template name
```cpp
typedef NewType std::vector<T>
```
* 這樣很奇怪,根本不知道 `T` 是啥
* 但在 C++11 可以這樣寫:
```cpp
template<typename T> using twin = pair<T, T>;
twin<string> authors; // authors is a pair<string, string>
```
* 還算好理解,alias 出來的 name(在上面的例子就是 `twin`)他還是個 template,不是型別
* 另外 `using` = 右邊的東西如果有用到 template 的話,還可以固定他的某些 type parameter(s):
```cpp
template <typename T> using partNo = pair<T, unsigned>;
partNo<string> books; // books is a pair<string, unsigned>
partNo<Vehicle> cars; // cars is a pair<Vehicle, unsigned>
partNo<Student> kids; // kids is a pair<Student, unsigned>
```
#### `static` Members of Class Templates
* class template 也可以定義 `static` members:
```cpp
template <typename T> class Foo {
public:
static std::size_t count() { return ctr; }
// other interface members
private:
static std::size_t ctr;
// other implementation members
};
```
* Each instantiation of `Foo` **has its own instance of the static members.**
* 每個 template instantiation 都有獨立的一份 static member
* That is, for any given type `X`, there is one `Foo<X>::ctr` and one `Foo<X>::count` member.
```cpp
// instantiates static members Foo<string>::ctr and Foo<string>::count
Foo<string> fs;
// all three objects share the same Foo<int>::ctr and Foo<int>::count members
Foo<int> fi, fi2, fi3;
```
* class template 的 `static` members 的 definition 一樣只能有一個
* 但是 template 的每個 instantiation 都有一個 `static` member,所以定義這個 member 時前面還是要給定 class template (的 instantiation)的 scope:
```cpp
template <typename T>
size_t Foo<T>::ctr = 0; // define and initialize ctr
```
* 這樣特定 type `T` 的 `Foo` 就有特定的 `Foo<T>::ctr`
* 跟一般 class 一樣,user code 可以用 instantiation(i.e. class type) 或者 instantiation 的 instances(i.e. class object) 來 access static member(s)
* 記得不是用 template name 而是 instantiation 來 access(也就是要給定 template argument):
```cpp
Foo<int> fi; // instantiates Foo<int> class
// the staticdata member ctr
auto ct = Foo<int>::count(); // instantiates Foo<int>::count
ct = fi.count(); // uses Foo<int>::count
ct = Foo::count(); // error: which template instantiation?
```
* 上面寫 `Foo::count` 是錯的
* class template `static` member 跟其他 member function(s) 一樣,要被 user code 用到才會被 instantiated
:::info
* 還記得第七章說 class static member 要定義一份在 implementation file(.cpp) 嗎?
* 可是 template 通常是 header only,這樣不就只能定義在 header,而且會遇到 multiple definition?
* 答案是不會,反正這些 instantiation 都是 compiler 生的,compiler 自己會解決 multiple definition 的問題
:::
### 16.1.3 Template Parameters
* 習題都叫你用它寫了一個簡單 `Vec` 了才要詳細講什麼事 template parameters...
* temp para 的名字跟 func para 一樣,它的名字沒有特殊意義,你想叫什麼都行:
```cpp
template <typename Foo> Foo calc(const Foo& a, const Foo& b) {
Foo tmp = a; // tmp has the same type as the parameters and return type
// ...
return tmp; // return type and parameters have the same type
}
```
* 跟 function parameter 一樣,template 宣告跟 template 定義時用的 parameter name 可以不同
#### Template Parameters and Scope
* follow normal scoping rules
* 當 temp parametr 宣告之後,到 template 宣告或定義結束為止都可以使用這個 temp parameter
* 跟其他 name 一樣,會 hides outer scope name
* Unlike most other contexts, however, **a name used as a template parameter may not be reused within the template:**
* 在其他的 syntax,如果你 outer scope 定義一個東西叫做 `A`,然後在 function/class 內重定義 `A` 是 OK 的,會把外面定義的 `A` 給 hide
* 但像下面這樣會直接噴 error:
```cpp
typedef double A;
template <typename A, typename B> void f(A a, B b) {
A tmp = a;// tmp has same type as the template parameter A, not double
double B; // error: redeclares template parameter B
}
```
* 你的 template parameter `A` 隱藏了外面的 `typedef` 使用的 `A`,所以 `temp` 的型態跟你實例化時給定的型態一樣,而不是 `double`
* 而 template parameter `B` 直接被 reuse 成 variable name,所以噴 error
* 既然不能重 reuse template parameter,當然也不能這樣寫:
```cpp
// error: illegal reuse of template parameter name V
template <typename V, typename V> // ...
```
#### Template Declarations
* must include the template parameters:
```cpp
// declares but does not define compare and Blob
template <typename T> int compare(const T&, const T&);
template <typename T> class Blob;
```
* 跟 function dec/def 一樣,template declaration 內寫的 templare parameter 的名字不一定要跟 definition 寫的一樣:
```cpp
// all three uses of calc refer to the same function template
template <typename T> T calc(const T&, const T&); // declaration
template <typename U> U calc(const U&, const U&); // declaration
// definition of the template
template <typename Type>
Type calc(const Type& a, const Type& b) { /* .. . */}
```
* 這三個都代表同一個 template
* Of course, every declaration and the definition of a given template must have the same number and kind (i.e., type or nontype) of parameters.
* 宣告跟定義的重點是,要維持同數量跟同型別的 template parameter
* 這裡的同型別是指說 template parameter 是用 `typename` 宣告的,或者是 `nontype` parameter
:::info
* Best Practice:
* 16.3 會講說明
* 最好把一個 file 會用到的 template 全部都先宣告在一開始
:::
#### Using Class Members That Are Types
* 參考: 跟這個有關
* https://en.cppreference.com/w/cpp/language/dependent_name
* 這裡在說明之前在宣告 class template 的 type 時偶爾會出現的 `typename` 到底是做什麼用的
* 還記得對 (nontemplate) class 可以用 scope operator `::` 來存取:
* class 內的 type
* *或 static member*
* 這時候 compiler 因為知道 class 的定義,它有辦法知道 `::` 右邊到底是 class 定義的 type 還是 static member
* 可是這件事情在 template 會遇到以下困難:
* 如果對 type parameter `T` 使用 `T::mem`
* 在 compiler 編譯 template 時根本無法知道 `T::mem` 是 type 還是 static member
* 只能在 instantiate 時才知道
* 或者更甚,用 `T` 來構成型別的型別也會碰到一樣的狀況,例如 `vector<T>::size_type`
* 但 compiler 為了要處理 template,它一定要知道這時 `::` 右邊的東西是什麼,例如看到下面這種 code:
```cpp
T::size_type * p;
```
* 這行 code 會因為 `T::size_type` 是 type 還是 static member 產生很大的差異
* 如果是 type,則這裡是宣告 `p` 為指標
* 如果是 static member,這裏則變成呼叫 multiplication
* 而 compiler 的預設行為就是看到這種 depends on `T` 的 name 直接當他是 static member,而不是 type
* 因為這樣,如果 `T::blabla` 實際是一個型別而不是物件,我們必須明確讓 compiler 知道它是型別,如下:
```cpp
template <typename T> typename T::value_type top(const T& c) {
if (!c.empty())
return c.back();
else
return typename T::value_type();
}
```
* 這 code 吃一個 container type `c`,型別為 `T`,並且回傳 container 的最後一個 element;如果 `c` 是空的則回傳由 element 型別預設初始化的物件
* 仔細看,在我們想要使用 `T::value_type` 的地方
* 也就是 function `top` 的 return type
* 跟 function 內的第二個 `return` statement
* **我們都額外加了 `typename`,告訴 compiler `T::value_type` 是一個型別**
* 如果你要把 `T::value_type` 當成 type 使用卻沒有在前面加 `typename`,compiler 就會噴:
* `missing 'typename' prior to dependent type name 'T::value_type'T::value_type`
* 這也是為什麼在很前面的地方會看到 template 內用 T 的 member 宣告 template 自定義的型別時需要額外加 `typename` 的原因:
```cpp
typedef typename std::vector<T>::size_type size_type;
```
* 不只直接使用 `T` 時需要加,整個 expression 有 depends on `T`(在上面的例子是 `vector<T>`) 的都要加上 `typename`
#### Default Template Arguments
* Under the new standard, we **can supply default arguments for both function and class templates.**
* 早期的版本只有 class template 可以放 default template arguments,function template 不行
* 詭異... 就當作沒這回事
* 重寫 `compare` 來舉例:
```cpp
// compare has a default template argument, less<T>
// and a default function argument, F()
template <typename T, typename F = less<T>>
int compare(const T &v1, const T &v2, F f = F()) {
if (f(v1, v2)) return -1;
if (f(v2, v1)) return 1;
return 0;
}
```
* 這裡 `F` 代表的是 callable object 的型別,預設是 `less<T>`,注意 `less<T>` 也是個型別
* 然後 `F` depends on `T` 有點噁心...
* 注意 template parameter 宣告順序
* 所以這個 template 要實例化時,要馬可以不提供 `F` 的 template argument,要馬不提供 `f` 的 function argument,會有四種情況...隨便寫個幾種:
```cpp
bool i = compare(0, 42);
Sales_data item1(cin), item2(cin);
bool j = compare(item1, item2, compareIsbn);
```
* 第一個 call 不管 template 還是 function parameter 都用 default argument 初始化
* 第二個則是丟了一個 function 進去:
* 這個 function 有些要求(requirements)
* 以前差不多也提過;回傳值要能轉成 `bool`,吃的兩個參數要跟 `Sales_data` 相容
* 實際上就是看 `compare` 怎麼使用這個 function
* 這時 F 就會**被推斷**成 `compareIsbn` 的型別
:::info
* 跟 function default arguments 一樣,template default argument 只能從最右邊的 parameter 開始提供 default arguments
:::
#### Template Default Arguments and Class Templates
* 之前有說過 user code 要使用 class template,一定要在 template name 後面加上角括號
* 如果 class template 所有 parameter 都有 default argument 勒?
* 答: **使用 class template 不管怎樣都要有角括號**
```cpp
template <class T = int> class Numbers {
// by default T is int
public:
Numbers(T v = 0): val(v) { }
// various operations on numbers
private: T val;
};
Numbers<long double> lots_of_precision;
Numbers<> average_precision; // empty <> says we want the default type
```
* 總之就是要有角括號
#### 16.1.4 Member Templates
* 我覺得蠻重要的東西
* class(template) 可以定義一個 member
* 它本身也是 template,叫做 **member templates**
* **可以回看看 STL container 的 ctor API
* 尤其是吃 input iterator range 的 API
* 這些 ctor 其實是 template
* 就是用 template 的方式可以吃各種不同型別的 input range
* 但**不能是 `virtual`**
* 原因好像有點眾說紛紜
* 但我覺得,這功能加下去,template author 跟 user 應該都會很想死
#### Member Templates of Ordinary (Nontemplate) Classes
* 來看一般 nontemplate class 怎麼定義 member template
* 不過偏偏找一個有點噁心的例子
* we'll define a class that is *similar to the default deleter* type used by `std::unique_ptr`
* `operator()(ptr)`
* print message when the deleter is executed
* Because we *want to use our deleter with any type,* we’ll make the call operator a template:
```cpp
// function-object class that calls delete on a given pointer
class DebugDelete {
public:
DebugDelete(std::ostream &s = std::cerr): os(s) { }
// as with any function template, the type of T is deduced by the compiler
template <typename T> void operator()(T *p) const
{ os << "deleting unique_ptr" << std::endl; delete p; }
private:
std::ostream &os;
};
```
* 定義 member template 一樣要用 `template` 宣告,給定 type parameter, etc
* We can use this class as a replacement for `delete`:
```cpp
double* p = new double;
DebugDelete d; // an object that can act like a delete eexpression
d(p); // calls DebugDelete::operator()(double*), which deletes p
int* ip = new int;
// calls operator()(int*) on a temporary DebugDelete object
DebugDelete()(ip);
```
* 當然如果這樣寫的話,print 的邏輯不會那麼單純
* 你也可以把它拿來替換 `std::unique_ptr` 的預設 deleter,要這樣宣告:
```cpp
// destroying the the object to which p points
// instantiates DebugDelete::operator()<int>(int *)
unique_ptr<int, DebugDelete> p(new int, DebugDelete());
// destroying the the object to which sp points
// instantiates DebugDelete::operator()<string>(string*)
unique_ptr<string, DebugDelete> sp(new string, DebugDelete());
```
* `unique_ptr` 準備呼叫 deleter 時就會把指向的物件的型態塞給 `DebugDelete` 的 `operator()`
* 這樣對應的 instantiation 就會被實例化
* 詳細步驟:
* `unique_ptr<T>` 被實例化時,因為 dtor 就會呼叫 `DebugDelete::operator()<T>`
* 所以實例化 dtor 時,`DebugDelete` 對應的 `operator()<T>` 也會被實例化
```cpp
// sample instantiations for member templates of DebugDelete
void DebugDelete::operator()(int *p) const { delete p; }
void DebugDelete::operator()(string *p) const { delete p; }
```
* 這邊 Primer 好像少打 `os << "deleting unique_ptr" << std::endl;`
#### Member Templates of Class Templates
* 換介紹 class template 內怎麼定義 member templates
* 其實 STL ctor 才是這種的
* member template 的 type parameter 不要跟 class template 的有 name collision
* 我們來為 `Blob` 寫一個跟 STL container 類似的 ctor:
```cpp
template <typename T> class Blob {
template <typename It> Blob(It b, It e);
// ...
};
```
* 那怎麼把 member templates 定義在 body 外?
* We must provide the template parameter list for the class template *and* for the function template.
* The parameter list for the class template comes first, followed by the member’s own template parameter list:
```cpp
template <typename T> // type parameter for the class
template <typename It> // type parameter for the constructor
Blob<T>::Blob(It b, It e):
data(std::make_shared<std::vector<T>>(b, e)) { }
```
* 看起來很恐怖,不過 user code 實際上在使用時不會很難用,依靠 copiler deduce type:
```cpp
vector<int> vec{1,2,3,4,5,6,7};
Blob<int> b(begin(vec), end(vec));
```
#### Instantiation and Member Templates
* To instantiate a member template of a class template, we must **supply arguments for the template parameters for *both* the class and the function templates.**
* 注意,supply 不代表 user code 一定都要打出來,compiler 會推斷
* template argument for class 已經在宣告物件的時候給定
* 之後要用這個物件時 compiler 就已經知道 temp argument 是哪種了
* 而 member templates 則是在你給定 function arguments 時由 compiler 自行推斷
* 綜合兩點,其實直接用起來沒有那麼恐怖
* 總之就跟使用 STL container 的感覺差不多
```cpp
int ia[] = {0,1,2,3,4,5,6,7,8,9};
vector<long> vi = {0,1,2,3,4,5,6,7,8,9};
list<const char*> w = {"now", "is", "the", "time"};
// instantiates the Blob<int> class
// and the Blob<int> constructor that has two int* parameters
Blob<int> a1(begin(ia), end(ia));
// instantiates the Blob<int> constructor that has
// two vector<long>::iterator parameters
Blob<int> a2(vi.begin(), vi.end());
// instantiates the Blob<string> class and the Blob<string>
// constructor that has two list<const char*>::iterator parameters
Blob<string> a3(w.begin(), w.end());
```
* 注意你在使用 class instantiation(s),以及傳遞不同的參數給 ctor 時,compiler 都會生出不同的 class(instantiation) 跟 member(instantiation)
* 上面的 code 生了 `Blob<int>` 以及他的兩個 ctors
* 還有 `Blob<string>` 以及他的一個 ctor
### 16.1.5 Controlling Instantiations
* 先講想解決什麼問題:
* 當你直接在某個 TU 使用 template 時,compiler 就會在那個 TU 生出實例
* 如果:
* 你在不同的 TUs 都使用到這個 template
* 而且提供一樣的 template argument(s)
* 而且這些 TU 的 object files 會 link
* 這樣這些不同 TU 的 object files 都會各自有一份實例(但 link 不會噴 error 就是,我還不知道 compiler 怎處理的... 既然實例都是他生的,他自己會處理吧)
* 參考:
* https://stackoverflow.com/questions/44335046/how-does-the-linker-handle-identical-template-instantiations-across-translation
* 這樣 binary 會很肥
* 所以 C++11 提供一個方法讓這些 TU 都只共用一份實例
* 叫做 **explicit instantiation**:
```cpp
extern template declaration; // instantiation **declaration**
template declaration;// instantiation **definition**
```
* 其中上面的 `declaration` 是 function/class declaration
* 並且*把 template arguments 都填上去*:
* 看例子:
```cpp
// instantion declaration and definition
extern template class Blob<string>; // declaration
template int compare(const int&, const int&); // definition
```
* 注意語法只跟以往宣告 template 時有一點不同,但做的事情差很多
* 當 compiler 看到 user code 用 `extern` 宣告,也就是寫一個 instantiation declaration 時,**compiler 不會在這個 TU 生出一個 declaration 對應的實例**;
* 這種宣告方式是跟 compiler **承諾**說其他 TU 會有這個實例,compiler 不需要生一個
* 如果做出了承諾,卻沒有任何一個 TU 有實例,那就是 link error
* 而當 user code 是寫 instantiation definition 時,compiler 就會在當前 TU 直接產生出對應的實例
* **這個 definition 在整個 program 內只能有一個**,declaration 則可以有很多個
* 而因為 compiler 會在我們使用 template 時就自動生出對應的實例,所以我們應該要在使用 template 的某個實例之前就宣告上面的 `extern` declaration
* 不然 compiler 還是會在當前 TU 生一個實例:
```cpp
// Application.cc
// these template types must be instantiated elsewhere in the program
extern template class Blob<string>;
extern template int compare(const int&, const int&);
Blob<string> sa1, sa2; // instantiation will appear elsewhere
// Blob<int> and its initializer_list constructor instantiated in this file
Blob<int> a1 = {0,1,2,3,4,5,6,7,8,9};
Blob<int> a2(a1); // copy constructor instantiated in this file
int i = compare(a1[0], a2[0]); // instantiation will appear elsewhere
```
* `Application.o` 會包含有 `Blob<int>` 的 class definition,以及 `Blob<int>` 吃 `initializer_list` 的 ctor 還有 copy ctor 的 definition
* 而 `compare<int>` 跟 `Blob<string>` 的實例則會在別的 TU 內產生:
```cpp
// templateBuild.cc
// instantiation file must provide a (nonextern) definition for every
// type and function that other files declare as extern
template int compare(const int&, const int&);
template class Blob<string>; // ***instantiates all members*** of the class template
```
* 上面兩個 explicit definition 就會讓 compiler 在 `templateBuild.o` 產生出對應的實例
* 如果要 build 上面的程式,這兩個 object files 必須 link
:::warning
#### Instantiation Definitions **Instantiate All Members**
* 當 user code 寫一個 class template 的 explicit definition 時,compiler 根本還沒辦法知道 user code 會用到哪些 member functions
* 所以 compiler 索性把所有 class member 都給實例化,不管你之後有沒有用到
* 這隱含了一件事,當你在寫 explicit definition 並且提供 type 時,**你提供的 type 必須確保可以用在所有的 class member function 上,因為他們全部都會被實例化**
:::
### 16.1.6 Efficiency and Flexibility
* 如果你的 template 只能在「很有彈性的 API」跟「有效能」選擇,你要好好考慮
* STL 的 `shared_ptr` 跟 `unique_ptr` 他們處理 user defined deleter 的方式就是個好例子
* `shared_ptr` 不但可以動態更改成不同的 deleter,而且**可以改成不同型別的 deleter**
* 只要呼叫 `shared_ptr::reset` 就行了
* *deleter 不屬於 `shared_ptr` 的型別的一部份*
* 相反的,`unique_ptr` 的 deleter 則被定義成 `unique_ptr` 的 template type parameter 之一
* *deleter 屬於 `unique_ptr` 的型別的一部份*
* 首先雖然我們不知道 `shared_ptr` 的具體實作,但我們可以推論說 `shared_ptr` **一定是間接的使用 deleter**:
* **deleter 不能是 `shared_ptr` 的 direct member**
* `shared_ptr` 只能用一個指標,或者像是 `std::function` 那樣的東西去存取它,使用 deleter 時就要透過這個指標呼叫。
* 為什麼不能把 deleter 宣告成 `shared_ptr` 的 member 呢?
* 因為我們要到 run-time 才會知道 deleter 的型別
* 而且在 run-time 時還可透過 `reset` 傳入*不同型別的物件*當作新的 deleter
* 這件事情如果把 deleter 宣告成 direct member 是做不到的,因為 member 的型別根本不能改變
* **所以 `shared_ptr` 要使用 deleter 會多一層 indirect call**
* 封裝的 member -> 存取內部 deleter -> 呼叫 deleter
* 而因為 `deleter` type 會用 template argument 指定,換句話說在 compile time 時就能知道 deleter 的型別
* 所以 `unique_ptr` 就能把 deleter 當作 direct member
* 這樣就不會有 `shared_ptr` 那一層多的 call 了
* 總結:
* `unique_ptr` 在 compile time 決定要執行什麼 deleter,省掉 run-time indirect access overhead
* `shared_ptr` 在動態時期才決定,可是可以讓你很彈性的在 runtime 換一個(不同型別的) deleter
## 16.2 Template Argument Deduction
* 之前提到,user code 使用 function template 時,compiler 會幫你推斷型別
* compiler
* 到底怎麼推斷
* 然後推出什麼型別
* 是這章要講的重點
* 這個過程叫做 **Template Argument Deduction**
### 16.2.1 Conversions and Template Type Parameters
* 先記住大觀念:
* 在 function template 的情況下,**compiler 幾乎不太愛把傳入給 function template 的 function argument 做轉型**
* 也就是不會使用
* built-in 轉型(4.11.1 p.159)
* user-defined conversion(7.5.4 p.294, and 14.9 p.579)
* 還有 derived-to-base conversion(15.2.2 p.597)
* 取而代之的是直接生一個 exact match 的 function template instantiation
* 其他 compiler 在 function template 會做轉型的情況如下(只有兩種):
* 首先,function argument 的 top level `const` 還是會無視
* 如果 function parameter 是 ref/ptr,則傳入 的 low-level 是 non`const` 版本的 argument 會被轉型成 `const` 版本
* ref/ptr to non`const` -> ref/ptr to `const`
* array/function 如果不是用 ref 來 bind,則還是會被 decay 成 pointer
* 看例子:
```cpp
template <typename T> T fobj(T, T); // arguments are ***copied***
template <typename T> T fref(const T&, const T&); // ***references***
string s1("a value");
const string s2("another value");
fobj(s1, s2); // calls fobj(string, string); const is ignored
fref(s1, s2); // calls fref(const string&, const string&)
// uses **premissible conversion to const on s1**
int a[10], b[42];
fobj(a, b); // calls f(int*, int*)
fref(a, b); // error: array types don’t match
```
* `fobj` 跟 `fref` 兩個 template 參數吃 reference 跟 value
* 仔細看傳進 function 的物件的型別
* `fobj(s1, s2)`, `s1` ,`s2` 一個是 `std::string` 一個是 `const std::string`
* 因為 `fobj` 是 copy argument 到 function parameter(call by value),top level `const` 會無視
* 所以 `fobj` 都是呼叫 `fobj(string, string)`
* 而 `fref` 則是使用到 reference to non`const` 到 referencto to `const` 的轉換(對 `s1` 做轉換),所以一樣 legal;
* 而傳進 `a` 跟 `b` 時就不一樣了
* `a`, `b` 型別不同(array size 不同)
* 傳給 `fobj` 時都會 decay 成 `int*` 所以沒差
* 可是傳給 `fref` 時就不會轉型,這樣 `fref` 的第一個跟第二個 parameter 推論出來的型別就會有衝突(deduced conflicting types),是 reference to (different size) array,所以噴 error
:::warning
* `const` conversions and array or function to pointer are the only automatic conversions for arguments to parameters with template types.
:::
#### Function Parameters That Use the Same Template Parameter Type
* 這裡就是在講上面的 deduced conflicting types error
* 如果有多個 function parameter 用了同一個 template type parameter 當作型態宣告,那 user code 使用 function template 時傳的對應的 arguments 的型態一定要一樣,否則就會發生這種 error
* 例如我們之前寫的 `compare`:
```cpp
template <typename T>
int compare(const T&, const T&);
long lng;
compare(lng, 1024); // error: cannot instantiate compare(long, int)
```
* compiler 推斷第一個 type 是 `long`,第二個是 `int`,不 match,噴 error
* 解決上面的方法之一是使用兩個 type parameters:
```cpp
// argument types can differ but must be compatible(comparable?)
template <typename A, typename B>
int flexibleCompare(const A& v1, const B& v2) {
if (v1 < v2) return -1;
if (v2 < v1) return 1;
return 0;
}
// ...
long lng;
flexibleCompare(lng, 1024); // ok: calls flexibleCompare(long,int)
```
* 這樣上面的 call 就會合法
* 前提是存在比較 `A` 跟 `B` 的 `operator<()`
#### *Normal Conversions Apply* for Ordinary Arguments
* function template 也是可以有不是用 template type parameter 宣告的 function parameter
* 這樣的 function parameter 它還是保有一般的 conversion 機制:
```cpp
template <typename T> ostream &print(ostream &os, const T &obj) {
return os << obj;
}
```
* 上面的 `print` 有一個 `std::ostream&` 參數 `os`,傳給他的 argument 使用一般的轉型規則:
```cpp
print(cout, 42); // instantiates print(ostream&, int)
ofstream f("output");
print(f, 10); // uses print(ostream&, int); converts f to ostream&
```
* 第二個 call 就用到 derived to base conversion
### 16.2.2 Function-Template Explicit Arguments
* 有時候 compiler 連推斷 template arguments 的型別都沒辦法,這時我們可以明確指示型別
* 這種情況最常發生在 function return type 跟 parameter list 的任何 type 都不一樣的時候
* 可以先給一個例子,`std::make_shared`,user code 一定要給 template argument,可以想想為什麼
#### Specifying an Explicit Template Argument
* 定義一個 `sum`,吃兩個不同型別的 args
* 然後可以**讓 user 指定回傳的型別**
* 以達到 user 要求的精確值:
```cpp
// T1 cannot be deduced: it doesn’t appear in the function parameter list
template <typename T1, typename T2, typename T3>
T1 sum(T2, T3);
```
* 這時就很明顯,compiler 不可能單純用你傳的 function arguments 來推斷 `T1` 的型別
* The caller must provide an **explicit template argument** for this parameter on *each* call to sum.
```cpp
// T1 is explicitly specified; T2 and T3 are inferred from the argument types
auto val3 = sum<long long>(i, lng); // long long sum(int, long)
```
* Explicit template argument(s) are matched to corresponding template parameter(s) **from left to right;**
* template arguments 跟 function argument 一樣,只能從最右邊開始省略
* **而且要 compiler 有辦法推斷型別的情況才能省略**
* 上面的省略 convention 意味著你 template type parameters 的宣告順序很重要!
* 看例子:
```cpp
// poor design: users must explicitly specify all three template parameters
template <typename T1, typename T2, typename T3>
T3 alternative_sum(T2, T1);
```
* 當 user code 使用該 template 時 compiler 不能推斷最右邊的 `T3`,所以你一定要明確指定 `T3` 的 template argument
* 但是因為他是 template 的第三個 paramter,這代表第一,第二個 argument 都要提供
* 否則你只給定一個 template argument,在這個 case 該 argument 會被 bind 到 `T1`
* 換句話說就是全部的 type 都要明確提供...
```cpp
// error: can’t infer initial template parameters
auto val3 = alternative_sum<long long>(i, lng);
// ok: all three parameters are explicitly specified
auto val2 = alternative_sum<long long, int, long>(i, lng);
```
#### *Normal Conversions Apply* for Explicitly Specified Arguments
* 注意之前說 template 幾乎不太做 conversion,不過那只有在 compiler 自己推斷型別的狀況
* **如果你用上面的方法明確指定 template argument 的型別,那對應的 function argument 還是會使用一般的 conversion:**
* 可以這樣想:
* 提供 explicit template argument,代表你已經指定要用哪個 instantiation 了
* 那對應的 function argument 如果型態不完全 match,compiler 也只能想辦法用一般的規則來作轉型
* 否則難道要 compiler 生一個 template parameter 跟你指定的 argument 不同的 instantiation 然後呼叫嗎?
* 看例子
```cpp
long lng;
compare(lng, 1024); // error: template parameters don’t match
compare<long>(lng, 1024); // ok: instantiates compare(long, long)
compare<int>(lng, 1024); // ok: instantiates compare(int, int)
```
* 記得我們寫的 `compare` 只有一個 template type parameter,`T`
* 第一個合法 call 會把 `1024` 轉成 `long`
* 第二個合法 call 則是把 `lng` 轉成 `int`
### 16.2.3 Trailing Return Types and *Type Transformation*
* 題外話:
* type transformation 背後是個進階 topic 叫做 template metaprogramming
* 參考: `<type_triats>`:
* https://docs.microsoft.com/en-US/cpp/standard-library/type-traits?view=vs-2019
* unary type triats
* binary type traits
* tranformation triats
* 看一個例子,這是一個很常出現的情境,但可能需要 user code 提供 template argument 才會 work
* 但就很不希望 user code 需要提供
* *put burden on the user with no compensating advantage*
* 例如下面的 code:
```cpp
template <typename It>
??? &fcn(It beg, It end) {
// process the range
return *beg; // return a reference to an element from the range
}
```
* 吃一個 iterator pair range
* return type 是 range 內的 element type 的 reference
* 這時候我們要寫 return type 就很困難
* 因為單純只從 template parameter `It` 無法知道 element type
* 沒,這裡在嘴砲,你可以用 [iterator_traits](https://en.cppreference.com/w/cpp/iterator/iterator_traits) 的 `value_type`
* 但這更進階
* 所以從目前學過的方法來看,可能要讓 return type 也是 template parameter
* *然後讓 user 明確指定*
* 可是對 user 來說在這個情況下提供 element type 很奇怪,他會覺得你對 iterator dereference 不就知道了?
* 例如 user 這樣寫:
```cpp
vector<int> vi = {1,2,3,4,5};
Blob<string> ca = { "hi", "bye" };
auto &i = fcn(vi.begin(), vi.end()); // fcn should return int&
auto &s = fcn(ca.begin(), ca.end()); // fcn should return string&
```
* 希望在使用 `fcn` 時可以像是呼叫一般 function,而不是要各自提供 `int&` 或 `string&`
* 真解法:
* 我們知道 return type 跟 `*beg` 是相同的
* 可是你不能直接在 function return type 那裏直接寫 `*beg`
* 而且在 parsing return type 時 compiler 還沒進到 function scope,所以 compiler 也看不到 `beg`
* 所以這裡用 c++11 的 `decltype` 加上 trailing return type(§ 6.3.3, p. 229),來達成這件事:
```cpp
// a trailing return lets us declare the return type **after the parameter list is seen**
template <typename It> auto fcn(It beg, It end) -> decltype(*beg) {
// process the range
return *beg;
// return a reference to an element from the range
}
```
* 首先 `*beg` 是 lvalue,傳 lvalue 給 `decltype` 會得到 reference
* Thus, if fcn is called on a sequence of `string`s, the return type will be `string&`.
* If the sequence is `int`, the return will be `int&`.
#### The Type Transformation Library Template Classes
* 這個章節有點進階,so called "template metaprogramming",
* a topic that is beyond the scope of this Primer
* 首先上一個 section 是達到 return reference to element 的效果
* 可是這時沒辦法用上面的方式 return element 的 value
* 亦即,return copy
* 而 iterator 又沒有 operation 可以拿到 element 的 value(都是拿到 lvalue,會被 `decltype` 當成 reference)
* 所以如果要得到 elements 的 nonreference type 要用別的方法:
* we can use a **library type transformation templates**.
* defined in the `<type_traits>` header
* 這 header 基本上是要寫 **meta programming** 用的,Primer 不會講,不過我們還是可以這它來處理現在面臨的問題
* 下表是 header 內有的東西:
* 
* 在這個 case 我們可以用裡面的 `remove_reference`:
* 它有一個 template type parameter
* 還有一個 (public) type member: `type`
* **如果實例化一個 `remove_reference` 時給定一個 reference type,那 `type` 就會是 refereed-to type:**
* `remove_reference<int&>`, the `type` member will be `int`.
* `remove_reference<string&>`, `t`ype will be `string`.
* 如果用在我們要處理的問題:
```cpp
typename remove_reference<decltype(*beg)>::type
```
* 我們就可以得到 `*beg` 綁定的的 element type!
* 記得使用時要加上 `typename`,因為 `*beg` 是 dependent name
* 我們在把原本的 function template 宣告改一下:
```cpp
// must use typename to use a type member of a template parameter; see § 16.1.3 (p. 670)
template <typename It> auto fcn2(It beg, It end) ->
typename remove_reference<decltype(*beg)>::type
{
// process the range
return *beg; // return a copy of an element from the range
}
```
* 這樣回傳的 type 就會是 (nonreference)element type 了!
* 注意 trailing return type 有加一個 `typename`
* 因為這個 `type` 是 (indirectly) depends on `It`:
* `*beg` -> *It`
* 表中的其他 template,用起來都跟 `remove_reference` 差不多
* 都有一個 `type` member,它實際代表的型別取決於你用哪個 template
* 如果你實例化時傳入的 template argument 不 make sense(in terms of template itself),那 `type` 有可能跟你實例化傳入的型別相同
* 例如 `remove_pointer`,如果你傳指標進去,它的 `type` 就會把指標移除
* 但如果你傳非指標進去,它的 `type` 就會跟你傳的型別一樣
:::info
* 上面這張表是 *transformation* template
* 只是 `<type_traits>` 的其中一個功能
* header 裡面還有 unary predicates 跟 binary predicates 兩種
:::
### 16.2.4 Function Pointers and Argument Deduction
* 當初始化或 assign 一個 function template(instantiation) 到一個 function pointer 時,compiler 會用 function pointer 的型別去推斷 template arguments
* 看例子:
```cpp
template <typename T> int compare(const T&, const T&);
// pf1 points to the instantiation int compare(const int&, const int&)
int (*pf1)(const int&, const int&) = compare;
```
* 感覺這裡 `auto` 就沒辦法用了?
* 以下的東西感覺就 code smell,可以看一下,會發生 ambiguous call:
```cpp
// overloaded versions of func; each takes a different function pointer type
void func(int(*)(const string&, const string&));
void func(int(*)(const int&, const int&));
func(compare); // **error: which instantiation of compare?**
```
* 因為有兩種實例化可以使用,compiler 直接噴 error
* 不過你還是可以用 explicit template argument 避免上面的 error:
```cpp
// ok: explicitly specify which version of compare to instantiate
func(compare<int>); // passing compare(const int&, const int&)
```
### 16.2.5 Template Argument Deduction and References
* 這種 func temp:
```cpp
template <typename T> void f(T &p);
```
* function's parameter 是 reference to template type parameter
* 跟一般的 reference 相比,這種 reference 的 referred-to type 是 compiler 推斷的
* 後面會講*這東西有魔法...*
* 看這種 template 要記得兩件事:
* Normal reference binding rules apply;
* lref 不能綁 rvalue 之類的規則
* and consts are *low level, not top level.*
#### Type Deduction from Lvalue Reference Function Parameters
* 如果 function parameter 是 ordinary(lvalue) reference,也就是 `T&`,則 binding rule 告訴我們只能傳入 lvalue
* 這個 lvalue 可能是也可能不是 `const`,如果是的話,T 就會被 deduced 成 `const` type:
```cpp
template <typename T> void f1(T&); // argument must be an lvalue
int i = 8;
const int ci = 7;
// calls to f1 **use the referred-to type of the argument
// as the template parameter type**
f1(i); // i is an int; template parameter T is int
f1(ci); // ci is a const int; template parameter T is const int
f1(5); // error: argument to a & parameter must be an lvalue
```
* 如果 function parameter 是 `const T&`,則 binding rule 告訴我們我們可以綁定任何種類的 value
* an object (`const` or otherwise)
* a temporary,
* or a literal value.
* **如果 function parameter 已經宣告成 `const`,則 deduced type `T` 就不會是 `const` 了**
* **`const` 已經是 *function* parameter type 的一部分,它就不會變成 *template* parameter type 的一部分了:**
```cpp
template <typename T> void f2(const T&); // can take an rvalue
// parameter in f2 is const &; const in the argument is irrelevant
// in each of these three calls, f2’s function parameter is inferred as const int&
f2(i); // i is an int; template parameter T is int
f2(ci); // ci is a const int, but template parameter T is int
f2(5); // a const& parameter can be bound to an rvalue; T is int
```
* 知道 `const` 到底包含在 function parameter 還是 template parameter 內很重要
* 因為很可能會拿 template parameter 來宣告變數,所以你當然要知道它是不是 const**
* 你可以把上面的 code 丟到 IDE 然後看 IDE 會呼叫的實例內,角括號的型態是 `int`,並沒有 `const`
#### *Type Deduction from Rvalue Reference Function Parameters*
* **警告,這裡是 forwarding reference**
* 但是 Primer 一直都用「rvalue reference to template parameter」
* function parameter 是 `T&&` 時會用到
* 要 exactly 長得像 `T&&`,不能是什麼 `vector<T>&&` 之類的
* *也不能有 `const`*
```cpp
template <typename T> void f3(T&&);
f3(42); // argument is an rvalue of type int; template parameter T is int
```
* 規則基本上跟 lvalue ref 差不多
#### Reference Collapsing and Rvalue Reference Parameters
* 參考:
* ***可以去看 Scott Meyers 大大說的 Universal Reference***
* 如果你某 template 的 function parameter 是定義 rref to template parameter(forwarding reference),你一定會覺得這樣的 code 會噴 error:
```cpp
template <typename T> void f(T&&);
int i;
f(i); // ?
```
* 畢竟之前學過不能用 rref 來綁 lvalue
* 但如果是 rref **to template parameter**,則這裡會是一個例外
* 首先如果 bind 一個 lvalue 到 rref to template parameter(forwarding reference) ,**compiler 會把 template parameter 推論成 function argument 的 lref**
* 換句話說上面 code 的 `T` 在呼叫 `f(i)` 時會被推論成 `int&'
* 這樣看起來就更奇怪了
* 因為這時 `f` 的 *function* parameter 看起來就好像是 rref to int&
* 問號,不是說不能有 ref to ref?
* However, **it is possible to do so *indirectly* through a type alias (§ 2.5.1, p. 67) or through a *template type parameter*.**
* 你可以用 type alias 來定義這種東西
* 或者是現在說的 rref to template parameter 情況來達成 ref to ref
#### Reference Collapsing
* 那所以 ref to ref 是什麼鬼?
* 如果真的間接定義出 ref to ref,則會發生 reference "collapse":
* 首先 ref to ref 有四種狀態:
* lref to lref, lref to rref, rref to lref, rref to rref
* ***除了 rref to rref,其他的都會 "collapse" 成 lvalue reference***
* 或者這樣說:
* `X& &`, `X& &&`, and `X&& &` all collapse to type `X&`
* The type `X&& &&` collapses to `X&&`
* 再提醒一次,以上的情況只有在 type alias,跟 rvalue reference to template parameter type(forwarding reference) 時才會發生
* 所以上面的 `f(i)` 到底會推成三小?
* `f` 原本吃 rref to template type parameter
* 你給他 lvalue `int&`,所以在這個特殊情況下 *template* type parameter `T` 會被推成 ref type `int&`
* 而 function parameter 的 type 在這裡就是 `int& &&`
* 所以會 collapse 成 `int&`
* 所以 *function* parameter 會是 `int&`
* 總之 compiler 會推出像這樣的實例:
* demo 用,not valid code:
```cpp
// invalid code, for illustration purposes only
void f3<int&>(int& &&); // when T is int&, function parameter is "int& &&", which collapses to int&
```
* There are two important consequences from these rules:
* A function parameter that is an *rvalue reference to a template type parameter*(forwarding reference)(e.g., `T&&`) can be bound to an lvalue
* If the argument is an lvalue, then the deduced *template* argument type will be an lvalue reference type
* and the *function* parameter will be instantiated as an (ordinary) lvalue reference parameter (`T&`)
* 你如果傳的 function argument 是 lvalue,會導致對應的 template type parameter 是 reference type
* 然後 function parameter type 也會是 lvalue reference
* 既然他可以綁定 lref 又可以綁 rref,換句話說我們可以傳入任何種類的 value(argument) 給這種 rvalue reference to template parameter(forwarding reference) 綁定
* 但之後會看到,**其實只會在兩個地方用到這個功能,在其他地方把 lvalue 傳給 rvalue reference to template type parameter 只會是[悲劇](#Writing-Template-Functions-with-Rvalue-Reference-Parameters)**
:::info
An argument of any type can be passed to a function parameter that is an **rvalue reference to a template parameter type(*forwarding reference*)** (i.e., `T&&`).
:::
#### Writing Template Functions with Rvalue Reference Parameters
* 上面關於 rvalue reference to template parameter type (forwarding reference) 的情境會導致 template type parameter 被推成 reference type,那下面的 code 就會很...撲朔迷離:
```cpp
template <typename T> void f3(T&& val) {
T t = val; // copy or binding a reference?
t = fcn(t); // does the assignment change only tor val and t?
if (val == t) { /* .. . */} // always true if T is a reference type
}
```
* 自己想想 `T` 是否為 reference 時,行為會差很多
:::info
實務上,template 會使用到 rvalue reference to template type parameter(f) 的情況只有下面兩個:
* template *forward* its arguments, 16.2.7 會講這是三小
* template 被 overloaded,16.3 會講
:::
* 現在先知道一個重要的東西,用了 rref to template type parameter 的 template 通常會做 overload,長得很像 13.6.3(`StrVec::push_back()`) overload 的方法:
```cpp
template <typename T> void f(T&&); // binds to non const rvalues
template <typename T> void f(const T&); // lvalues and const rvalues
```
* 這時候 compiler 就會跟據傳入給 `f` 的 type 決定要丟給哪個版本的 `f`:
* modifiable rvalues 就會丟給 `f(T&&)`
* lvalue 或者 `const` rvalue 就會給 `f(T&)`
### 16.2.6 Understanding `std::move`
* The library `std::move` function (§ 13.6.1, p. 533) is a good illustration of a template that uses rvalue references (to template type parameter).
* In § 13.6.2 (p. 534) we noted that although we cannot directly bind an rvalue reference to an lvalue, we can use `std::move` to obtain an rvalue reference bound to an lvalue.
* Because `std::move` can take arguments of essentially any type, *it should not be surprising that `std::move` is a function template.*
#### How `std::move` Is Defined
* 題外話:
* 我討厭這裡 Primer 的安排,它先用 `static_cast` demo 給你看說可以把 lvalue 轉成 rvalue,之後才跟你說 `static_cast` 可以這樣用
* 來看 `std::move` 怎麼定義:
```cpp
// for the use of typename in the return type and the cast see § 16.1.3 (p. 670)
// remove_reference is covered in § 16.2.3 (p. 684)
template <typename T>
typename remove_reference<T>::type&& move(T&& t) {
// static_cast covered in § 4.11.3 (p. 163)
return static_cast<typename remove_reference<T>::type&&>(t);
}
```
* 你可以去翻翻 source code,還真的長這樣
* 我們是要 demo 的是**不管你傳的是 lvalue 還是 rvalue,`std::move` 都會回傳一個 rvalue**
* 再來,看清楚 `std::move` 的參數是 `T&&`,是 rref to template type parameter(forwarding reference)
* **所以我們要看我們傳入 l/rvalue 給 `std::move` 時 `T` 分別會被推斷成什麼型態**
* 用這段 code 講解:
```cpp
string s1("hi!"), s2;
s2 = std::move(string("bye!")); // ok: moving from an rvalue
s2 = std::move(s1); // ok: but after the assigment s1 has indeterminate value
```
#### How `std::move` Works
* 先看 `s2 = std::move(string("bye!"));` 的情況:
* 這裡就只是把 `std::string` 給 `T&&` 綁定,這時 `T` 會被推成 referred to type(也就是 `std::string`)
* 忘記的話寫在 p.687 底下
* 所以使用 `remove_reference` 時會實例化一個 `remove_reference<string>`
* `remove_reference<string>::type` 一樣是 `string` (傳入的 type 如果不是 reference 的話就維持原本的 type)
* `remove_reference<string>::type&&` 就會是 rvalue reference 惹!
* 最後再將 `t` 用 `static_cast` 轉成上面的 rvalue reference
* 所以 `std::move` 的 return type 是 rvalue reference to `std::string`
* 你也可以說這個 call 實例化了 `move<string>`:
```cpp
string&& move(string &&t)
```
* 再來看 `s2 = std::move(s1);`
* 因為你把 lvalue 傳給 `std::move` 的 rref to `T`,所以這裡的 `T` 被推斷成 reference type,也就是 `string&`
* 所以你會實例化一個 `remove_reference<string&>`
* 他的 `type` member 就會把 reference 移除,所以是 `string`
* 那你的 move return type 是 `type&&`,所以還是 `string&&`
* 最後再將 `t` 用 `static_cast` 轉成上面的 rvalue reference
* 題外話,這時 move 的 *function* parameter 會從 `string& &&` collapse 成 `string&`
* 你也可以說這個 call 實例化了 `move<string&>`:
```cpp
string&& move(string &t)
```
* 這才是常用的情況,我們希望把 rref 綁到一個 lvalue 上!
#### `static_cast` from an Lvalue to an Rvalue Reference *Is Permitted*
* Primer 都用了才講 LOL
* Binding an rvalue reference to an lvalue **gives** code that operates on the rvalue reference **permission to clobber the lvalue.**
* 例如還記得的話,13.6.1 的 `StrVec` 的 `reallocate` 就有用 `std::move` 把舊的空間的 elements 直接 move 到新空間,因為我們知道舊的空間的 elements 準備被破壞惹
:::warning
* 請不要手動 call `static_cast` 把 lvalue 轉成 rvalue reference
* 要轉也是 call `std::move`,比較好用
* 你可以想成 `std::move` 就是這種噁心的 `static_cast` 的一個 wrapper,讓你比較簡單使用
:::
### 16.2.7 Forwarding
* use case: 有的時候 function template 會有一種寫法,就是把 function parameter 丟給另一個 function 來幫你做事情,template 本身有點像 wrapper 的概念
* 例如一個 function 把事情拆成一些 utility 做
* 這個時候我們可能會需要**確保 function parameter 被丟進另一個 function 時,它們的所有特性都要被保留**
* 例如 `const`ness
* 跟是否為 reference type
* rvalue 還是 rvalue
* 否則另一個 function 的執行行為可能不是我們想要的
* 例如我們設計一個 function template 叫做 `flip1`
* 收一個 callable object 跟兩個參數
* 他會呼叫 callable object,並且把兩個參數傳進去
* *只是順序倒過來*
* 題外話,我覺得定義 `greater` 然後使用 `less` 來定義比較有意義啦
* 以下是*錯誤的實作版本*
```cpp
// template that takes a callable and two parameters
// and calls the given callable **with the parameters "flipped"**
// flip1 is an **incomplete implementation: top-level const and references are lost**
template <typename F, typename T1, typename T2>
void flip1(F f, T1 t1, T2 t2) {
f(t2, t1);
}
```
* 使用該 template,如果 callable 參數都是 pass by value 則沒差
* 但是如果 callable object 有 passed by reference parameter 就會有問題了
* 如果是 passed by reference,那代表 callable object 有意思要直接存取丟給他的 argument,而非存取一份 copy
* 但當 user code 在把 argument 傳遞給 `flip1` 時,首先 `flip1` 會把 template type parameter(也就是 `T1` `T2`) 推成 nonreference type
* 這樣 `flip1` 的 function parameter type 也會是 nonreference type
* 所以傳入到 `flip1` 的 arguments 是**被複製** 到 `t1 t2`
* 在 `flip1` 不管怎麼更改 `t1 t2` 都改不到原本的 arguments 了
* 注意這裡 `f` 還是可以改到傳給 `f` 的 `t1 t2`,因為 `f` 的 parameter 是 reference type,可是 `f` 還是改不到傳給 `flip1` 的 arguments。
* 看 code,給定以下 `f`:
```cpp
void f(int v1, int &v2) // note v2 is a reference {
cout << v1 << " " << ++v2 << endl;
}
```
* 如果你做下面的呼叫:
```cpp
f(42, i); // f changes its argument i
flip1(f, j, 42); // f called through flip1 leaves j unchanged
```
* 直接呼叫 `f` 可以改到 argument,可是透過 `flip1` 呼叫就沒有改到了,也就是說 `j` 不會被更改
* 你可以看用 IDE 功能看 compiler 生出什麼實例:
```cpp
void flip1(void(*fcn)(int, int&), int t1, int t2);
```
* `t1` 跟 `t2` 的型態都是 `int`,不是 `int&`
* **`j` is copied into `t1`.** The **reference parameter in `f` is bound to `t1`, not to `j`.**
#### Defining Function Parameters That *Retain Type Information*
* 使用 forwarding reference 可以(部分)解決上面的問題
* we need to rewrite our function so that its **parameters preserve the "lvalueness"** of its given arguments.
* Thinking ahead a bit, we can imagine that we’d also like to **preserve the `const`ness of the arguments** as well.
* **做法是把 function parameter 的型態定義成 forwarding reference**
1. 定義成 ref,都會保持被綁定的型態的 `const`ness,因為 low level `const` never ignored
2. 而利用 forwarding reference 的 reference collapsing(§ 16.2.5, p. 687),這樣如果傳進 function template 的 argument 是 lvalue,則 parameter type 就會是 lref,argument 是 rvalue,parameter type 就會是 rref
```cpp
template <typename F, typename T1, typename T2>
void flip2(F f, T1 &&t1, T2 &&t2) {
f(t2, t1);
}
```
* 以下是解釋,但要仔細看,一下說 `T1` 一下說 `t1`,一個是 template type parameter 一個是 function parameter
* As in our earlier call, if we call `flip2(f, j, 42)`, the lvalue `j` is passed to the parameter `t1`
* However, in `flip2`, **the type deduced for `T1` is `int&`, which means that the type of `t1` collapses to `int&`.**
* The **reference `t1` is bound to `j`.**
* When `flip2` calls `f`, **the reference parameter `v2` in `f` is bound to `t1`, which in turn is bound to `j`**.
* When `f` increments `v2`, it is changing the value of `j`.
:::info
* A function parameter that is an rvalue reference to a template type parameter(forwarding reference) (i.e., T&&) **preserves the `const`ness and lvalue/rvalue property of its corresponding argument.**
:::
##### 但 `flip2` 只解決一半的問題...
* 你傳吃 lref 的 callable 給它,行為會正常
* 可是傳吃 rref 的 callable 就會出事
* 看例子:
```cpp
void g(int &&i, int& j) {
cout << i << " " << j << endl;
}
```
* 如果我們想用 `flip2` 來 call `g`,
```cpp
flip2(g, i, 42); // error: can’t initialize int&& from an lvalue
```
* 這時你的 `t1` 會綁到 42,`t2` 會綁到 i,而且他們本身是 rvalue reference
* 可是一個 variable name,不管它是 r/lvalue ref,直接用它的時候它就是 lvalue(§ 13.6.1, p. 533, variable expression is lvalue),所以你把 `t2` 丟給 `g` 的 `i` 時,等於是把 lvalue 丟給 rref 來綁,所以會噴 eror
#### Using `std::forward` to Preserve Type Information in a Call
* 上面的問題是 c++11 才會碰到的,新標準當然要定義東西來解決R
* 跟 `std::move` 一樣定義在 `<utility>
* 呼叫的時候**一定要用 explicit template argument**
* `std::forward` **returns an rvalue reference to that explicit argument type(forwarding reference).**
* https://en.cppreference.com/w/cpp/utility/forward
* 看他怎麼宣告的
* That is, the return type of `forward<T>` is `T&&`.
* `std::move` 是不管你傳 l 還 r value 進去,他都回傳 r
* `std::forward` 則是回傳當時 caller 丟給他的 forwarding reference 的 type
* 所以你可以寫這樣的 function:
```cpp
template <typename Type> intermediary(Type &&arg) {
finalFcn(std::forward<Type>(arg)); // ...
}
```
* 看上面的 `Type` 跟 `arg`
* 因為 `arg` 是 forwarding reference,所以他可以把傳進來的 argument 的所有 type information 都保留(`const`/lr)
* 如果 argument 是 rvalue,則 `Type` 是 plain type(就是沒有 reference),那 `forward<Type>` 就是 `Type&&`
* 如果 argument 是 lvalue,則 `Type` 是 lvalue reference,這時 `forward<Type>` 一樣是 lvalue reference type,因為 `forward` 實際上回傳的是 **rref to lref**,根據 reference collapsing,這樣是 lref
:::info
* 再總結: When used with a function parameter that is forwarding reference (`T&&`), `std::forward` preserves all the details about an argument's type.
:::
* 所以總結,我們的 `flip` 應該這樣寫:
```cpp
template <typename F, typename T1, typename T2>
void flip(F f, T1 &&t1, T2 &&t2) {
f(std::forward<T2>(t2), std::forward<T1>(t1));
}
```
* If we call `flip(g, i, 42)`, `i` will be passed to `g` as an `int&` and `42` will be passed as an `int&&`.
:::warning
* 不要對 `forward` 用 `using`,理由跟 `move` 一樣,18.2.3 會講
:::
### 16.3 Overloading and Templates
* function template(s) 也可以參與 overloading
* 有了 function template(s),function matching 的規則就是第六章說明的(p.233) 加上以下規則
* 所有可以 match(或者說 deduce) 的 instantiation(s) 的 template(s) 都是 candidates
* 這些 cadndates 同時(一定)也是 viable functions
* 因為可以 deduce 出來的 instantiations 一定可以被呼叫
* 如第六章的 rank 方式,function(templates) 也是按照是否須要對 argument 做(哪種) conversion 來排名
* 不過別忘記 function template 可以做的 conversion 很少(16.2)
* 一樣,如果 viable functions 裡面有一個 function 全部參數都是 better match,就會被選擇來呼叫;如果沒有則看以下情況:
* 如果有多個以上 viables,**但是這些 viables 裡面 nontemplate function 只有一個**,那就是這個 nontemplate function 被呼叫
* 如果 viable 內一個 nontemplate function 都沒有,而有多個 function template instantiations,**但是其中一個 more specialized**,則他被呼叫
* 否則就是 ambiguous call
* 後面會講什麼是 **more specialized**
:::warning
* Correctly defining a set of overloaded function templates requires a good understanding of the relationship among types and of the restricted conversions applied to arguments in template functions.
:::
#### Writing Overloaded Templates
* overload matching 規則那麼複雜根本記不起來,先看 code
* 來定義一些 debug 時很有用的 function template(s),`debug_rep`,會回傳 `std::string`,代表某個物件的 `string` representation
```cpp
// print any type we don’t otherwise handle
template <typename T> string debug_rep(const T &t) {
ostringstream ret; // see § 8.3 (p. 321)
ret << t; // uses T’s output operator to print a representation of t
return ret.str(); // return a copy of the string to which ret is bound
}
```
* 這裡用了 `std::ostreamstring` 簡化字串處理,不過傳入物件要支援 `operator<<`
* 接下來定義另一個版本,**吃 `T*`**
```cpp
// print pointers as their pointer value, followed by the object to which the pointer points
// NB(注意)): this function will not work properly with char*; see § 16.3 (p. 698)
template <typename T> string debug_rep(T *p) {
ostringstream ret;
ret << "pointer: " << p;
if (p) // print the pointer’s own value
ret << " " << debug_rep(*p); // print the value to which p points
else
ret << " null pointer"; // or indicate that the pis null
return ret.str(); // return a copy ofthe string to which ret is bound
}
```
* 注意這個版本會呼叫前一個版本 LOL
* 另外**這個版本對 `char*` 有問題**
* 因為 `operator<<` 已經對 `char*` 做 overload 了,直接認定 operand 指向 null terminated string
* p.698 會講要怎麼處理這個情況
* 可以這樣用這兩個 templates:
```cpp
string s("hi");
cout << debug_rep(s) << endl;
```
* 上面這個直接把 s 的 string representation 印出來(這樣講很奇怪,但 string 也是物件,而且支援 <<,就說 string 有「string representation」吧)
* 來分析 compiler 怎麼 match `debug_rep`
* 首先只有第一個版本的 `debug_rep` 是 viable
* 因為第二個版本是吃指標,而我們是傳物件進去,**不可能把一個物件轉成指標,所以 deduction fails**
* 這裡提供一個更單純的範例告訴你這種情境的 deduction fail 會發生什麼事
```cpp
template <typename T> void eat_ptr(T *) {}
int main() {
int i;
eat_ptr(i);
}
```
* 因為只有一個 viable 所以就選他惹
* 接下來看下面這個:
```cpp
cout << debug_rep(&s) << endl;
```
* 注意!**兩個 templates 都是 viable!!**
* 第一個 template 會產生 `debug_rep(const string*&)`,reference to pointer
* 第二個會產生 `debug_rep(string*)`
* 第二個 viable 是 exact match,第一個 viable 要把 normal pointer 轉成 pointer to `const`,所以第二個被呼叫
#### Multiple Viable Templates
* 再看一個例子:
```cpp
const string *sp = &s;
cout << debug_rep(sp) << endl;
```
* 這時兩個 template 都是 exact match!
* 第一個生出 `debug_rep(const string*&)`
* 第二個生出 `debug_rep(const string*)`
* 這個 case 用第六章的 rule 來看是 ambiguous 的
* **但是!!** 這裡如果用 template 獨有的 matching rule,則會呼叫第二個 function
* 因為他 **more specialized**
* 如果沒有上面那個 rule,則宣告成吃指標的那個模板,永遠不可能在 caller 傳入 ptr to `const` 時被呼叫
* 真正的癥結點在於 **const T& 可以吃 ANY TYPE**,包括指標
* 我們說這樣的 template **更加的 general**
* 吃指標的版本更加 specialized
* 如果沒有這個 rule,那傳入常數指標時永遠會 ambiguous
:::info
When there are several overloaded templates that provide an equally good match for a call, **the most specialized version is preferred.**
:::
#### Nontemplate and Template Overloads
* 如果要讓一般函數跟模板函數 overload 呢?
* 為此我們再定義一個一般函數:
```cpp
// print strings inside double quotes
string debug_rep(const string &s) {
return '"' + s + '"';
}
```
* 這時寫這樣的 code 會如何?:
```cpp
string s("hi");
cout << debug_rep(s) << endl;
```
* 這時有兩個 viable functions, 而且 equally good
* `debug_rep<string>(const string&)`,從第一個模板生的
* `debug_rep(const string&)`,一般函數
* **但這裡一般函數會被呼叫**
* 就這樣想
* template 比較 general
* nontemplate function 本質上比較專屬於某個型態,所以 more specialized
:::info
* 再一個小總結: When a nontemplate function provides an equally good match for a call as a function template, the nontemplate version is preferred.
:::
#### Overloaded Templates and Conversions
* 但上面還沒說怎麼解決上面的傳入 `char*` 出現的問題
* 假設寫這樣的 code:
```cpp
cout << debug_rep("hi world!") << endl; // calls debug_rep(T*)
```
* user code 的 intention 應該是呼叫吃 `const string&` 的 `debug_rep`
* 中間會使用 implicit conversion
* 但首先這個狀況有三個 viable functions:
* `debug_rep(const T&)`, with `T` bound to `char[10]`
* `debug_rep(T*)`, with `T` bound to `const char`
* `debug_rep(const string&)`, which **requires a conversion** from `const char*` to `string`
* 首先第一個第二個 template 都是 exact match(注意第二個從 array decay 成 ptr 算是 exact match,詳情 p.245)
* 第三個 viable 反而還需要 user defined(正確來說是 standard defined) conversion(`const char*` to `std::string`),所以不會考慮
* 所以剩前兩個 templates 可以選
* 而上面已經說了,吃指標的版本 more specialized,所以會被呼叫
* 解決方法就是定義兩個吃 (`const`) `char*` 的一般 function
```cpp
// convert the character pointers to string and call the string version of debug_rep
string debug_rep(char *p) {
return debug_rep(string(p));
}
string debug_rep(const char *p) {
return debug_rep(string(p));
}
```
* 這樣傳入 `char[]` 時,有四個版本都是 exact match(注意現在總共有五個 function(templates),包含 `debug_rep(const string&)`)
* compiler 會挑吃 `char*` 的 nontemplate `debug_rep`
* 傳入 `const char[]` 時,有三個 exact match
* compiler 挑吃 `const char*` 的一般函數
#### Missing Declarations *Can Cause the Program to Misbehave*
* 請注意看我們的,最上面那兩個吃 C style string 的版本,他們的 return 會呼叫吃 `const string&` 的 nontemplate `debug_rep`
* **前提是在做 function matching 時有看到這個 `debug_rep`**
* 如果 compiler 沒看到這個 function(例如還沒宣告),就會使用吃 `const T&` 的 template 生一個實例出來給你呼叫
* 注意那個吃 `const &` 的模板做的事情跟我們自定義的 `const string&` 做的事情是不一樣的
* 自己測試,會少印那個 `""`
```cpp
template <typename T> string debug_rep(const T &t);
template <typename T> string debug_rep(T *p);
// ***the following declaration must be in scope***
// ***for the definition of debug_rep(char*) to do the right thing***
string debug_rep(const string &);
string debug_rep(char *p) {
// if the declaration for the version that takes a const string& is not in scope
// the return will call debug_rep(constT&) with T instantiated to string
return debug_rep(string(p));
}
```
:::info
* **Declare every function in an overload set** before you define any of the functions.
* That way you don’t have to worry whether the compiler will instantiate a call before it sees the function you intended to call.
* 請把所有要 overloading 的 function(templates) 全部都宣告在一起
:::
## 16.4 Variadic Templates
* 可以吃任意數量的 template parameter 的 templates
* 這些數量可以任意變化的 parameters 叫做 **parameter pack**
* 有兩種 parameter packs:
* template parameter packs: 代表有 0 到多個 template parameters
* function parameter packs: 代表有 0 到多個 function parameters
* 用 ellipsis(`...`)來說明一個 function 或 template parameter(name) 是一個 parameter pack
* 具體來說,如果在 template parameter list 使用 `typename ...` 或者 `class ...` 來宣告 template parameter name,那個 name 就代表了 template parameter packs
* **代表了 0 到多個不同的 types**
* 如果是在 function parameter list 內想要用 function parameter pack,則這個對應的參數型別就要是 template parameter pack
* 繞口令?
* 看例子
```cpp
// Args is a template parameter pack; rest is a function parameter pack
// Args represents zero or more template type parameters
// rest represents zero or more function parameters
template <typename T, typename... Args>
void foo(const T &t, const Args& ... rest);
```
* `foo` 是個 variadic template function,有一個 template type parameter `T`,跟一個 template parameter pack,`Args`
* `Args` 代表了額外的 0 到多個 type parameters
* 而 `foo` 的 function parameter list 則有一個 function parameter `T`,確切型別要看 instantiation,還有一個 function parameter pack,`rest`
* `rest` 則是代表了額外的 0 到多個 function parameters
* 在用這種有 pack 的 template 時,compiler 除了幫你 deduce type 外,還會 deduce 有多少數量的型別:
```cpp
int i = 0; double d = 3.14; string s = "how now brown cow";
foo(i, s, 42, d); // 3 parameters in the pack
foo(s, 42, "hi"); // 2 parameters in the pack
foo(d, s); // 1 parameter in the pack
foo("hi"); // empty pack
```
* `foo` 的第一個 arguments 會用來推斷 `T` 的型別;
* 剩下的則都拿來推斷 template parameter pack 內包含的型別(以及數量)
* compiler 會按照上面的 code 生出以下四個 instantiations:
```cpp
void foo(const int&, const string&, const int&, const double&);
void foo(const string&, const int&, const char(&)[3]);
void foo(const double&, const string&);
void foo(const char(&)[3]);
```
* 注意 template parameter pack 對應的參數都有 `const`
* 因為型態宣告那邊是寫 `const Args...&`
* 被 `...` 展開成多個 parameter 的東西叫做 **pattern**
* 看 16.4.2
#### The `sizeof...` Operator
* 有時需要靜態就知道 pack 內有幾個 parameters,這時可以用 `sizeof...`:
```cpp
template<typename ... Args> void g(Args ... args) {
cout << sizeof...(Args) << endl; // number of type parameters
cout << sizeof...(args) << endl; // number of function parameters
}
```
* **return constant expression**
* 傳入 `sizeof...` 的 argument 一定要是 parameter pack,不管是 function/template parameter pack 都可以
### 16.4.1 Writing a Variadic Function Template
* 還記得第六章的 `initializer_list` 嗎
* 他可以吃任意數量的**同型別的** arguments
* Variadic functions 則是用在我們**既不知道 user code 會傳的參數的型別,也不知道 user code 會傳多少參數**
* 之後會定義一個 `errorMsg` 來示範怎麼用
* 不過在這之前先定義一個 variadic 版本的 `print`
* 他會把所有 arguments 全部都印出來
* 管你有幾個 & &什麼型別
* 另外,**Variadic functions are often recursive**
* 那是因為一定要這樣寫,你無法對 pack 做什麼 range for 之類的,沒有這種功能
* 實作如下
```cpp
// function to end the recursion and print the last element
// ***this function must be declared before the variadic version of print is defined***
template<typename T>
ostream &print(ostream &os, const T &t) {
return os << t; // no separator after the last element in the pack
}
// this version of print will be called for all but the last element in the pack
template <typename T, typename... Args>
ostream &print(ostream &os, const T &t, const Args&... rest)
{
os << t << ", "; // print the first argument
return print(os, rest...); // recursive call; print the other arguments
}
```
* 先看最下方的 `print`
* 第一次呼叫時**先從 pack 內抓一個參數出來處理**
* 然後 pack 內剩下的參數就拿來遞迴呼叫 `print`
* 重點就是 `print`,裡面的 `return` 部分;直接把 `rest...` 丟給 `print`
* 這樣傳遞的話,**pack 內的第一個參數就會被綁到 `t`**
* 剩下的綁到下一次 `print` 的 `rest...`
* 而第一個版本的 nonvariadic `print` 等等會講是拿來幹嘛的
* 舉例子:
```cpp
print(cout, i, s, 42); // two parameters in the pack
```

* 前兩次 call 時,第一個版本都不是 viable function,因為他只吃兩個參數,而前兩次 call 一次傳四個參數,一次傳三個;
* 但第三次 call 時,只傳兩個參數,這樣的話兩個版本的 `print` 都會是 viable;
* 注意 parameter pack 可以為空,所以第二個版本的 `print` 雖然看起來有三個參數,但是只傳遞兩個參數的話這個 `print` 還是 viable 的
* 而根據 specialized rule,**nonvariadic template 比 variadic more specialized**,所以會呼叫第一個版本的 `print`
* **注意如果沒有讓第一個版本的 `print` 先宣告,會編不過,噴 error**
* 另外我原本想要這樣寫:
```cpp
template <typename T, typename... Args>
ostream &print(ostream &os, const T &t, const Args &... rest)
{
if (sizeof...(rest) > 0)
{
cout << "sizeof: " << sizeof...(rest) << "\n";
os << t << "\n"; // print the first argument
return print(os, rest...); // recursive call; print the other arguments
}
else
{
return os << t;
}
}
int main() { print(cout, 1, 2, 3, 4, 5) << endl; }
```
* 注意那個 `if (sizeof...(rest) > 0 )` 的部分;我原本是想要用這個來防止 compiler 去找 `print(ostream)` 這個 function,可是還是會噴 error,結果有人跟我說這要用 **SFINAE** 來解...
* **或者 用 C++17 的 `if constexpr`....**:
```cpp
template <typename T, typename... Args>
ostream &print(ostream &os, const T &t, const Args &... rest)
{
if constexpr (sizeof...(rest) > 0)
{
cout << "sizeof: " << sizeof...(rest) << "\n";
os << t << "\n"; // print the first argument
return print(os, rest...); // recursive call; print the other arguments
}
else
{
return os << t;
}
}
int main() { print(cout, 1, 2, 3, 4, 5) << endl; }
```
* `if constexpr` 應該算是一種 conditional compilation
### 16.4.2 Pack Expansion
* 除了拿 pack 的 size,也可以把 pack **expand**
* 在 expand 的時候,我們也要提供一個 **pattern** 來說明怎麼展開每個 pack 內的 element
* 基本上 expand 就是把一個 pack 按照 pattern 變成一個一個 elements
* 要 trigger expand,就是在 pattern 右邊擺 `...`
* 換句話說 Primer 又先斬後奏了,因為上面的 code 已經用到了
* 例如我們的 `print` 就有用到兩次 expand:
```cpp
template <typename T, typename... Args>
ostream &
print(ostream &os, const T &t, const Args&... rest) // expand Args
{
os << t << ", ";
return print(os, rest...); // expand rest
}
```
* 第一個 expand 把 `const Args&` 展開然後**生成 `print` 的 function *parameter* list**
* 第二個 expand 是在遞迴 call `print` 的時候;這時則是**生成 `print` 的 *argument* list**
* 我們再細看他們各自的 "pattern":
* 展開 `Args` 的時候,pattern 其實就是 `const Args&`,這樣每個 element 就會在展開的時候多了 `const` 跟 reference
* 寫的更精確一點: `const type&`
* 例如:
```cpp
print(cout, i, s, 42); // two parameters in the pack
```
* 會被展開成:
```cpp
ostream& print(ostream&, const int&, const string&, const int&);
```
* 而第二個遞迴 call `print` 時,pattern 就很單純,只有 pack 本身的 name 而已,也就是 `rest`
* 會展開成這樣:
```cpp
print(os, s, 42);
```
#### Understanding Pack Expansions
* 上面的 expand 看起來很 toy? 來看看更進階的展開方式
* For example, we might write a second variadic function that calls `debug_rep` (§ 16.3, p. 695) on each of its arguments and then calls `print` to print the resulting `string`s:
```cpp
// call debug_rep on each argument in the call to print
template <typename... Args>
ostream &errorMsg(ostream &os, const Args&... rest) {
// print(os, debug_rep(a1), debug_rep(a2), ..., debug_rep(an)
return print(os, debug_rep(rest)...);
}
```
* 在 `errorMsg` 內呼叫 `print` 的地方用的 pattern 是 `debug_rep(rest)`
* That pattern says that **we want to call `debug_rep` on each element in the function parameter pack rest**.
* The resulting expanded pack will be a comma-separated list of calls to `debug_rep`
* 看 code:
```cpp
errorMsg(cerr, fcnName, code.num(), otherData, "other", item);
```
* 裡面呼叫 `print` 的部分會變這樣:
```cpp
print(cerr, debug_rep(fcnName), debug_rep(code.num()),
debug_rep(otherData), debug_rep("otherData"),
debug_rep(item));
```
* 如果你寫下面的 code 則無法編譯,**注意看 `...` 的位置**
```cpp
// passes the pack to debug_rep; print(os, debug_rep(a1, a2, ..., an))
print(os, debug_rep(rest...)); // error: no matching function to call
```
* 最上面的 code 是寫 `debug_rep(rest)...`,這裡則是寫 `debug(rest...)`, `...` 作用的 pattern 不同
* 硬要展開的話就會變成這樣:
```cpp
print(cerr, debug_rep(fcnName, code.num(), otherData, "otherData", item));
// no matching function call
```
### 16.4.3 *Forwarding* Parameter Packs
* 太棒惹,又是 forward,這次還要你 forward pack
* 其實 `vector::emplace_back` 就是這樣實作的
* 這次要為之前寫的 `StrVec`,他增加一個 `emplace_back`
* 或者要用練習題寫的 template 版本 `Vec` 新增也可以
* `emplace_back` 忘記的話看第九章
* `StrVec` container 只有存 `std::string` 一種型別
* 但是因為 `string` 的 ctor 有很多種,參數個數以及型別也不同,所以還是要把 `StrVec` 的 `emplace_back` 寫成 variadic template
* **然後我們希望可以用到 `string` 的 move ctor,這意味著我們需要把傳進來的 rvalue 的型別原封不動的丟給 ctor
* *會用到 `std::forward`***
* 為了做到這件事情,`emplace_back` 的 function parameters 就要是 rvalue reference to template parameter(forwarding reference)
```cpp
class StrVec {
public:
template <class... Args> void emplace_back(Args&&...);
// remaining members as in § 13.5 (p. 526)
};
```
* 注意看上面用到的 template parameter pack,他用到了 `&&`,代表展開之後的每個 type 是 rvalue reference to its corresponding template argument
```cpp
template <class... Args>
inline
void StrVec::emplace_back(Args&&... args) {
chk_n_alloc(); // reallocates the StrVec ifnecessary
alloc.construct(first_free++, std::forward<Args>(args)...);
}
```
* 然後傳給 `alloc.construct`,**傳入 arguments 時就要使用 `forward` 保持原本的型別**
* pattern 是 `std::forward<Args>(args)`
* 這個 pattern 同時把 template parameter pack 跟 function parameter pack 都用到了...
* 然後如果之前沒想到的話,仔細想,`construct` 其實也是 variadic template...
* 總之 expansion 會展開成 `std::forward<Ti>(ti)`
* `Ti` 就是傳給 `emplace_back` 的第 i 個 argument 的型別,`ti` 則是第 i 個 argument 本身
* 看 code:
```cpp
svec.emplace_back(10, 'c'); // adds ccccccccccas a new last element
```
* 則丟給 `construct` 的 expansion 會被展開成:
```cpp
std::forward<int>(10), std::forward<char>(c)
```
* 總之用了 `std::forward` 之後我們可以保證,如果原本傳給 `emplace_back` 的 value 是 rvalue,則傳給 `construct` 的 value 一樣是 rvalue:
```cpp
svec.emplace_back(s1 + s2); // uses the move constructor
```
* 丟給 `construct` 時:
```cpp
std::forward<string>(string("the end"))
```
* `std::forward` 腳括號內為什麼是 `string`
* 這個情況傳給 `construct` 的 value 型別為 rvalue reference
* 所以最後會由 `construct` 呼叫 `string` 的 **move ctor**
:::info
#### ADVICE: FORWARDING AND VARIADIC TEMPLATES
* 基本上 variadic templates 很常會跟 `forward` 一起使用,其 pattern 跟我們寫的 `emplace_back` 很像:
```cpp
// fun has zero or more parameters each of which is
// an rvalue reference to a template parameter type(forwarding reference)
template<typename... Args>
void fun(Args&&... args) // expands Args as a list of rvalue references
{
// the argument to work expands both Args and args
work(std::forward<Args>(args)...);
}
```
* 跟在 `construct` 內一樣,我們在 `work` 也會同時展開 `Args` 跟 `args`(function/template parameter packs)
* 跟 `emplace_back` 也一樣,讓參數是 rvalue reference,可以傳入任何 type;用 forward,則可以維持原本 argument 的型別
:::
## 16.5 Template Specializations
* template 定義的 generic code 有時候不可能滿足所有 type,甚至根本不能編譯,或者行為是錯的
* 這時候我們就可以根據某個 type 的特定性為它寫一個特別的版本,這樣行為才會正確或更有效率
* 例如我們的 `compare`,完全不能比較 character pointers
* 我們希望比較 char ptr 時是呼叫(lib已經寫好且行為正確的) `strcmp`,而不是用原本 template 定義的直接比較指標
* 實際上 16.1.1 已經寫了一個*有點像*的東西,還記得的話:
```cpp
// first version; can compare any two types
template <typename T> int compare(const T&, const T&); // second version to handle string literals
template<size_t N, size_t M>
int compare(const char (&)[N], const char (&)[M]);
```
* 但是上面有兩個 nontype parameter 的版本只能處理 string literal 或者 `char` array,
* 如果你傳 `(const) char*`,一樣是第一個版本會被呼叫:
```cpp
const char *p1 = "hi", *p2 = "mom";
compare(p1, p2); // calls the first template
compare("hi", "mom"); // calls the template with two nontype parameters
```
* 第二個版本 non viable,因為不可能把 `(const) char*` 轉成 reference to array
* 我們可以定義對第一個版本的 template 定義 **template specialization** 來解決這個問題
* A **specialization** is a separate definition of the template **in which one or more template parameters are specified to have particular types.**
#### Defining a Function Template Specialization
* 格式長這樣:
```cpp
// special version of compare to handle pointers to character arrays
template <>
int compare(const char* const &p1, const char* const &p2) {
return strcmp(p1, p2);
}
```
* 用 `template<>` 來說這是某個 template 的 specialization
* 然後 template type parameter 全部都要給定
* 注意上面的 specialization 的 function parameter type 有點複雜,為什麼要這樣寫得先從原本的 template 講起:
```cpp
template <typename T> int compare(const T&, const T&);
```
* 首先我們的 specialization,除了 T 以外,其他型別都要一致,包含 `const`,reference 等等
* 而這邊原本的 template 它是吃一個 **reference to 「`const` T」**
* 我們這邊是希望寫一個 T 為 `const char*` 的 specialization
* 所以我們的 specialization 要宣告為 **reference to `const` 「`const char*`」**
* 正式 type 定義就是 reference to `const` pointer to `const` char
* `const char* const &`
#### Function Overloading versus Template Specializations
* 本質上定義一個 template specialization 是在搶 compiler 的工作(因為它做的不會照你的期望)
* **It is important to realize that *a specialization is an instantiation*; it is not an overloaded instance of the function name.**
* Specializations instantiate a template; they do not overload it. As a result, **specializations do not affect function matching.**
* overloading 跟 specialization 本質上不同
* overloading 會影響 function matching,specialization 不會
* specialization 其實只是告訴 compiler 特定的 instantiation 應該長怎樣,並**不會影響 compiler 看到 function template 被呼叫時怎麼 deduce 成特定的 instantiation**
* 假設原本有這兩種:
```cpp
// first version; can compare any two types
template <typename T> int compare(const T&, const T&); // second version to handle string literals
template<size_t N, size_t M>
int compare(const char (&)[N], const char (&)[M]);
```
* 然後你定義了第一個的 specialization,就如同上面定義的那個參數很噁心的版本
* 當你寫
```cpp
compare("hi", "mom")
```
* 兩個都是 exact match,但是因為 template nontype parameter 的版本更 specialized,所以 compiler 會選它,這時你定義的 specialization 完全不會被用到
* 但如果你不要定義成 specialization,定義成 ordinary function
* 則這時會有三個 viable function,一個就是你新定義的一般 function,另外兩個是 template 生的
* 而之前也學到,這種情況下 compiler 會選擇一般 function。
:::warning
#### KEY CONCEPT: ORDINARY SCOPE RULES APPLY TO SPECIALIZATIONS
* 要定義某個 template 的 specialization 時,template declaration 要 in scope
* **而 user code 要使用這個 specialization 對應的 instantiation 時這個 specialization 的宣告也要 in scope!!**
* 如果在使用某特定的 instantiation 時,對應的 specialization 還沒 in scope,compiler 就會生一個它自己的版本,這樣就用不到自訂的 specialization 了,這樣(預期)行為就會出錯
* 而且這種 error 很難找
:::
:::info
* Best Practice: **Templates and their specializations should be declared in the same header file.**
* Declarations for all the templates with a given name should appear first, followed by any specializations of those templates.
:::
#### Class Template Specializations
* class template 也可以 specialize
* 用 `std::hash` 舉例
* 還記得 unordered container 嗎?
* 要存在 `std::hash<key_type>` 才能存在 unordered container
* 忘了去看 11 章
* 如果我們要在 unorder 容器放 `Sales_data` 等沒有支援 `hash` 的 type,但又想要容器用預設的 `hash<key_type>` 我們就要提供 `hash<key_type>` 的 specialization
* `std::hash` 要定義下列 member:
* `operator()`,吃 container 的 `key_type`,return `size_t`
* 兩個 type member: `result_type` 跟 `argument_type`,分別就是 `operator()` 的 return type 跟 argument type
* default ctor 跟 copy assignment 用 compiler 合成
* 另外,要對 class/function template 定義 specialization 有一個前提:
* **必須在跟它同樣 scope 的地方定義 specialization**
* 但是 `std::hash` 定義在 `std` 這個 namespace 內耶? 要怎麼在這裏面定義 specialization 呢?
* 18.2 章會講到,我們現在只要知道如下操作就可:
* 我們需要"開啟"(open) namespace:
```cpp
// open the std namespace so we can specialize std::hash
namespace std {
} // close the std namespace; note: no semicolon after the close curly
```
* 這樣你在 `{}` 內定義的 name 就會屬於 `std` 這個 namespace 了
* 題外話: reopen `std` 除了少數情況都是 UB
* https://en.cppreference.com/w/cpp/language/extending_std
* 下面的 code 會寫一個 `std::hash<Sales_data>` 的 specialization:
```cpp
// open the std namespace so we can specialize std::hash
namespace std {
template <> // we’re defining a specialization with
struct hash<Sales_data> // the template parameter of Sales_data
{
// the type used to hash an unordered container must define these types
typedef size_t result_type;
typedef Sales_data argument_type; // by default, this type needs ==
size_t operator()(const Sales_data& s) const;
// our class uses synthesized copy control and default constructor
};
size_t
hash<Sales_data>::operator()(const Sales_data& s) const
{
return hash<string>()(s.bookNo) ^
hash<unsigned>()(s.units_sold) ^
hash<double>()(s.revenue);
}
}// close the std namespace
```
* 我們對 `std::hash` 這個 template 定義一個 specialization,`hash<Sales_data>`
* 直接使用 library 定義好的 `hash<string/unsigned/double>` 來算 hash code
* 而去看 `std::hash` 的文件,這些版本其實也是標準定義的 specializations
* 總之寫了這個以後,我們就可以寫下面的 code 了:
```cpp
// uses hash<Sales_data> and Sales_data::operator== from § 14.3.1 (p. 561)
unordered_multiset<Sales_data> SDset;
```
* 另外需要在 `Sales_data` 內宣告 `std::hash<Sales_data>` 為 friend,因為它會用到 private member
```cpp
template <class T> class std::hash; // needed for the friend declaration
class Sales_data {
friend class std::hash<Sales_data>;
// other members as before
};
```
* 另外整個 specialization 要定義在 `Sales_data` 的 header 內,這樣 compiler 實例化 `unorder<Sales_data>`(然後間接實例化 `std::hash<Sales_data>`) 才會看的到這個 specialization
#### Class-Template *Partial* Specializations
* 跟 function template 不同
* class template specialization 不一定要全部 template argument 都提供
* 或者「只需提供某些 arg 的某些特性」(`const` , l/r reference 之類的)
* 這叫做 **partial specializations**
* A class template **partial specialization is itself a template.**
* 因為沒有全部 template argument 都完整提供,所以還是 template
* 之前用過的 `std::remove_reference`,其實就有很多種 partial specializations:
```cpp
// original, most general template
template <class T> struct remove_reference {
typedef T type;
};
// partial specializations that will be used for lvalue and rvalue references
template <class T> struct remove_reference<T&> // lvalue references
{ typedef T type; };
template <class T> struct remove_reference<T&&> // rvalue references
{ typedef T type; };
```
* 第一個 template 最 general,可以實例化任何 type;後面兩個是 partial specializations
* 看看丟不同 type 給 `std::remove_reference` 分別會用到哪種版本
```cpp
int i;
// decltype(42) is int, uses the original template
remove_reference<decltype(42)>::type a;
// decltype(i) is int&, uses first (T&) partial specialization
remove_reference<decltype(i)>::type b;
// decltype(std::move(i)) is int&&, uses second (i.e., T&&) partial specialization
remove_reference<decltype(std::move(i))>::type c;
```
#### Specializing Members but Not the Class
* 我們可以只 spectialize 某個 class instantiation *的 member*:
```cpp
template <typename T> struct Foo {
Foo(const T &t = T()): mem(t) { }
void Bar() { /* .. . */}
T mem;
// other members of Foo
};
template<> // we're specializing a template
void Foo<int>::Bar() // we're specializing the Bar member of Foo<int>
{
// do whatever specialized processing that applies to ints
}
```
* 我們這樣寫的話,當我們真的定義了 `Foo<int>` 且使用 `Bar` 時,compiler 就不會實例化這個 member,而是用我們定義的:
```cpp
Foo<string> fs; // instantiates Foo<string>::Foo()
fs.Bar(); // instantiates Foo<string>::Bar()
Foo<int> fi; // instantiates Foo<int>::Foo()
fi.Bar(); // ***uses our specialization ofFoo<int>::Bar()***
```
* 最後一行 code 會用我們定義的 `Foo<int>::Bar()`