欧美性猛交XXXX免费看蜜桃,成人网18免费韩国,亚洲国产成人精品区综合,欧美日韩一区二区三区高清不卡,亚洲综合一区二区精品久久

打開(kāi)APP
userphoto
未登錄

開(kāi)通VIP,暢享免費電子書(shū)等14項超值服

開(kāi)通VIP
C ++template模板 學(xué)習筆記
“腳本之家
”,與百萬(wàn)開(kāi)發(fā)者在一起

出處:騰訊技術(shù)工程(ID:Tencent_TEG)
作者:readywang(王玉龍)
如若轉載請聯(lián)系原公眾號

template 是 c++ 相當重要的組成部分,堪稱(chēng) c++語(yǔ)言的一大利器。在大大小小的 c++ 程序中,模板無(wú)處不在。c++ templates 作為模板學(xué)習的經(jīng)典書(shū)籍,歷來(lái)被無(wú)數 c++學(xué)習者所推崇。第二版書(shū)籍覆蓋了 c++ 11 14 和 17 標準,值得程序猿們精讀學(xué)習,特此整理學(xué)習筆記,將每一部分自認為較為重要的部分逐條陳列,并對少數錯誤代碼進(jìn)行修改

一、函數模板

1.1 函數模板初探

1.模板實(shí)例化時(shí),模板實(shí)參必須支持模板中類(lèi)型對應的所有運算符操作。

template <typename T>
T max(const T &a, const T &b) {
    return a > b? a : b;
}

class NdGreater {
};

int main() {
    NdGreater n1, n2;
    ::max(n1, n2); // 不支持 > 編譯報錯
}

2.模板編譯時(shí)會(huì )進(jìn)行兩階段檢查

a.模板定義時(shí),進(jìn)行和類(lèi)型參數無(wú)關(guān)的檢查,如未定義的符號等。

b.模板實(shí)例化時(shí),進(jìn)行類(lèi)型參數相關(guān)的檢查。

template<typename T>
void foo(T t) {
    undeclared(); // 如果 undeclared()未定義,第一階段就會(huì )報錯,因為與模板參數無(wú)關(guān)
    static_assert(sizeof(T) > 10, 'T too small'); //與模板參數有關(guān),只會(huì )在第二階段報錯
}

3.根據兩階段檢查,模板在實(shí)例化時(shí)要看到完整定義,最簡(jiǎn)單的方法是將實(shí)現放在頭文件中。

1.2 模板參數推斷

1.函數模板的模板參數可以通過(guò)傳遞的函數參數進(jìn)行推斷。

2.函數推斷時(shí)會(huì )用到參數類(lèi)型轉換,規則如下:

a.如果函數參數是按引用傳遞的,任何類(lèi)型轉換都不被允許。(此處有疑問(wèn),const 轉換還是可以的)

b.如果函數參數是按值傳遞的,可以進(jìn)行退化(decay)轉換:const(指針或者引用只有頂層 const 可以被忽略) 和 volatile 被忽略;引用變?yōu)榉且?;數組和函數變?yōu)閷羔橆?lèi)型。

template <typename T>
void RefFunc(const T &a, const T &b){};

template <typename T>
void NoRefFunc(T a, T b){};

int main() {
    int *const ic = nullptr;
    const int *ci = nullptr;
    int *p = nullptr;
    RefFunc(p, ic);  // ok 頂層const可以被忽略 T 為 int *
    RefFunc(p, ci);  // error 底層const不可以忽略
    NoRefFunc(p, ci); // error 底層const不可以忽略

    int i = 0;
    int &ri = i;
    NoRefFunc(i, ri); // ok ri從int &轉換為int

    int arr[4];
    NoRefFunc(p, arr);  // ok arr 被推斷為int *

    NoRefFunc(4, 5.0);  // error T 可以推斷為int或double
}

3.上文的最后一句調用,類(lèi)型推斷具有二義性,無(wú)法正確實(shí)例化??梢酝ㄟ^(guò)以下方式解決

a.類(lèi)型轉換:

b.顯式指定模板實(shí)參:

    NoRefFunc(static_cast<double>(4), 5.0);  // ok 類(lèi)型轉換
    NoRefFunc<int>(4, 5.0);  // 顯式指定

4.函數模板無(wú)法通過(guò)默認參數推斷模板參數。如果函數模板只有一個(gè)函數參數,且函數參數提供了默認值的情況,應該為模板類(lèi)型參數 T 也提供和函數參數默認值匹配的默認類(lèi)型。

template <typename T>
void Default(T t = 0){};

Default(); // error 無(wú)法推斷為int

template <typename T = int>
void Default(T t = 0){};

Default(); // ok 默認類(lèi)型為int

1.3 多模板參數

1.當函數返回類(lèi)型不能或不便由函數參數類(lèi)型直接推斷時(shí),可以在函數模版中新增模板參賽指定返回類(lèi)型。

2.c++11 之后,可以通過(guò) auto + decltype +尾后返回類(lèi)型 推斷函數模板返回類(lèi)型。當函數參數為引用類(lèi)型時(shí),返回類(lèi)型應該為非引用。而decltype 會(huì )保留引用,因此還需通過(guò) decay 進(jìn)行類(lèi)型退化。

3.c++14 之后,可以通過(guò) auto 直接推斷函數模板返回類(lèi)型,前提是函數內部的多個(gè)返回語(yǔ)句推斷出的返回類(lèi)型要一致。auto 會(huì )自動(dòng)對類(lèi)型進(jìn)行 decay。

4.c++11 之后,可以通過(guò) common_type 返回多個(gè)模版類(lèi)型參賽的公共類(lèi)型,common_type 返回的類(lèi)型也是 decay 的。

#include<type_traits>
// 單獨通過(guò)RT指定返回類(lèi)型
template <typename RT, typename T1, typename T2>
RT max1(const T1& a, const T2& b) { return a > b ? a : b; }

// auto c++11支持 通過(guò)decay 進(jìn)行類(lèi)型退化 typename 用于聲明嵌套從屬名稱(chēng) type 為類(lèi)型而不是成員
template <typename T1, typename T2>
auto max2(const T1& a, const T2& b) -> typename std::decay<decltype(a > b ? a : b)>::type { return a > b ? a : b; }

// auto c++14支持
template <typename T1, typename T2>
auto max3(const T1& a, const T2& b) { return a > b ? a : b; }

// common_type c++11支持 max4(5, 7.3) max4(7.4, 5) 的返回類(lèi)型均被推斷為double
template <typename T1, typename T2>
typename std::common_type<T1, T2>::type max4(const T1& a, const T2& b) { return a > b ? a : b; }

1.4 默認模板參數

1.可以給模板參數指定默認值。

// 默認模板參賽 因為RT需要T1 T2推斷,所以放在最后
template <typename T1, typename T2, typename RT = typename std::common_type<T1, T2>::type>
RT max5(const T1& a, const T2& b) { return a > b ? a : b; }

1.5 函數模板重載

1.一個(gè)非模板函數可以和同名的函數模板共存,并且函數模板可實(shí)例化為和非模板函數具有相同類(lèi)型參數的函數。函數調用時(shí),若匹配度相同,將優(yōu)先調用非模板函數。但若顯式指定模板列表,則優(yōu)先調用函數模板。

2.函數模板不可以進(jìn)行類(lèi)型自動(dòng)轉換,非模板函數可以。

#pragma once

template <typename T>
T max(T a, T b) {
    return a > b ? a : b;
}

template <typename RT, typename T1, typename T2>
RT max(T1 a, T2 b) {
    return a > b ? a : b;
}
int max(int a, int b) {
    return a > b ? a : b;
}

int main() {
    ::max(6, 8);  // 調用非模板函數
    ::max<>(6, 8); // 調用函數模板 max<int>
    ::max('a', 'b'); // 調用函數模板 max<char>
    ::max(4, 4.0); // 通過(guò)類(lèi)型轉換調用非模板函數
    ::max<double>(4, 4.0); //指定了返回類(lèi)型 調用max<double,int,double>
}

3.調用函數模板時(shí),必須保證函數模板已經(jīng)定義。

int max(int a, int b) {
    return a > b ? a : b;
}

template <typename T>
T max(T a, T b, T c) {
    return max(max(a,b),c);  //T為int時(shí),并不會(huì )調用max<int> 而是調用非模板函數
}

template <typename T>
T max(T a, T b) {
    return a > b ? a : b;
}

max(1, 2, 3);  // 最終調用非模板函數比較
max('sjx', 'wyl', 'shh'); // error 找不到二元的max<const char *>

二、類(lèi)模板

2.1 stack 類(lèi)模板實(shí)現

1.類(lèi)模板不可以定義在函數作用域或者塊作用域內部,通常定義在 global/namespace/類(lèi)作用域。

#include<vector>
#include<iostream>

template <typename T>
class Stack
{
public:
    void push(const T& value);
    void pop();
    T top();
    int size() const { elem_.size(); };
    bool empty() const { return elem_.empty(); };

    void print(std::ostream & out) const;
protected:
    std::vector<T> elem_;
};

template <typename T>
void Stack<T>::push(const T &value)
{
    elem_.push_back(value);
}

template <typename T>
void Stack<T>::pop()
{
    elem_.pop_back();
}

template <typename T>
T Stack<T>::top()
{
    return elem_.back();
}

template <typename T>
void Stack<T>::print(std::ostream &out) const
{
    for (auto e : elem_)
    {
        out << e << std::endl;
    }
}

2.2 stack 類(lèi)模板使用

1.直到 c++17,使用類(lèi)模板都需要顯式指定模板參數。

2.類(lèi)模板的成員函數只有在調用的時(shí)候才會(huì )實(shí)例化。

2.3 部分使用類(lèi)模板

1.類(lèi)模板實(shí)例化時(shí),模板實(shí)參只需要支持被實(shí)例化部分所有用到的操作。

int main()
{
    // 只會(huì )實(shí)例化類(lèi)模板中的push 和 print函數
    Stack<int> s;
    s.push(3);
    s.print(std::cout);

    // Stack<int>未重載<<運算符,實(shí)例化print函數時(shí)失敗
    Stack<Stack<int>> ss;
    ss.push(s);
    ss.print(std::cout);
    return 0;
}

2.c++11 開(kāi)始,可以通過(guò) static_assert 和 type_traits 做一些簡(jiǎn)單的類(lèi)型檢查

template <typename T>
class C
{
    static_assert(std::is_default_constructible<T>::value, 'class C requires default contructible');
};

2.4 友元

2.5 模板特化

1.可以對類(lèi)模板的一個(gè)參數進(jìn)行特化,類(lèi)模板特化的同時(shí)需要特化所有的成員函數,非特化的函數在特化后的模板中屬于未定義函數,無(wú)法使用。

// stringle類(lèi)型特化
template <>
class Stack<std::string>
{
public:
    void push(const std::string& value);
    /* 特化其他成員函數*/
};

2.6 模板偏特化

1.類(lèi)模板特化時(shí),可以只特化部分參數,或者對參數進(jìn)行部分特化。

// 指針類(lèi)型特化
template <typename T>
class Stack<T *>
{
    public:
    void push(T *value);
    void pop();
    T* top();
    int size() const { elem_.size(); };
    bool empty() const { return elem_.empty(); };
protected:
    std::vector<T *> elem_;
};

template <typename T>
void Stack<T*>::push(T *value)
{
    elem_.push_back(value);
}

template <typename T>
void Stack<T*>::pop()
{
    elem_.pop_back();
}

template <typename T>
T* Stack<T*>::top()
{
    return elem_.back();
}

2.7 默認類(lèi)模板參數

1.類(lèi)模板也可以指定默認模板參數。

template <typename T, typename COND = std::vector<T> >
class Stack
{
public:
    void push(const T& value);
    void pop();
    T top();
    int size() const { elem_.size(); };
    bool empty() const { return elem_.empty(); };
protected:
    COND elem_;
};

template <typename T, typename COND>
void Stack<T, COND>::push(const T &value)
{
    printf('template 1\n');
    elem_.push_back(value);
}

template <typename T, typename COND>
void Stack<T, COND>::pop()
{
    elem_.pop_back();
}

template <typename T, typename COND>
T Stack<T, COND>::top()
{
    return elem_.back();
}

2.8 類(lèi)型別名

1.為了便于使用,可以給類(lèi)模板定義別名。

typedef Stack<int> IntStack;
using DoubleStack = Stack<double>;

2.c++11 開(kāi)始可以定義別名模板,為一組類(lèi)型取一個(gè)方便的名字。

template <typename T>
using DequeStack = Stack<T, std::deque<T>>;

3.c++14 開(kāi)始,標準庫使用別名模板技術(shù),為所有返回一個(gè)類(lèi)型的 type_trait 定義了快捷的使用方式。

// stl庫定義
namespace std
{
    template <typename T>
    using add_const_t = typename add_const<T>::type;
}

typename add_const<T>::type;  //c++ 11 使用
std::add_const_t<T>; //c++14使用

2.9 類(lèi)模板類(lèi)型推導

1.c++17 開(kāi)始,如果構造函數能夠推斷出所有模板參數的類(lèi)型,那么不需要指定參數類(lèi)型了。

template <typename T>
class Stack
{
public:
    Stack() = default;
    Stack(T e): elem_({e}){};
protected:
    std::vector<T> elem_;
};

Stack intStack = 0; //通過(guò)構造函數推斷為int

2.類(lèi)型推導時(shí),構造函數參數應該按照值傳遞,而非按引用。引用傳遞會(huì )導致類(lèi)型推斷時(shí)無(wú)法進(jìn)行 decay 轉化。

Stack strStack = 'sjx';
//若構造函數參數為值傳遞,則T為const char *,引用傳遞時(shí)則為const char[4]

3.c++ 17 支持提供推斷指引來(lái)提供額外的推斷規則,推斷指引一般緊跟類(lèi)模板定義之后。

// 推斷指引,傳遞字符串常量時(shí)會(huì )被推斷為string
Stack<const char *> -> Stack<std::string>

2.10 聚合類(lèi)的模板化

1.聚合類(lèi):沒(méi)有顯式定義或繼承來(lái)的構造函數,沒(méi)有非 public 的非靜態(tài)成員,沒(méi)有虛函數,沒(méi)有 virtual,private ,protected 繼承。聚合類(lèi)也可以是模板。

template <typename T>
struct ValueWithComment
{
    T val;
    std::string comment;
};

ValueWithComment<int> vc;
vc.val = 42;
vc.comment = 'sjx';

三、非類(lèi)型模板參數

3.1 類(lèi)模板的非類(lèi)型模板參數

1.模板參數不一定是類(lèi)型,可以是數值,如可以給 Stack 指定最大容量,避免使用過(guò)程元素增刪時(shí)的內存調整。

template <typename T, int MAXSIZE>
class Stack
{
public:
    Stack():num_(0){};
    void push(const T& value);
    void pop();
    T top();
    int size() const { return num_; };
    bool empty() const { return num_ == 0; };
protected:
    T elem_[MAXSIZE];
    int num_;
};

template <typename T, int MAXSIZE>
void Stack<T, MAXSIZE>::push(const T &value)
{
    printf('template 1\n');
    assert(num_ < MAXSIZE);
    elem_[num_++] = value;
}

template <typename T, int MAXSIZE>
void Stack<T, MAXSIZE>::pop()
{
    assert(num_ > 0);
    --num_;
}

template <typename T, int MAXSIZE>
T Stack<T, MAXSIZE>::top()
{
    assert(num_ > 0);
    return elem_[0];
}

3.2 函數模板的非類(lèi)型模板參數

1.函數模板也可以指定非類(lèi)型模板參數。

template<typename T, int VAL>
T addval(const T &num)
{
    return num + VAL;
}

int main()
{
    std::vector<int> nums = {1, 2, 3, 4, 5};
    std::vector<int>nums2(nums.size(),0);
    std::transform(nums.begin(),nums.end(),nums2.begin(),addval<int,5>);
    for(auto num : nums2)
    {
        printf('%d\n',num);
    }
}

3.3 非類(lèi)型模板參數限制

1.非類(lèi)型模板參數只能是整形常量(包含枚舉),指向 objects/functions/members 的指針,objects 或者 functions 的左值引用,或者是 std::nullptr_t(類(lèi)型是 nullptr),浮點(diǎn)數和類(lèi)對象不能作為非類(lèi)型模板參數。

2.當傳遞對象的指針或者引用作為模板參數時(shí),對象不能是字符串常量,臨時(shí)變量或者數據成員以及其它子對象。

3.對于非類(lèi)型模板參數是 const char*的情況,不同 c++版本有不同限制

a. C++11 中,字符串常量對象必須要有外部鏈接

b. C++14 中,字符串常量對象必須要有外部鏈接或內部鏈接

c. C++17 中, 無(wú)鏈接屬性也可

4.內部鏈接:如果一個(gè)名稱(chēng)對編譯單元(.cpp)來(lái)說(shuō)是局部的,在鏈接的時(shí)候其他的編譯單元無(wú)法鏈接到它且不會(huì )與其它編譯單元(.cpp)中的同樣的名稱(chēng)相沖突。例如 static 函數,inline 函數等。

  1. 如果一個(gè)名稱(chēng)對編譯單元(.cpp)來(lái)說(shuō)不是局部的,而在鏈接的時(shí)候其他的編譯單元可以訪(fǎng)問(wèn)它,也就是說(shuō)它可以和別的編譯單元交互。

6.非類(lèi)型模板參數的實(shí)參可以是任何編譯器表達式,不過(guò)如果在表達式中使用了 operator >,就必須將相應表達式放在括號里面,否則>會(huì )被作為模板參數列表末尾的>,從而截斷了參數列表。

#include<string>

template < double VAL > // error 浮點(diǎn)數不能作為非類(lèi)型模板參數
double process(double v)
{
    return v * VAL;
}

template < std::string name > // error class對象不能作為非類(lèi)型模板參數
class MyClass {
};

template < const char * name >
class Test{
};

extern const char s01[] = 'sjx'; // 外部鏈接
const char s02[] = 'sjx'; // 內部鏈接

template<int I, bool B>
class C{};
int main()
{
    Test<'sjx'> t0; // error before c++17
    Test<s01> t1; // ok
    Test<s02> t2; // since c++14
    static const char s03[] = 'sjx'; // 無(wú)鏈接
    Test<s03> t3; // since c++17

    C<42, sizeof(int)> 4 > c; //error 第一個(gè)>被認為模板參數列表已經(jīng)結束
    C<42, (sizeof(int)> 4) > c; // ok
}

3.4 用 auto 作為非模板類(lèi)型參數的類(lèi)

1.從 C++17 開(kāi)始,可以不指定非類(lèi)型模板參數的具體類(lèi)型(代之以 auto),從而使其可以用于任意有效的非類(lèi)型模板參數的類(lèi)型。

template <typename T, auto MAXSIZE>
class Stack
{
public:
    using size_type = decltype(MAXSIZE); // 根據MAXSIZE推斷類(lèi)型
public:
    Stack():num_(0){};
    void push(const T& value);
    void pop();
    T top();
    size_type size() const { num_; };
    bool empty() const { num_ == 0; };
protected:
    T elem_[MAXSIZE];
    size_type num_;
};

template <typename T, int MAXSIZE>
void Stack<T, MAXSIZE>::push(const T &value)
{
    printf('template 1\n');
    assert(num_ < MAXSIZE);
    elem_[num_++] = value;
}

template <typename T, int MAXSIZE>
void Stack<T, MAXSIZE>::pop()
{
    assert(num_ > 0);
    --num_;
}

template <typename T, int MAXSIZE>
T Stack<T, MAXSIZE>::top()
{
    assert(num_ > 0);
    return elem_[0];
}

int main()
{
    Stack<int,20u> s1; // size_type為unsigned int
    Stack<double,40> s2; // size_type為int
}

四、可變參數模板

4.1 可變參數模板

1.c++11 開(kāi)始,模板可以接收一組數量可變的參數。

#include <iostream>
void print(){};  // 遞歸基
template<typename T, typename ...Types>
void print(T firstArg, Types ... args)
{
    std::cout << firstArg << std::endl;
    print(args...);
}

int main(){
    print(7.5, 'hello', 10); // 調用三次模板函數后再調用普通函數
}
  1. 當兩個(gè)函數模板的區別只在于尾部的參數包的時(shí)候,會(huì )優(yōu)先選擇沒(méi)有尾部參數包的函數模板。
// 使用模板函數的遞歸基,最后只剩一個(gè)參數時(shí)會(huì )優(yōu)先使用本模板
template<typename T>
void print(T arg)
{
    std::cout << arg << std::endl;
}

3.c++11 提供了sizeof...運算符來(lái)統計可變參數包中的參數數目

template<typename T, typename ...Types>
void print(T firstArg, Types ... args)
{
    std::cout << sizeof...(Types) << std::endl;
    std::cout << sizeof...(args) << std::endl;
    std::cout << firstArg << std::endl;
    print(args...);
}

4.函數模板實(shí)例化時(shí)會(huì )將可能調用的函數都實(shí)例化。

#include <iostream>
template<typename T, typename ...Types>
void print(T firstArg, Types ... args)
{
    std::cout << firstArg << std::endl;
    if(sizeof...(args) > 0)
        print(args...); // error 缺少遞歸基。即使只在參數包數目>0時(shí)調用,args為0個(gè)參數時(shí)的print函數
}
int main(){
    print(7.5, 'hello', 10); // 調用三次模板函數后再調用普通函數
}

4.2 折疊表達式

1.C++17 提供了一種可以用來(lái)計算參數包(可以有初始值)中所有參數運算結果的二元運算符 ...

template<typename ...T>
auto sum(T ...s)
{
    return (... + s); // ((s1+s2)+s3)...
}

4.3 可變參數模板的使用

4.4 變參類(lèi)模板和變參表達式

  1. 可變參數包可以出現在數學(xué)表達式中,用于表達式運算。
template<typename ...T>
void PrintDouble(const T &... args)
{
    print(args + args...); // 將args翻倍傳給print
}

template<typename ...T>
void addOne(const T & ...args)
{
    print(args + 1 ...); // 1和...中間要有空格
}
int main(){
    PrintDouble(7.5, std::string('hello'), 10); // 等價(jià)調用print(7.5 + 7.5, std::string('hello') + std::string('hello'), 10 + 10);
    addOne(1, 2, 3); // 輸出 2 3 4
}
  1. 可變參數包可以出現在下標中,用于訪(fǎng)問(wèn)指定下標的元素。

  2. 可變參數包可以為非類(lèi)型模板參數。

template<typename C, typename ...Index>
void printElems(const C &coll, Index ...idx)
{
    print(coll[idx]...);
}
template<typename C, std::size_t ...Idx> // 參數包為非類(lèi)型模板參數
void printIndex(const C &coll)
{
    print(coll[Idx]...);
}
int main(){
    std::vector<std::string> coll = {'wyl', 'sjx', 'love'};
    printElems(coll, 2, 0, 1); //相當于調用print(coll[2], coll[0], coll[1]);
    printIndex<std::vector<std::string>, 2, 0, 1>(coll); // 等同于以上調用
}
  1. 可變參數包也可以作為類(lèi)模板參數,用于表示數據成員的類(lèi)型,或對象可能的類(lèi)型等含義。
template <typename... T>
class Tuple;
Tuple<int, char> t; // 可以表示數據成員的類(lèi)型

template <typename... T>
class Variant;
Variant<int, double, bool, float> v; // 可以表示對象可能的類(lèi)型

5.推斷指引也可以是可變參數的。

namespace std {
    // std::array a{42,43,44} 會(huì )被推斷為 std::array<int,3> a{42,43,44}
    template<typename T, typename… U> array(T, U…) -> array<enable_if_t<(is_same_v<T, U> && …), T>, (1 + sizeof…(U))>;
}

五、基礎技巧

5.1 typename 關(guān)鍵字

1.c++規定模板中通過(guò)域作用符訪(fǎng)問(wèn)的嵌套從屬名稱(chēng)不是類(lèi)型名稱(chēng),為了表明該名稱(chēng)是類(lèi)型,需要加上 typename 關(guān)鍵字。

template<typename T>
class MyClass
{
public:
    void foo()
    {
        /* 若無(wú)typename SubType會(huì )被當做T中的一個(gè)static成員或枚舉值
        下方的表達式被理解為SubType 和 ptr的乘積 */
        typename T::SubType *ptr;
        //...
    }
};

5.2 零初始化

1.c++中對于未定義默認構造函數的類(lèi)型對象,定義時(shí)一般不會(huì )進(jìn)行默認初始化,這時(shí)候對象的值將是未定義的。

2.在模板中定義對象時(shí),為了避免產(chǎn)生未定義的行為,可以進(jìn)行零初始化。

template<typename T>
void foo()
{
    T x = T(); // 對x提供默認值
}

5.3 使用 this ->

1.若類(lèi)模板的基類(lèi)也是類(lèi)模板,這時(shí)在類(lèi)模板中不能直接通過(guò)名稱(chēng)調用從基類(lèi)繼承的成員,而應該通過(guò) this-> 或 Base::。

template<typename T>
class Base
{
public:
    void bar(){};
};

template<typename T>
class Derived:public Base<T>
{
public:
    void foo(){
        bar(); //ERROR 無(wú)法訪(fǎng)問(wèn)基類(lèi)bar成員,若global存在bar函數,則會(huì )訪(fǎng)問(wèn)全局bar函數
        this->bar(); // ok
        Base<T>::bar(); // ok
    }
};

5.4 使用裸數組或字符串常量的模板

1.當向模板傳遞裸數組或字符串常量時(shí),如果是引用傳遞,則類(lèi)型不會(huì ) decay;如果是值傳遞,則會(huì ) decay。

2.也可以通過(guò)將數組或字符串長(cháng)度作為非類(lèi)型模板參數,定義可以適配不同長(cháng)度的裸數組或字符串常量的模板。

template <typename T>
void foo(T t){};

template <typename T>
void RefFoo(const T &t){};

template<typename T, int N, int M>
bool less (T(&a)[N], T(&b)[M])
{
    for (int i = 0; i<N && i<M; ++i)
    {
    if (a[i]<b[i]) return true; if (b[i]<a[i]) return false;
    }
    return N < M;
}

int main(){
    foo('hello'); // T 為const char *
    RefFoo('hello'); // T 為const char[6]
    less('sjx', 'wyl'); // T 為const char, N,M為3
}

5.5 成員模板

1.不管類(lèi)是普通類(lèi)還是類(lèi)模板,類(lèi)中的成員函數都可以定義為模板。

2.若是將構造函數或者賦值運算符定義為模板函數,此時(shí)定義的模板函數不會(huì )取代默認的的構造函數和賦值運算符。下面定義的 operater = 只能用于不同類(lèi)型的 stack 之間的賦值,若是相同類(lèi)型,仍然采用默認的賦值運算符。

#include<deque>

template <typename T, typename COND = std::deque<T> >
class Stack
{
public:
    void push(const T& value);
    void pop();
    T top();
    int size() const { elem_.size(); };
    bool empty() const { return elem_.empty(); };
    //實(shí)現不同類(lèi)型的stack之間的相互賦值
    template <typename T2>
    Stack<T,COND> & operator = (const Stack<T2>& s);
protected:
    COND elem_;
};

template <typename T, typename COND>
template <typename T2>
Stack<T,COND> & Stack<T,COND>::operator = (const Stack<T2>& s)
{
    if((void *)this == (void *)&s)
    {
        return *this;
    }
    Stack<T2> tmp(s);
    elem_.clear();
    while(!tmp.empty())
    {
        elem_.push_front(tmp.top());
        tmp.pop();
    }
    return *this;
}

2.成員函數模板也可以被偏特化或者全特化。

#include<string>

class BoolString {
private:
    std::string value;
public:
    BoolString (std::string const& s): value(s) {}
    template<typename T = std::string>
    T get() const {
        return value;
    }
};

// 進(jìn)行全特化, 全特化版本的成員函數相當于普通函數,
// 放在頭文件中會(huì )導致重復定義,因此必須加inline
template<>
inline bool BoolString::get<bool>() const
{
    return value == 'true' || value == '1';
}

int main()
{
    BoolString s1('hello');
    s1.get();  // 'hello'
    s1.get<bool>(); // false
}

3.當通過(guò)依賴(lài)于模板參數的對象通過(guò).調用函數模板時(shí),需要通過(guò) template 關(guān)鍵字提示<是成員函數模板參數列表的開(kāi)始,而非<運算符。

#include<bitset>
#include<iostream>

template<unsigned int N>
void printBitSet(const std::bitset<N> &bs)
{
    // bs依賴(lài)于模板參數N 此時(shí)為了表明to_string后是模板參數,需要加template
    std::cout << bs.template to_string<char,std::char_traits<char>,std::allocator<char> >() << std::endl;
}

4.c++14 引入的泛型 lambda 是對成員函數模板的簡(jiǎn)化。

[](auto x, auto y)
{
    return x + y;
}

// 編譯將以上lamba轉化為下方類(lèi)
class SomeCompilerSpecificName {
public:
    SomeCompilerSpecificName();
    template<typename T1, typename T2>
    auto operator() (T1 x, T2 y) const {
        return x + y;
    }
};

5.6 變量模板

1.c++14 開(kāi)始,可以通過(guò)變量模板對變量進(jìn)行參數化。

2.變量模板的常見(jiàn)應用場(chǎng)景是定義代表類(lèi)模板成員的變量模板。

3.c++17 開(kāi)始,標準庫用變量模板為其用來(lái)產(chǎn)生一個(gè)值(布爾型)的類(lèi)型萃取定義了簡(jiǎn)化方式。

#include<iostream>

template <typename T = double>
constexpr T pi{3.1415926};

std::cout<< pi<> <<std::endl; // <>不可少,輸出3.1415926
std::cout<< pi<int> <<std::endl; // 輸出3

template<typename T>
class MyClass {
public:
    static constexpr int max = 1000; // 類(lèi)靜態(tài)成員
};

// 定義變量模板表示類(lèi)靜態(tài)成員
template <typename T>
int myMax = MyClass<T>::max;

// 使用更方便
auto i = myMax<int>; // 相當于auto i = MyClass<int>::max;

// 萃取簡(jiǎn)化方式,since c++17
namespace std {
    template<typename T>
    constexpr bool is_const_v = is_const<T>::value;
}

5.7 模板模板參數

1.當非類(lèi)型模板參數是一個(gè)模板時(shí),我們稱(chēng)它為模板模板參數。

2.實(shí)例化時(shí),模板模板參數和實(shí)參的模板參數必須完全匹配。

#include <deque>
#include <vector>

// 錯誤定義 deque 中的模板參數有兩個(gè):類(lèi)型和默認參數allocator
// 而模板模板參數Cont的參數只有類(lèi)型Elem
template<typename T,
template<typename Elem> class Cont = std::deque>
class Stack {
private:
    Cont<T> elems; // elements
public:
    void push(T const&); // push element
    void pop(); // pop element
    T const& top() const; // return top element
    bool empty() const { // return whether the stack is empty
        return elems.empty();
    }
};

// 正確定義
template<typename T, template<typename Elem, typename =
std::allocator<Elem>> class Cont = std::deque>
class Stack {
private:
    Cont<T> elems; // elements
public:
    void push(T const&); // push element
    void pop(); // pop element
    T const& top() const; // return top element
    bool empty() const { // return whether the stack is empty
        return elems.empty();
    }
};

// 使用
Stack<int> iStack;
Stack<double,std::vector> dStack;

六、移動(dòng)語(yǔ)義和 enable_if<>

6.1 完美轉發(fā)

1.c++11 引入了引用折疊:創(chuàng )建一個(gè)引用的引用,引用就會(huì )折疊。除了右值引用的右值引用折疊之后還是右值引用之外,其它的引用全部折疊成左值引用。

2.基于引用折疊和 std::forward,可以實(shí)現完美轉發(fā):將傳入將被參數的基本特性(是否 const,左值、右值引用)轉發(fā)出去。

#include <utility>
#include <iostream>
class X {
};
void g (X&) {
    std::cout << 'g() for variable\n';
}
void g (X const&) {
    std::cout << 'g() for constant\n';
}
void g (X&&) {
    std::cout << 'g() for movable object\n';
}

template<typename T>
void f (T&& val) {
    g(std::forward<T>(val));
}
int main()
{
    X v; // create variable
    X const c; // create constant
    f(v); // f() for variable calls f(X&) => calls g(X&)
    f(c); // f() for constant calls f(X const&) => calls g(X const&)
    f(X()); // f() for temporary calls f(X&&) => calls g(X&&)
    f(std::move(v)); // f() for move-enabled variable calls f(X&&)=>calls g(X&&)
}

6.2 特殊成員函數模板

1.當類(lèi)中定義了模板構造函數時(shí):

a.定義的模板構造函數不會(huì )屏蔽默認構造函數。

b.優(yōu)先選用匹配程度高的構造函數

c.匹配程度相同時(shí),優(yōu)先選用非模板構造函數

#include <utility>
#include <string>
#include <iostream>
class Person
{
private:
    std::string name;
public:
    // generic constructor for passed initial name:
    template<typename STR>
    explicit Person(STR&& n) : name(std::forward<STR>(n)) {
        std::cout << 'TMPL-CONSTR for ’' << name <<std::endl;
    }
    // copy and move constructor:
    Person (Person const& p) : name(p.name) {
        std::cout << 'COPY-CONSTR Person ’' << name << '’\n';
    }
    Person (Person&& p) : name(std::move(p.name)) {
        std::cout << 'MOVE-CONSTR Person ’' << name << '’\n';
    }
};

int main()
{
    Person p1('tmp'); // ok 調用模板函數n 被推斷為const char [4]類(lèi)型,可以賦值給string
    Person p2(p1); // error 模板函數相比Person (Person const& p)構造函數更匹配,n 被推斷為Person &類(lèi)型,最終將Person對象賦值給string
    Person p3(std::move(p1)); // ok 同等匹配程度時(shí),優(yōu)先調用Person (Person&& p)
}

6.3 通過(guò) std::enable_if 禁用模板

1.c++11 提供了輔助模板 std::enable_if,使用規則如下:

a.第一個(gè)參數是布爾表達式,第二個(gè)參數為類(lèi)型。若表達式結果為 true,則 type 成員返回類(lèi)型參數,此時(shí)若未提供第二參數,默認返回 void。

b.若表達式結果為 false,根據替換失敗并非錯誤的原則,包含 std::enable_if 的模板將會(huì )被忽略。

2.c++14 提供了別名模板技術(shù)(見(jiàn) 2.8 節),可以用 std::enable_if_t<>代替 std::enable_if<>::type.

3.若不想在聲明中使用 std::enable_if,可以提供一個(gè)額外的、有默認值的模板參數。

#include <type_traits>
template<typename T>
typename std::enable_if<(sizeof(T) > 4)>::type
foo1(){}

// std::enable_if為true時(shí)等同下方函數,未提供第二參數默認返回void; false時(shí)函數模板被忽略
// void foo1(){};
template<typename T>
typename std::enable_if<(sizeof(T) > 4), bool>::type
foo2(){}

// std::enable_if為true時(shí)等同下方函數,false時(shí)函數模板被忽略
// bool foo2(){};

// 提供額外默認參數進(jìn)行類(lèi)型檢查
template<typename T, typename = typename std::enable_if<(sizeof(T) > 4)>::type>

void foo3(){}

// std::enable_if為true時(shí)等同下方函數模板,false時(shí)函數模板被忽略
// template<typename T, typename = void>
// void foo3(){};

6.4 使用 std::enable_if

1.通過(guò) std::enable_if 和標準庫的類(lèi)型萃取 std::is_convertiable<FROM, TO>可以解決 6.2 節構造函數模板的問(wèn)題。

class Person
{
private:
    std::string name;
public:
    // 只有STR可以轉換為string時(shí)才有效
    template<typename STR, typename = typename std::enable_if<std::is_convertible<STR, std::string>::value>::type>
    explicit Person(STR&& n) : name(std::forward<STR>(n)) {
        std::cout << 'TMPL-CONSTR for ’' << name <<std::endl;
    }
    // copy and move constructor:
    Person (Person const& p) : name(p.name) {
        std::cout << 'COPY-CONSTR Person ’' << name << '’\n';
    }
    Person (Person&& p) : name(std::move(p.name)) {
        std::cout << 'MOVE-CONSTR Person ’' << name << '’\n';
    }
};

6.5 使用 concept 簡(jiǎn)化 enable_if<>表達式

1.c++20 提出了 concept 模板可以進(jìn)行編譯期條件檢查,大大簡(jiǎn)化了 enable_if

template <typename T>
concept convert_to_string = std::is_convertible<STR, std::string>::value;

class Person
{
private:
    std::string name;
public:
    // 只有STR可以轉換為string時(shí)才有效
    template<convert_to_string STR>
    explicit Person(STR&& n) : name(std::forward<STR>(n)) {
        std::cout << 'TMPL-CONSTR for ’' << name <<std::endl;
    }
    // copy and move constructor:
    Person (Person const& p) : name(p.name) {
        std::cout << 'COPY-CONSTR Person ’' << name << '’\n';
    }
    Person (Person&& p) : name(std::move(p.name)) {
        std::cout << 'MOVE-CONSTR Person ’' << name << '’\n';
    }
};

七、按值傳遞還是按引用傳遞

7.1 按值傳遞

1.當函數參數按值傳遞時(shí),原則上所有參數都會(huì )被拷貝。

2.當傳遞的參數是純右值時(shí),編譯器會(huì )優(yōu)化,避免拷貝產(chǎn)生;從 c++17 開(kāi)始,要求此項優(yōu)化必須執行。

3.按值傳遞時(shí),參數會(huì )發(fā)生 decay,如裸數組退化為指針,const、volatile 等限制符會(huì )被刪除。

#include <string>
template <typename T>
void foo(T t){};

int main(){
    std::string s('Sjx');
    foo(s); // 進(jìn)行拷貝
    foo(std::string('sjx')); // 避免拷貝
    foo('sjx'); // T decay為const char *
}

7.2 按引用傳遞

1.當函數參數按照引用傳遞時(shí),不會(huì )被拷貝,并且不會(huì ) decay。

2.當函數參數按照引用傳遞時(shí),一般是定義為 const 引用,此時(shí)推斷出的類(lèi)型 T 中無(wú) const。

3.當參數定義為非常量引用時(shí),說(shuō)明函數內部可以修改傳入參數,此時(shí)不允許將臨時(shí)變量傳給左值引用。此時(shí)若傳入參數是 const 的,則類(lèi)型 T 被推斷為 const,此時(shí)函數內部的參數修改將報錯.

template <typename T>
void print(const T &t){};

template <typename T>
void out(T &t){};

template <typename T>
void modify(T &t)
{
    t = T();
};
int main(){
    print('sjx'); // arg為const char[3], T 為char[3]
    std::string s('Sjx');
    out(s); // ok
    out(std::string('sjx')); // error 不能將臨時(shí)變量傳給左值引用
    const std::string name = 'sjx';
    modify(name); //error T 被推斷為 const std::string, 此時(shí)modify不能修改傳入參數
}

4.對于給非 const 引用參數傳遞 const 對象導致編譯失敗的情形,可以通過(guò) static_assert std::enable_if 或者 concept 等方式進(jìn)行檢查。

// static_assert 觸發(fā)編譯期錯誤
template <typename T>
void modify(T &t)
{
    static_assert(!std::is_const<T>::value, 'param is const');
    t = T();
};

// std::enable_if禁用模板
template <typename T, typename = typename std::enable_if<!std::is_const<T>::value> >
void modify(T &t)
{
    t = T();
};

// concept禁用模板
template <typename T>
concept is_not_const = !std::is_const<T>::value;

template <is_not_const T>
void modify(T &t)
{
    t = T();
};

5.當完美轉發(fā)時(shí),會(huì )將函數參數定義為右值引用。此時(shí)若在函數內部用 T 定義未初始化的變量,會(huì )編譯失敗。

template<typename T>
void passR(T &&t)
{
    T x;
}

std::string s('Sjx');
passR(s); // error T 推斷為string &,初始化時(shí)必須綁定到對象

7.3 使用 std::ref()和 std::cref()

1.c++11 開(kāi)始,若模板參數定義為按值傳遞時(shí),調用者可以通過(guò) std::cref 或 std::ref 將參數按照引用傳遞進(jìn)去。

2.std::cref 或 std::ref創(chuàng )建了一個(gè) std::reference_wrapper<>的對象,該對象引用了原始參數,并被按值傳遞給了函數模板。std::reference_wrapper<>對象只支持一個(gè)操作:向原始對象的隱式類(lèi)型轉換。

template<typename T>
void foo(T arg)
{
    T x;
}

int main(){
    std::string s = 'hello';
    foo(s); // T 為std::string
    foo(std::cref(s)); // error T 為std::reference_wrapper<std::string> 只支持向std::string的類(lèi)型轉換
}

7.4 處理字符串常量和裸數組

1.字符串常量或裸數組傳遞給模板時(shí),如果是按值傳遞,則會(huì ) decay;如果是按照引用傳遞,則不會(huì ) decay。實(shí)際應用時(shí),可以根據函數作用加以選擇,若要比較大小,一般是按照引用傳遞;若是比較參數類(lèi)型是否相同,則可以是按值傳遞。

7.5 處理返回值

1.函數返回值也可以是按值返回或按引用返回。若返回類(lèi)型為非常量引用,則表示可以修改返回對象引用的對象。

2.模板中即使使用 T 作為返回類(lèi)型,也不一定能保證是按值返回。

template<typename T>
T retR(T &&p)
{
    return T();
}

template<typename T>
T retV(T t)
{
    return T();
}

int main()
{
    int x;
    retR(x); // error T 被推斷為int &
    retV<int &>(x); // error T推斷為int &
}

3.為保證按值返回,可以使用 std::remove_reference<>或 std::decay<>。

7.6 關(guān)于模板參數聲明的推薦方法

1.一般通常按值傳遞,如有特殊需要,可以結合實(shí)際按引用傳遞。

2.定義的函數模板要明確使用范圍,不要過(guò)分泛化。

八、編譯期編程

8.1 模板元編程

1.模板元編程:在編譯期通過(guò)模板實(shí)例化的過(guò)程計算程序結果。

/* 定義用于編譯期判斷素數的模板 */
template<unsigned p ,unsigned d>
struct DoIsPrime
{
    // 從p%d !=0 開(kāi)始依次判斷 p%(d-1)!=0,p%(d-2)!=0,...
    static constexpr bool value = (p % d != 0) && DoIsPrime<p, d-1>::value;
};

// 遞歸基
template<unsigned p>
struct DoIsPrime<p ,2>
{
    static constexpr bool value = (p % 2 != 0);
};

// 提供給用戶(hù)調用的模板
template<unsigned p>
struct IsPrime
{
    static constexpr bool value = DoIsPrime<p, p / 2>::value;
};

// 對于p/2<2的情形進(jìn)行特化
template<>
struct IsPrime<0> {
    static constexpr bool value = false;
};

template<>
struct IsPrime<1> {
    static constexpr bool value = false;
};

template<>
struct IsPrime<2> {
    static constexpr bool value = false;
};

template<>
struct IsPrime<3> {
    static constexpr bool value = false;
};


int main(){
    IsPrime<9>::value; // 編譯期通過(guò)層層遞歸實(shí)例化最后得到結果為false
}

8.2 通過(guò) constexpr 進(jìn)行計算

1.c++11 提出了 constexpr 關(guān)鍵字可以用于修飾函數返回值,此時(shí)該函數為常量表達式函數,編譯器可以在編譯期完成該函數的計算。

2.c++11 中規定常量表達式函數在使用前必須要知道完整定義,不能僅僅是聲明,同時(shí)函數內部只能有一條返回語(yǔ)句。

3.c++14 中移除了常量表達式函數只能有一條返回語(yǔ)句的限制。

4.編譯器可以在編譯期完成該函數計算,但是是否進(jìn)行還取決于上下文環(huán)境及編譯器。

// constexpr函數只能有一個(gè)return語(yǔ)句
constexpr bool doIsPrime(unsigned p, unsigned d)
{
    return d != 2 ? (p % d != 0) && doIsPrime(p, d - 1) : (p % 2 != 0);
}

constexpr bool isPrime(unsigned p)
{
    return p < 4 ? !(p < 2) : doIsPrime(p, p / 2);
}
int main(){
    constexpr bool ret = isPrime(9);     // constexpr 限定必須在編譯期得到結果
}

8.3 通過(guò)模板偏特化進(jìn)行路徑選擇

1.可以通過(guò)模板偏特化在不同的實(shí)現方案之間做選擇。

// 根據是否是素數判斷是否喜歡
template <unsigned int SZ, bool = isPrime(SZ)>
struct IsLove;

// 特化兩個(gè)版本處理
template< unsigned int SZ>
struct IsLove<SZ, false>
{
    static constexpr bool isLove = false;
};

template< unsigned int SZ>
struct IsLove<SZ, true>
{
    static constexpr bool isLove = true;
};
int main()
{
    IsLove<520>::isLove; // 編譯期就知道了520不是喜歡
}

8.4 SFINAE(替換失敗不是錯誤)

  1. SFINAE:當函數調用的備選方案中出現函數模板時(shí),編譯器根據函數參數確定(替換)函數模板的參數類(lèi)型及返回類(lèi)型,最后評估替換后函數的匹配程度。替換過(guò)程中可能失敗,此時(shí)編譯器會(huì )忽略掉這一替換結果。

  2. 替換和實(shí)例化不同,替換只涉及函數函數模板的參數類(lèi)型及返回類(lèi)型,最后編譯器選擇匹配程度最高的函數模板進(jìn)行實(shí)例化。

#include<vector>
#include<iostream>

// 返回裸數組長(cháng)度的模板,只有用裸數組替換時(shí)才能成功
template<typename T, unsigned N>
std::size_t len (T(&)[N])
{
    return N;
}

// 只有含有T::size_type的類(lèi)型才能替換成功
template<typename T>
typename T::size_type len (T const& t)
{
    return t.size();
}

// ... 表示為可變參數,匹配所有類(lèi)型, 但匹配程度最差
std::size_t len(...)
{
    return 0;
}

int main()
{
    int a[10];
    std::cout << len(a) <<std::endl; // 匹配裸數組
    std::cout << len('sjx') <<std::endl; // 匹配裸數組
    std::vector<int> v;
    std::cout << len(v) <<std::endl; // T::size_type
    int *p;
    std::cout << len(p) <<std::endl; // 函數模板均不匹配,最后調用變參函數
    std::allocator<int> x;

    /* std::allocator 定義了size_type,所以匹配T::size_type和變參函數,
    前者匹配程度更高,因此選擇該模板。但在實(shí)例化時(shí)會(huì )發(fā)現allocator不存在size成員 */
    std::cout << len(x) <<std::endl; //error
}
  1. 我們可以通過(guò) SFINAE 原理在一些情形下忽略掉該模板。一種簡(jiǎn)單用法是 6.3 6.4 中的 std::enable_if;對于上例,則可以采用 auto + 尾后返回的方式,使得在替換時(shí)就知道 T 中必須含有 size 成員的限制。
/* decltype中采用逗號表達式,只有若T中不存在size成員,則替換失敗。
替換成功時(shí),才會(huì )將逗號表達式最后一句作為返回類(lèi)型 */
template<typename T>
auto len (T const& t) -> decltype((void)(t.size()) , T::size_type)
{
    return t.size();
}

8.5 編譯期 if

  1. 除了前面介紹的忽略模板的方法,c++17 還提供了編譯期的條件判斷語(yǔ)法 if constexpr(...)。

// 修改4.1中的print函數
template<typename T, typename… Types>
void print (T const& firstArg, Types const&… args)
{
    std::cout << firstArg << std::endl; // print只有一個(gè)參數時(shí),只會(huì )編譯這一部分
    if constexpr(sizeof…(args) > 0) {
        print(args…); // 只有args不為空的時(shí)候才會(huì )繼續遞歸實(shí)例化
        C++17)
    }
}

九、實(shí)踐中使用模板

9.1 包含模式

  1. 模板在編譯期會(huì )進(jìn)行實(shí)例化,實(shí)例化時(shí)需要提供模板的定義,所以對于模板相關(guān)代碼,正確用法是將聲明和定義均置于頭文件中。

9.2 模板和 inline

  1. 函數模板全特化后和普通函數相同,但函數模板一般定義在頭文件中,為了避免在多個(gè)模塊 include 時(shí)出現重復定義的錯誤,一般將全特化后的函數模板定義為 inline。
template<typename T>
void foo(const T& t){};

// 全特化函數
template<>
inline void foo<int>(const int& i){};

9.3 預編譯頭文件

  1. 預編譯頭文件不在 c++標準要求中,具體由編譯器實(shí)現。

  2. 預編譯頭文件:如果多個(gè)代碼文件的前 n 行均相同,編譯器就可以先對前 n 行進(jìn)行編譯,再依次對每個(gè)文件從 n+1 行進(jìn)行編譯。

  3. 為了充分利用預編譯頭文件功能,實(shí)踐中建議文件中#include 的順序盡量相同。

9.4 破譯大篇幅的錯誤信息

  1. 預編譯頭文件不在 c++標

十、模板基本術(shù)語(yǔ)

10.1 “類(lèi)模板”還是“模板類(lèi)”

10.2 替換,實(shí)例化和特例化

  1. 替換:在用模板實(shí)參去查找匹配的模板時(shí),會(huì )嘗試用實(shí)參去替換模板參數,見(jiàn) 8.4 節。

  2. 實(shí)例化:查找到最匹配的模板后,根據實(shí)參從模板創(chuàng )建出常規類(lèi)或函數的過(guò)程。

  3. 特例化:對模板中的部分或全部參數進(jìn)行特化,定義新模板的過(guò)程。

10.3 聲明和定義

  1. 聲明:將一個(gè)名稱(chēng)引入 c++作用域內,并不需要知道名稱(chēng)的相關(guān)細節。

  2. 定義:如果在聲明時(shí)提供了細節,聲明就變成了定義。

// 聲明
class A;
extern int v;
void f();

// 定義
class A{};
int v = 1;
void f(){};

10.4 唯一定義法則

  1. 唯一定義法制(ODR)

a.常規(比如非模板)非 inline 函數和成員函數,以及非 inline 的全局變量和靜態(tài)數據成員,在整個(gè)程序中只能被定義一次.

b. Class 類(lèi)型(包含 struct 和 union),模板(包含部分特例化,但不能是全特例化),以及 inline 函數和變量,在一個(gè)編譯單元中只能被定義一次,而且不同編譯單元間的定義 應該相同.

10.5 模板參數和模板實(shí)參

1.模板參數:模板定義中模板參數列表中的參數。

  1. 模板實(shí)參:實(shí)例化模板參數時(shí)傳入的參數。

十一、泛型庫

11.1 可調用對象

  1. c++可調用對象類(lèi)型

a.函數指針

b. 仿函數

c. 存在一個(gè)函數指針或者函數引用的轉換函數的 class 類(lèi)型

#include <iostream>
#include <vector>

template<typename Iter, typename Callable>
void foreach (Iter current, Iter end, Callable op)
{
    while (current != end) { //as long as not reached the end
        op(*current); // call passed operator for current element
        ++current; // and move iterator to next element
    }
}

// a function to call:
void func(int i)
{
    std::cout << 'func() called for: ' << i << std::endl;
}
// a function object type (for objects that can be used as functions):
class FuncObj {
    public:
    void operator() (int i) const { //Note: const member function
        std::cout << 'FuncObj::op() called for: ' << i << std::endl;
    }
};
int main()
{
    std::vector<int> primes = {2, 3, 5, 7, 11, 13, 17, 19};
    foreach (primes.begin(), primes.end(), func); // function as callable (decays to pointer)
    foreach(primes.begin(), primes.end(), &func); // function pointer as callable
    foreach(primes.begin(), primes.end(), FuncObj()); // function object as callable
    foreach(primes.begin(), primes.end(), [] (int i) { //lambda as callable
    std::cout << 'lambda called for: ' << i << std::endl;});
}

11.2 其它實(shí)現泛型庫的工具

  1. 標準庫中提供了豐富多樣的 type traits 工具,使用時(shí)一定要注意類(lèi)型萃取的精確定義。
#include <type_traits>
template<typename T>
class C
{
    // ensure that T is not void (ignoring const or volatile):
    static_assert(!std::is_same_v<std::remove_cv_t<T>,void>,
        'invalid instantiation of class C for void type');
public:
    template<typename V>
    void f(V&& v) {
        if constexpr(std::is_reference_v<T>) { … // special code if T is a reference type
        }
        if constexpr(std::is_convertible_v<std::decay_t<V>,T>) { … // special code if V is convertible to T
        }
        if constexpr(std::has_virtual_destructor_v<V>) { … // special code if V has virtual destructor
        }
    }
};
  1. std::addressof<>()會(huì )返回一個(gè)對象或者函數的準確地址,即使一個(gè)對象重載了取地址運算符&也是這樣。

  2. 函數模板 std::declval()可以被用作某一類(lèi)型的對象的引用的占位符。

// 避免在調用運算符?:的時(shí)候不得不去調用 T1 和 T2 的(默認)構造函數,這里使用了std::declval
#include <utility>
template<typename T1, typename T2,
typename RT = typename std::decay_t< decltype(true ? std::declval<T1>() : std::declval<T2>())> >
RT max (T1 a, T2 b)
{
    return b < a ? a : b;
}

11.3 完美轉發(fā)臨時(shí)變量

  1. 使用 auto &&可以創(chuàng )建一個(gè)可以被轉發(fā)的臨時(shí)變量。
template<typename T>
void foo(T x)
{
    auto&& val = get(x);
    // perfectly forward the return value of get() to set():
    set(std::forward<decltype(val)>(val));
}

11.4 其它實(shí)現泛型庫的工具

  1. 模板參數 T 的類(lèi)型可能被推斷為引用類(lèi)型,此時(shí)可能會(huì )引起意料之外的錯誤。
template<typename T, T Z = T{}>
class RefMem {
private:
    T zero;
public:
    RefMem() : zero{Z} {}
};

int null = 0;

int main()
{
    RefMem<int> rm1, rm2;
    rm1 = rm2; // OK
    RefMem<int&> rm3; // ERROR: 無(wú)法用int & 對Z進(jìn)行默認初始化
    RefMem<int&, 0> rm4; // ERROR: 不能用0 初始化int & 對象zero
    RefMem<int&,null> rm5, rm6;
    rm5 = rm6; // ERROR: 具有非static引用成員的類(lèi),默認賦值運算符會(huì )被刪掉
}

2.如果需要禁止引用類(lèi)型進(jìn)行實(shí)例化,可以使用 std::is_reference 進(jìn)行判斷。

11.5 推遲計算

  1. 可以通過(guò)模板來(lái)延遲表達式的計算,這樣模板可以用于不完整類(lèi)型。
template<typename T>
class Cont {
private:
    T* elems;
public:
};

struct Node
{
    std::string value;
    Cont<Node> next; // only possible if Cont accepts incomplete types
};

十二、深入模板基礎

12.1 參數化聲明

  1. C++ 目前支持四種基本類(lèi)型的模板:類(lèi)模板、函數模板、變量模板和別名模板。這些模板均可定義在全局作用域或者類(lèi)作用域中。
template<typename T> // a namespace scope class template
class Data {
public:
    static constexpr bool copyable = true;
};

template<typename T> // a namespace scope function template
void log (T x) {
}

template<typename T> // a namespace scope variable template (since C++14)
T zero = 0;

template<typename T> // a namespace scope variable template (since C++14)
bool dataCopyable = Data<T>::copyable;

template<typename T> // a namespace scope alias template
using DataList = Data<T*>;

class Collection {
public:
    template<typename T> // an in-class member class template definition
    class Node {
    };

    template<typename T> // an in-class (and therefore implicitly inline)
    T* alloc() { // member function template definition
    }

    template<typename T> // a member variable template (since C++14)
    static T zero = 0;

    template<typename T> // a member alias template
    using NodePtr = Node<T>*;
};
  1. 除了類(lèi)模板,也可以定義 union 模板
template<typename T>
union AllocChunk {
    T object;
    unsigned char bytes[sizeof(T)];
};

3.在類(lèi)模板內,也可以定義和模板參數無(wú)關(guān)的非模板成員。

4.類(lèi)內的模板函數不能是虛函數,普通函數可以是虛函數。

template<int I>
class CupBoard
{
public:
    class Shelf; // ordinary class in class template
    void open(); // ordinary function in class template
    enum Wood{}; // ordinary enumeration type in class template
    static double totalWeight; // ordinary static data member in class template
    virtual void fun(){}; // 普通函數可以是虛函數
    virtual ~CupBoard(){}; // 析構函數也可以是虛函數
    template <typename U>
    virtual void foo(const U &u){}; // error 模板函數不能是虛函數
};
  1. 每一個(gè)模板名稱(chēng)需要在所在作用域內獨一無(wú)二。

  2. 函數模板可以有 c++鏈接,但不能有 C 鏈接。

  3. 函數模板一般具有外部鏈接,除非是 static 或定義在未命名的命名空間中。

int X;

template <typename T>
class X; // error 模板名稱(chēng)必須獨一無(wú)二

extern 'C++' template<typename T>
void normal(); // ok
extern 'C' template <typename T>
void invalid(); // error 模板函數不能有C鏈接

template<typename T>
void external(); // 指向另一文件中定義的external函數模板

template<typename T>
static void internal(); // 并未指向另一文件中定義的internal函數模板,需要在本文件內提供定義
static void internal();
namespace {
    template<typename>
    void otherInternal(); // 并未指向另一文件中定義的otherInternal函數模板
}

namespace {
    template<typename>
    void otherInternal(); // 并未指向另一文件中定義的otherInternal函數模板
}

12.2 模板參數

  1. 模板參數分為三種:類(lèi)型模板參數,非類(lèi)型模板參數和模板模板參數。

2.非類(lèi)型模板參數可以是以下形式:

a.整型或枚舉類(lèi)型

b.指針類(lèi)型

c.指向類(lèi)成員的指針

d.左值引用

e.std::nullptr_t

f.含有 auto 或者 decltype(auto)的類(lèi)型(since c++17)

  1. 模版模板參數的關(guān)鍵字只能是 class 不能是 struct 或 union

4. c++11 開(kāi)始,可以通過(guò)...定義模板參數包,匹配任意類(lèi)型和數目的參數。

5. 也可以定義指定類(lèi)型的非模板參數包,匹配指定類(lèi)型任意數目的參數。

6. 模板可以提供模板參數的默認值,一旦為一個(gè)參數提供默認值,其后的參數都必須已經(jīng)定義默認值。

7. 若一個(gè)模板存在多處聲明或聲明和定義同時(shí)存在,那么可以在這些地方定義模板參數的默認值,不能為一個(gè)參數重復定義默認值。

template<template<typename X> class C> // ok
void f(C<int>* p);
template<template<typename X> struct C> // error
void f(C<int>* p);
template<template<typename X> union C> // error
void f(C<int>* p);
template<template<typename X> typename C> // ok since c++17
void f(C<int>* p);

template<template<typename T, T*> class Buf> // OK
class Lexer
{
static T* storage; // 模板模板參數的參數不能在聲明之外使用
};

template<typename ...Types> // 模板參數包可以匹配任意參數
class Tuple{};

Tuple<int> t1; // 匹配int
Tuple<int, char> t2; // 匹配int char

template<typename T, unsigned... Dimensions> // 非類(lèi)型模板參數包匹配任意數目的unsigned參數
class MultiArray {};
MultiArray<double, 3, 3> matrix; // 定義矩陣

template<typename T1 = int, typename T2> // error T2未定義默認值
class Test;

template<typename T1, typename T2 = double> // ok
class Quintuple;
template<typename T1 = int, typename T2> // ok T2默認值前面已經(jīng)定義
class Quintuple;
template<typename T1 = int> // error 不能重復定義默認值
class Quintuple;

12.3 模板實(shí)參

  1. 函數模板一般可以通過(guò)模板實(shí)參來(lái)推斷模板參數,但也存在無(wú)法推斷的情形。

  2. 模板模板參數的實(shí)參必須完全匹配模板模板參數。

#include <vector>
template<typename DstT, typename SrcT>
 DstT implicit_cast (SrcT const& x) // SrcT can be deduced, but DstT cannot
{
return x;
}

implicit_cast<double> (0); // 需要指定返回類(lèi)型,若DstT和SrcT順序調換,使用時(shí)需要指定兩個(gè)類(lèi)型


template<typename ... Ts, int N>
void f(double (&)[N+1], Ts ... ps); // useless declaration because N cannot be specified or deduced

template<typename T, template<typename U> class Cond>
class MyCond;

MyCond<int,std::vector> c; // error std::vector 為template<class _Tp, class _Alloc>,雖然_Alloc為默認參數,但仍然不匹配 

12.4 可變參數模板

12.5 友元

  1. 將類(lèi)模板作為友元時(shí),必須保證友元定義位置已經(jīng)知道類(lèi)模板的聲明。

  2. 也可以將類(lèi)型模板參數定義為友元 。

  3. 可以將函數模板定義為友元,此時(shí)若模板參數可推導,在友元聲明時(shí)可以省略。

template<typename T>
class Tree {
friend class Factory; // OK even if first declaration of Factory
friend class MyNode<T>; // error MyNode未找到聲明
};

template<typename T>
class Stack {
public:
    // assign stack of elements of type T2
    template<typename T2>
    Stack<T>& operator= (Stack<T2> const&);
    // to get access to private members of Stack<T2> for any type T2: template<typename> friend class Stack;
};
template<typename T>
class Test
{
    friend T; // 可以將類(lèi)型模板參數定義為友元
};

template<typename T1, typename T2>
   void combine(T1, T2);
class Mixer {
    friend void combine<>(int&, int&); //OK:T1 = int&,T2 = int&
    friend void combine<int, int>(int, int);//OK:T1 = int,T2 = int
    friend void combine<char>(char, int);//OK:T1 = charT2 = int
    friend void combine<char>(char&, int);// ERROR: doesn’t match combine() template
    friend void combine<>(long, long) {  } // ERROR: definition not allowed!
};

十三、模板中的名稱(chēng)

13.1 名稱(chēng)分類(lèi)

1.名稱(chēng)分為受限名稱(chēng)和非受限名稱(chēng),受限名稱(chēng)前面有顯式的出現 ->, ::, . 這三個(gè)限定符

13.2 名稱(chēng)查找

  1. c++名稱(chēng)的普通查找規則為從名稱(chēng)所在的 scope 從內向外依次查找。

  2. ADL( Argument-Dependent Lookup)查找為依賴(lài)于參數的查找,是用于函數調用表達式中查找非限定函數名稱(chēng)的規則。當在使用函數的上下文中找不到函數定義,我們可以在其參數的關(guān)聯(lián)類(lèi)和關(guān)聯(lián)名字空間中查找該函數的定義。

  3. ADL 生效條件:a.使用此規則的函數必須要有參數 b. 使用此規則的函數名稱(chēng)必須為非受限名稱(chēng)

4.ADL 查找范圍:

(1)對于基本類(lèi)型(int, char 等), 該集合為空集

(2)對于指針和數組類(lèi)型,該集合是所引用類(lèi)型的關(guān)聯(lián)類(lèi)和關(guān)聯(lián)名字空間

(3)對于枚舉類(lèi)型,名字空間是名字空間是枚舉聲明所在的名字空間,對于類(lèi)成員,關(guān)聯(lián)類(lèi)是枚舉所在的類(lèi)

(4)對于 class(包含聯(lián)合類(lèi)型),關(guān)聯(lián)類(lèi)包括該類(lèi)本身,他的外圍類(lèi),直接基類(lèi),間接基類(lèi)。關(guān)聯(lián)名字空間包括每個(gè)關(guān)聯(lián)類(lèi)所在的名字空間。

(5)對于函數類(lèi)型, 該集合包含所有參數類(lèi)型和返回類(lèi)型的關(guān)聯(lián)類(lèi)和關(guān)聯(lián)名字空間

(6)對于類(lèi) X 的成員指針類(lèi)型,除了包括成員相關(guān)的關(guān)聯(lián)名字空間,關(guān)聯(lián)類(lèi),該集合還包括與 X 相關(guān)的關(guān)聯(lián)名字空間和關(guān)聯(lián)類(lèi)

#include <iostream>
namespace X {
    template<typename T> void f(T);
}
namespace N {
    using namespace X;
    enum E { e1 };
    void f(E) {
        std::cout << 'N::f(N::E) called\n';
    }
}
void f(int)
{
    std::cout << '::f(int) called\n';
}
int main()
{
    ::f(N::e1); // qualified function name: no ADL
    f(N::e1); // ordinary lookup finds ::f() and ADL finds N::f(),the latter is preferred
}
  1. c++ 類(lèi)中聲明的友元函數在類(lèi)外是不可見(jiàn)的,若未在類(lèi)外提供定義,要想查找到該函數,只能通過(guò) ADL.
template<typename T>
class C {
    friend void f();
    friend void f(C<T> const&);
};
void g (C<int>* p)
{
    f(); // error 類(lèi)內聲明的友元類(lèi)外不可見(jiàn),普通查找無(wú)法找到
    f(*p); // ok 雖然不可見(jiàn),但是可以通過(guò)ADL找到
}
  1. 類(lèi)名注入:在類(lèi)作用域中,當前類(lèi)的名字被當做它如同是一個(gè)公開(kāi)成員名一樣;這被稱(chēng)為注入類(lèi)名(injected-class-name)。
int X;
struct X {
    void f() {
        X* p; // OK:X 指代注入類(lèi)名
        ::X* q; // 錯誤:名稱(chēng)查找找到變量名,它隱藏 struct 名
    }
};
  1. 對于類(lèi)模板,除了注入類(lèi)名,還可以注入實(shí)例化的類(lèi)名。
template<typename T> class C {
    using Type = T;
    struct J {
        C* c; // C refers to a current instantiation
        C<Type>* c2; // C<Type> refers to a current instantiation
        I* i; // I refers to an unknown specialization,
        // because I does not enclose J
        J* j; // J refers to a current instantiation
    };
    struct I {
        C* c; // C refers to a current instantiation
        C<Type>* c2; // C<Type> refers to a current instantiation
        I* i; // I refers to a current instantiation
    };
};

13.3 模板解析

  1. 大多數編程語(yǔ)言的編譯器的兩個(gè)基本活動(dòng)是標記化和解析。

  2. X<1>(0) ;被解析時(shí),若 X 是一個(gè)類(lèi)型,<1>則被認為是模板實(shí)參;若 X 不是類(lèi)型,則被認為是 x<1 比較后的結果再和>0 比較。在處理模板時(shí),一定要避免大于符號>被當做模板參數終止符。

template<bool B>
class Invert {
    public:
    static bool const result = !B;
};
void g()
{
    bool test = Invert<1>0>::result; // error 模板參數被提前結束
    bool test = Invert<(1>0)>::result; // ok
}
  1. 類(lèi)型名稱(chēng)具有以下性質(zhì)時(shí),就需要在其前面添加 typename

a. 名稱(chēng)出現在一個(gè)模板中

b. 名稱(chēng)是受限的

c. 名稱(chēng)不是用于基類(lèi)的派生列表或構造函數的初始化列表中

d. 名稱(chēng)依賴(lài)于模板參數

  1. ADL 用于模板函數時(shí),可能會(huì )產(chǎn)生錯誤。
namespace N {
    class X {

    };
    template<int I> void select(X*);
}
void g (N::X* xp)
{
    select<3>(xp); // ERROR 編譯無(wú)法知道<3>是模板參數,進(jìn)而無(wú)法判斷select是函數調用
}

13.4 派生和類(lèi)模板

  1. 大多數情況中類(lèi)模板派生和普通類(lèi)派生無(wú)太大區別。

2.非依賴(lài)型基類(lèi):無(wú)需知道模板名稱(chēng)就可以完全確定類(lèi)型的基類(lèi)。

3.非依賴(lài)型基類(lèi)的派生類(lèi)中查找一個(gè)非受限名稱(chēng)時(shí),會(huì )先從非依賴(lài)型基類(lèi)中查找,然后才是模板參數列表。

template<typename X>
class Base {
public:
    int basefield;
    using T = int;
};

class D1: public Base<Base<void>> { // 實(shí)際上不是模板
public:
    void f() { basefield = 3; } // 繼承成員普通訪(fǎng)問(wèn)
};
template<typename T>
class D2 : public Base<double> { // 繼承自非依賴(lài)型基類(lèi)
public:
    void f() { basefield = 7; } // 繼承成員普通訪(fǎng)問(wèn)
    T strange; // T 是 Base<double>::T 類(lèi)型,也就是int, 而不是模板參數T!
};

十四、實(shí)例化

14.1 On-Demand 實(shí)例化

1.模板被實(shí)例化時(shí),編譯器需要知道實(shí)例化部分的完整定義。

14.2 延遲實(shí)例化

1.模板實(shí)例化存在延遲現象,編譯器只會(huì )實(shí)例化需要的部分。如類(lèi)模板會(huì )只實(shí)例化用到的部分成員函數,函數模板如果提供了默認參數,也只會(huì )在這個(gè)參數會(huì )用到的時(shí)候實(shí)例化它。

14.3 c++實(shí)例化模型

1.兩階段查找:編譯器在模板解析階段會(huì )檢測不依賴(lài)于模板參數的非依懶型名稱(chēng),在模板實(shí)例化階段再檢查依懶型名稱(chēng)。

2.Points of Instantiation: 編譯器會(huì )在需要實(shí)例化模板的地方插入實(shí)例化點(diǎn)(POI)

14.4 幾種實(shí)現方案

14.5 顯式實(shí)例化

十五、模板實(shí)參推導

15.1 推導的過(guò)程

1.函數模板實(shí)例化過(guò)程中,編譯器會(huì )根據實(shí)參的類(lèi)型和模板參數 T 定義的形式,推導出函數的各個(gè)參數的類(lèi)型,如果最后推導的結論矛盾,則推導失敗。

template<typename T>
T max (T a, T b)
{
    return b < a ? a : b;
}
auto g = max(1, 1.0); // error 根據1推導T為int 根據1.0推導T為double

template<typename T> void f(T);
template<typename T> void g(T&);
double arr[20];
int const seven = 7;
f(arr); // T 被decay為double*
g(arr); //T 推斷為 double[20]
f(seven); // T 被decay為int
g(seven); // T 推斷為 int const

15.2 推導的上下文

15.3 特殊的推導情況

1.函數模板被取地址賦予函數指針時(shí),也會(huì )產(chǎn)生實(shí)參推導。

2.類(lèi)中定義了類(lèi)型轉換的模板函數時(shí),在類(lèi)型轉換時(shí)可以產(chǎn)生實(shí)參推導。

template<typename T>
void f(T, T);
void (*pf)(char, char) = &f;  // T 被推斷為char

class S {
public:
    template<typename T> operator T&();
};

void f(int (&)[20]);
void g(S s)
{
    f(s); // 類(lèi)型轉換時(shí)T被推導為int[20]
}

15.4 初始化列表

1.模板實(shí)參如果是初始化列表時(shí),無(wú)法直接完成模板參數類(lèi)型 T 的推導。若函數參數通過(guò) std::initializer_list 定義,則實(shí)參類(lèi)型需要一致。

template<typename T> void g(T p);
template<typename T> void f(std::initializer_list<T>);

int main() {
    g({’a’, ’e’, ’i’, ’o’, ’u’, 42}); // ERROR: T deduced to both char and int
    f({1, 2, 3}); // ERROR: cannot deduce T from a braced list
    f({2, 3, 5, 7, 9}); // OK: T is deduced to int
}

15.5 參數包

15.6 右值引用

1.引用折疊:只有兩個(gè)右值引用會(huì )被折疊為右值引用,其它情形都是左值引用

undefined
using RCI = int const&;
RCI volatile&& r = 42; // OK: r has type int const&
using RRI = int&&;
RRI const&& rr = 42; // OK: rr has type int&&

15.7 SFINAE

1.根據 SFINAE 原理,編譯器在用實(shí)參推導模板參數失敗時(shí),會(huì )將該模板忽略。

template<typename T, unsigned N>
T* begin(T (&array)[N])
{
    return array;
}
template<typename Container>
typename Container::iterator begin(Container& c)
{
    return c.begin();
}
int main()
{
    std::vector<int> v;
    int a[10];
    ::begin(v); // OK: only container begin() matches, because the first deduction fails
    ::begin(a); // OK: only array begin() matches, because the second substitution fails
}

十六、特化和重載

16.1 當泛型代碼不再適用的時(shí)候

16.2 重載函數模板

1.函數模板和普通函數一樣,是可以被重載的。

#include<iostream>

template<typename T>
int f(T)
{
    return 1;
}
template<typename T>
int f(T*)
{
    return 2;
}

int main()
{
    std::cout << f<int*>((int*)0) << std::endl; // 更匹配第一個(gè)函數calls f<T>(T)
    std::cout << f<int>((int*)0) << std::endl; // 只匹配第二個(gè)函數 calls f<T>(T*)
    std::cout << f((int*)0) << std::endl; // 都匹配,但第二個(gè)更特殊,優(yōu)先選用 calls f<T>(T*)
}
  1. 如上所示,main 中實(shí)例化后的前兩個(gè)函數完全相同,但是可以同時(shí)存在,原因是它們具有不同的簽名。

3.函數簽名由以下部分構成:

a. 非受限函數名稱(chēng)

b. 名稱(chēng)所屬的類(lèi)作用域

c. 函數的 const volatile 限定符

d. 函數參數的類(lèi)型

e. 如果是函數模板,還包括返回類(lèi)型、模板參數和模板實(shí)參。

// 以下模板的實(shí)例化函數可以同時(shí)存在
template<typename T1, typename T2>
void f1(T1, T2);
template<typename T1, typename T2>
void f1(T2, T1);
template<typename T>
long f2(T);
template<typename T>
char f2(T);
  1. c++最開(kāi)始例子中最后一個(gè)函數實(shí)例化時(shí)和兩個(gè)模板均匹配,此時(shí)將選用更特殊的函數模板。

  2. 普通函數和模板函數也可以同時(shí)重載,此時(shí)在匹配程度相同時(shí),優(yōu)先調用普通函數。

template<typename T>
std::string f(T)
{
    return 'Template';
}
std::string f(int&)
{
    return 'Nontemplate';
}
int main()
{
int x = 7;
std::cout << f(x) << std::endl; // 調用非模板函數
}

16.3 顯式特化

  1. 重載只適用于函數模板,對于類(lèi)模板,可以使用特化的方式使得編譯器進(jìn)行更優(yōu)的選擇。

  2. 類(lèi)模板若提供了默認參數,特化后的模板可以不加。

template<typename T>
class Types {
public:
    using I = int;
};
template<typename T, typename U = typename Types<T>::I>
class S; // 模板1聲明
template<>
class S<void> { // 特化后的模板2定義,可以不用默認參數
public:
    void f();
};
template<> class S<char, char>; // 特化后的模板3聲明
template<> class S<char, 0>; // error 特化失敗,0不能當類(lèi)型
int main()
{
    S<int>* pi; // OK: 使用模板1且不需定義
    S<int> e1; // ERROR: 使用模板1且需要定義
    S<void>* pv; // OK:  使用模板2且不需定義
    S<void,int> sv; // OK: 使用模板2且不需定義
    S<void,char> e2; // ERROR: 使用模板1且需要定義
    S<char,char> e3; // ERROR: 使用模板3且需要定義
}

template<>
class S<char, char> { // 特化后的模板3定義,此處定義對main中的實(shí)例化調用是不可見(jiàn)的
};
  1. 模板全特化之后的類(lèi)和由相同的特化參數實(shí)例化后的類(lèi)是相同的,不能同時(shí)存在。
template<typename T,typename U>
class Invalid {
};
Invalid<double, int> x1; // 實(shí)例化一個(gè)類(lèi)模板
template<typename T>
class Invalid<T,double>; // ok, 部分偏特化可以和實(shí)體化實(shí)體同時(shí)存在

template<>
class Invalid<double,int>; // ERROR: 全特化后的模板不能和實(shí)例化實(shí)體同時(shí)存在
  1. 類(lèi)模板全特化后,若在類(lèi)外定義函數,則不能在前面加 template<>
template<typename T>
class S;
template<> class S<char**> {
public:
    void print() const;
};

// 不能出現template<>
void S<char**>::print() const
{
    std::cout << 'pointer to pointer to char\n';
}
  1. 函數模板全特化時(shí),不能含有默認參數值,但可以直接使用。
template<typename T>
int f(T) // #1
{
    return 1;
}
template<typename T>
int f(T*) // #2
{
    return 2;
}
template<> int f(int) // OK: specialization of #1
{
    return 3;
}
template<> int f(int*) // OK: specialization of #2
{
    return 4;
}

template<typename T>
int f(T, T x = 42)
{
    return x;
}
template<> int f(int, int = 35) // 特化時(shí)不能指定默認參數
{
    return 0;
}

template<typename T>
int g(T, T x = 42)
{
    return x;
}
template<> int g(int, int y)
{
    return y/2; // 返回21
}
  1. 變量模板也可以進(jìn)行全特化。
template<typename T> constexpr std::size_t SZ = sizeof(T);
template<> constexpr std::size_t SZ<void> = 0;
  1. 除了對類(lèi)模板進(jìn)行全特化以外,也可以單獨為類(lèi)模板中的某個(gè)成員進(jìn)行全特化。
template<typename T>
class Outer {
public:
    static int code;
    void print() const {
        std::cout << 'generic';
    }

    void test(){};
};

template<typename T>
int Outer<T>::code = 6; // 類(lèi)外定義

// 對于Outer<void>類(lèi)型,code和print將采用特化版本,其余采用普通版本
template<>
int Outer<void>::code = 12; // 特化靜態(tài)成員
template<>
void Outer<void>::print() const // 特化類(lèi)模板
{
    std::cout << 'Outer<void>';
}

16.4 類(lèi)模板偏特化

  1. 對于類(lèi)模板,可以進(jìn)行偏特化,偏特化也不能提供默認參數,但可以直接使用。
template<typename T, int I = 3>
class S; // primary template
template<typename T>
class S<int, T>; // ERROR: 參數類(lèi)型不匹配
template<typename T = int>
class S<T, 10>; // ERROR: 不能有默認參數
template<int I>
class S<int, I*2>; // ERROR:不能有非類(lèi)型表達式
template<typename U, int K>
class S<U, K>; // ERROR:特化模板和基本模板無(wú)區別

16.5 變量模板偏特化

  1. 對于變量模板,也可以偏特化。
template<typename T> constexpr std::size_t SZ = sizeof(T);
template<typename T> constexpr std::size_t SZ<T&> = sizeof(void*);

十七、未來(lái)的方向

十八、模板的多態(tài)性

18.1 動(dòng)態(tài)多態(tài)

  1. 動(dòng)態(tài)多態(tài):通過(guò)繼承和虛函數實(shí)現,在運行期根據指針或引用的具體類(lèi)型決定具體調用那一個(gè)虛函數。

18.2 靜態(tài)多態(tài)

  1. 靜態(tài)多態(tài):通過(guò)模板實(shí)現,在編譯期基于類(lèi)型調用不同模板。

18.3 動(dòng)態(tài)多態(tài) vs 靜態(tài)多態(tài)

18.4 使用 concepts

  1. 使用靜態(tài)多態(tài)時(shí)可以采用 6.5 中介紹的 concept 對可傳入模板的類(lèi)型做以限制。

18.5 新形式的設計模式

  1. 對于橋接模式,若具體實(shí)現的類(lèi)型在編譯期間可以確定,則可以使用模板代替傳統的橋接模式實(shí)現。
undefined
undefined

18.6 泛型編程

十九、萃取實(shí)現

19.1 序列求和示例

  1. 簡(jiǎn)單的求和模板函數的定義及使用如下:
template<typename T>
T accum (T const* beg, T const* end)
{
    T total{}; // 初始化為零值
    while (beg != end) {
        total += *beg;
        ++beg;
    }
    return total;
}

int main() {
    int num[] = { 1, 2, 3, 4, 5 };
    std::cout << 'the average value of the integer values is '
            << accum(num, num+5) / 5;  // 輸出3
    char name[] = 'templates';
    int length = sizeof(name)-1;
    std::cout << 'the average value of the characters in \''
                << name << '\' is '
                << accum(name, name+length) / length;
                // 輸出-5 因為字符轉為對應的八位ASCII編碼數字進(jìn)行累加,累計超過(guò)了char表示的最大數字
}
  1. 為了避免求和溢出,可以通過(guò)下面簡(jiǎn)單萃取的方式重新定義求和函數。
template<typename T>
struct AccumulationTraits;
template<>
struct AccumulationTraits<char> {
    using AccT = int;
};
template<>
struct AccumulationTraits<short> {
    using AccT = int;
};
template<>
struct AccumulationTraits<int> {
    using AccT = long;
};
template<>
struct AccumulationTraits<unsigned int> {
    using AccT = unsigned long;
};
template<>
struct AccumulationTraits<float> {
    using AccT = double;
};

template<typename T>
auto accum (T const* beg, T const* end)
{
using AccT = typename AccumulationTraits<T>::AccT;
AccT total{}; // assume this actually creates a zero value
while (beg != end) {
    total += *beg;
    ++beg;
    }
return total;
}
  1. 上面例子還可能出現問(wèn)題,就是初始化 total 時(shí),有些類(lèi)型可能會(huì )并不支持默認初始化。為了兼容這種情形,可采用值萃取的形式提供默認初始值。
template<>
struct AccumulationTraits<char> {
    using AccT = int;
    static AccT const zero = 0;
};
template<>
struct AccumulationTraits<short> {
    using AccT = int;
    static AccT const zero = 0;
};
template<>
struct AccumulationTraits<int> {
    using AccT = long;
    static AccT const zero = 0;
};
template<typename T>
auto accum (T const* beg, T const* end)
{
using AccT = typename AccumulationTraits<T>::AccT;
AccT total = AccumulationTraits<T>::zero;
while (beg != end) {
    total += *beg;
    ++beg;
    }
    return total;
}
  1. 上面類(lèi)內初始化靜態(tài)數據成員的方式只對整型有效,對于 float 和字面值常量,可以通過(guò) constexpr 定義進(jìn)行類(lèi)類(lèi)初始化,對于非字面值的類(lèi)型,則可以通過(guò) inline 成員函數提供類(lèi)內定義。
template<>
struct AccumulationTraits<char> {
    using AccT = int;
    static constexpr AccT zero() { // 通過(guò)內聯(lián)函數定義初始值
    return 0;
    }
};
  1. 也可以將上述的萃取形式參數化,便于特殊情形下指定不同的萃取形式。
template<typename T, typename AT = AccumulationTraits<T>>
  auto accum (T const* beg, T const* end)
  {
      typename AT::AccT total = AT::zero();
      while (beg != end) {
          total += *beg;
          ++beg; }
      return total;
  }

19.2 萃取 vs 策略或策略類(lèi)

  1. 對于 19.1 中的 accum 函數,可以指定不同策略,不止用來(lái)求和,可以求乘。
// 求和策略
class SumPolicy {
    public:
      template<typename T1, typename T2>
      static void accumulate (T1& total, T2 const& value) {
          total += value;
      }
};

// 求乘積策略
class MultPolicy {
    public:
      template<typename T1, typename T2>
      static void accumulate (T1& total, T2 const& value) {
          total *= value;
      }
};

template<typename T,
        typename Policy = SumPolicy,
        typename Traits = AccumulationTraits<T>>
auto accum (T const* beg, T const* end)
{
    using AccT = typename Traits::AccT;
    AccT total = Traits::zero();
    while (beg != end) {
        Policy::accumulate(total, *beg);
    ++beg; }
    return total;
}
  1. 萃取偏向于模板參數本質(zhì)的特性,策略偏向于泛型可配置的行為。

19.3 類(lèi)型函數

  1. 類(lèi)型函數:接收一些類(lèi)型作為參數,返回類(lèi)型或常量值為結果。
#include<vector>
#include<list>
#include<iostream>
#include<typeinfo>
template<typename T>
struct ElementT; // primary template
template<typename T>
struct ElementT<std::vector<T>> { // partial specialization for std::vector
    using Type = T;
};
template<typename T>
struct ElementT<std::list<T>> {
    using Type = T;
};

// partial specialization for std::list
template<typename T, std::size_t N>
struct ElementT<T[N]> {
    using Type = T;
};
template<typename T>
struct ElementT<T[]> {
    using Type = T;
};

// 類(lèi)型函數,打印容器類(lèi)型T中元素的類(lèi)型
template<typename T>
void printElementType (T const& c)
{
    std::cout << 'Container of '
            << typeid(typename ElementT<T>::Type).name()
            << ' elements.\n';
}

int main() {
      std::vector<bool> s;
      printElementType(s); // print b
      int arr[42];
      printElementType(arr); // print i
}
  1. 萃取還可以用來(lái)為類(lèi)型添加或移除引用,const,volatile 等限定符。
// 移除引用
template<typename T>
struct RemoveReferenceT {
    using Type = T;
};
template<typename T>
struct RemoveReferenceT<T&> {
    using Type = T;
};
template<typename T>
struct RemoveReferenceT<T&&> {
    using Type = T;
};

// 添加引用
template<typename T>
struct AddLValueReferenceT {
    using Type = T&;
};

template<typename T>
struct AddRValueReferenceT {
    using Type = T&&;
};
  1. 除了對單個(gè)類(lèi)型進(jìn)行萃取,也可以通過(guò)萃取對多個(gè)類(lèi)型進(jìn)行預測。
// 判斷兩個(gè)類(lèi)型是否相同
template<typename T1, typename T2>
struct IsSameT {
    static constexpr bool value = false;
};
template<typename T>
struct IsSameT<T, T> {
    static constexpr bool value = true;
};
  1. 對 3 中的 same 判斷可以進(jìn)一步優(yōu)化,以實(shí)現編譯期多態(tài)。
#include <iostream>

// 定義bool模板類(lèi)型
template<bool val>
struct BoolConstant {
    using Type = BoolConstant<val>;
    static constexpr bool value = val;
};

// 定義兩個(gè)實(shí)例化類(lèi)型表示true 和 false
using TrueType  = BoolConstant<true>;
using FalseType = BoolConstant<false>;

// 普通IsSameT模板繼承FalseType
template<typename T1, typename T2>
struct IsSameT : FalseType
{
};

// 特化的相同類(lèi)型IsSameT模板繼承TrueType
template<typename T>
struct IsSameT<T, T> : TrueType
{
};

// 根據fool的結果調用不同的foolImpl
template<typename T>
void foolImpl(T, TrueType)
{
    std::cout<<'true'<<std::endl;
}

template<typename T>
void foolImpl(T, FalseType)
{
    std::cout<<'false'<<std::endl;
}

template<typename T>
void fool(T t)
{
    foolImpl(t, IsSameT<T,int>{}); // IsSameT<T,int>{} 類(lèi)型可能為T(mén)rueType或FalseType
}

int main()
{
    fool(5); // print true
    fool(5.0); // print false
}

19.4 基于 SFINAE 的萃取

  1. 可以基于 SFINAE 原理排除某些重載的函數模板。
// 實(shí)現一個(gè)能判斷T是否具有默認構造函數的模板
template<typename T>
struct IsDefaultConstructibleT {
private:
    // 定義兩個(gè)重載的函數模板,返回類(lèi)型不同,用于傳入IsSameT進(jìn)行判斷
    template<typename U, typename = decltype(U())> // decltype(U()) 中U若不可以默認初始化,則替換失敗,這個(gè)函數模板被淘汰,注意此處不能為T(mén),否則會(huì )編譯失敗
    static char test(void*);
    template<typename>
    static long test(...); // ...參數可以匹配任意實(shí)參
public:
    // 將T傳入test,如可以默認構造,則匹配第一個(gè)test,test返回類(lèi)型為char,IsSameT 結果為true,否則為false
    static constexpr bool value = IsSameT<decltype(test<T>(nullptr)), char>::value;
};

IsDefaultConstructibleT<int>::value //結果為 true

 struct S {
   S() = delete;
 };
 IsDefaultConstructibleT<S>::value // 結果為false
  1. 可以基于 SFINAE 原理排除某些重載的類(lèi)模板。
 // helper to ignore any number of template parameters:
  template<typename...> using VoidT = void;
// primary template:
  template<typename, typename = VoidT<>>
  struct IsDefaultConstructibleT : std::false_type
  {
  };
// partial specialization (may be SFINAE’d away):
  template<typename T>
  struct IsDefaultConstructibleT<T, VoidT<decltype(T())>> : std::true_type
{
};

19.5 IsConvertibleT

19.6 探測成員

1.可以基于 SFINAE 原理探測類(lèi)型 T 中是否含有名為 X 的成員。

// helper to ignore any number of template parameters:
  template<typename...> using VoidT = void;
// primary template:
  template<typename, typename = VoidT<>>
  struct HasSizeTypeT : std::false_type
  {
  };
// 只有含有size_type時(shí)才會(huì )替換成功,其余情況偏特化的模板被忽略
  template<typename T>
  struct HasSizeTypeT<T, VoidT<typename T::size_type>> : std::true_type
{
};

std::cout << HasSizeTypeT<int>::value; // false
struct CX {
    using size_type = std::size_t;
};
std::cout << HasSizeType<CX>::value; // true

2.對于 13.2 中的介紹的注入的類(lèi)名詞,上述檢查結果為 true。

struct size_type { };
struct Sizeable : size_type { };
std::cout << HasSizeTypeT<Sizeable>::value; //ture
  1. 可以將萃取的類(lèi)型名稱(chēng)參數化,通過(guò)宏定義適用于任何類(lèi)型參數的萃取
#define DEFINE_HAS_TYPE(MemType) \
template<typename, typename = std::void_t<>> \
struct HasTypeT_##MemType \
: std::false_type { \
}; \
template<typename T> \
struct HasTypeT_##MemType<T, std::void_t<typename T::MemType>> \ : std::true_type { }

// 定義兩種類(lèi)型萃取
DEFINE_HAS_TYPE(value_type);
DEFINE_HAS_TYPE(size_type);
  1. 除了探測類(lèi)型成員,也可以探測非類(lèi)型成員
#define DEFINE_HAS_MEMBER(Member) \
template<typename, typename = std::void_t<>> \
struct HasMemberT_##Member \ : std::false_type { }; \
template<typename T> \
struct HasMemberT_##Member<T, std::void_t<decltype(&T::Member)>> \
: std::true_type { }

19.7 其他的萃取技術(shù)

1.if-then-else:接收一個(gè)條件參數,根據條件從兩個(gè)類(lèi)型參數中做選擇。

template<bool COND, typename TrueType, typename FalseType>
struct IfThenElseT {
    using Type = TrueType;
};
// 模板偏特化
template<typename TrueType, typename FalseType> struct IfThenElseT<false, TrueType, FalseType> {
    using Type = FalseType;
};
template<bool COND, typename TrueType, typename FalseType>
using IfThenElse = typename IfThenElseT<COND, TrueType, FalseType>::Type;

19.8 類(lèi)型分類(lèi)

19.9 策略萃取

19.10 在標準庫中的情形

二十、基于類(lèi)型屬性的重載

20.1 算法特化

  1. 可以通過(guò)給函數模版提供不同形式的參數實(shí)現算法特化。
template<typename T> void swap(T& x, T& y)
{
    T tmp(x);
    x = y;
    y = tmp;
}

// Array<T> 參數將優(yōu)先匹配下面的版本
template<typename T>
void swap(Array<T>& x, Array<T>& y) {
    swap(x.ptr, y.ptr);
    swap(x.len, y.len);
}

20.2 標記派發(fā)

  1. stl 中根據迭代器類(lèi)型調用不同接口實(shí)現 itr+n

20.3 enable\disable 函數模板

二十一、模板和繼承

21.1 空基類(lèi)優(yōu)化

  1. c++中對于空類(lèi),為保證類(lèi)對象有唯一地址,會(huì )為其插入一子節的內存。

  2. 空基類(lèi)優(yōu)化:在空類(lèi)作為基類(lèi)時(shí),如果為它不分配內存不會(huì )導致它存儲到其他同類(lèi)型對象或者子類(lèi)型對象的相同地址上,則可以不分配。

class Empty {
    using Int = int;// type alias members don’t make a class nonempty
};
class EmptyToo : public Empty { };
class EmptyThree : public EmptyToo { };
int main()
{
    // 輸出的三個(gè)類(lèi)大小相同
    std::cout << 'sizeof(Empty): ' << sizeof(Empty) << ’\n’;
    std::cout << 'sizeof(EmptyToo): ' << sizeof(EmptyToo) << ’\n’;
    std::cout << 'sizeof(EmptyThree): ' << sizeof(EmptyThree) << ’\n’;
}


21.2 CRTP

  1. crtp:將派生類(lèi)類(lèi)型作為模板參數傳遞給基類(lèi)。
// 兩種crtp形式
template<typename Derived> class CuriousBase {
};
class Curious : public CuriousBase<Curious> {
};

template<typename Derived> class CuriousBase {
};
template<typename T>
class CuriousTemplate : public CuriousBase<CuriousTemplate<T>> {
};

21.3 混入

21.4 命名的模板參數

二十二、橋接靜態(tài)多條和動(dòng)態(tài)多態(tài)

22.1 函數對象,指針及 std::function<>

  1. 與 C++函數指針相比,std::functional<>還可以被用來(lái)存儲 lambda,以及其它任意 實(shí)現了合適的 operator()的函數對象。

二十三、模板元編程

22.1 c++元編程現狀

1.值元編程:在編譯期通過(guò)模板基于輸入參數計算得到結果值。

// 計算平方根的模板
template<typename T>
constexpr T sqrt(T x)
{
    if (x <= 1) {
        return x;
    }
    T lo = 0, hi = x;
    for (;;)
    {
        auto mid = (hi+lo)/2, midSquared = mid*mid;
        if (lo+1 >= hi || midSquared == x) {
            return mid;
        }
        if (midSquared < x) { lo = mid;
        } else {
            hi = mid;
        }
    }
}

sqrt(9); // 編譯期計算結果

2.類(lèi)型元編程:在編譯期通過(guò)模板基于輸入類(lèi)型得到輸出類(lèi)型。

// primary template: in general we yield the given type:
template<typename T> struct RemoveAllExtentsT {
    using Type = T;
};
// partial specializations for array types (with and without bounds):
template<typename T, std::size_t SZ>
struct RemoveAllExtentsT<T[SZ]> {
    using Type = typename RemoveAllExtentsT<T>::Type;
};
template<typename T>
struct RemoveAllExtentsT<T[]> {
    using Type = typename RemoveAllExtentsT<T>::Type;
};
template<typename T>
using RemoveAllExtents = typename RemoveAllExtentsT<T>::Type;
  1. 混合元編程:混合值和類(lèi)型元編程。
// 計算向量的點(diǎn)乘
template<typename T, std::size_t N>
struct DotProductT {
    static inline T result(T* a, T* b)
    {
        return *a * *b + DotProduct<T, N-1>::result(a+1,b+1);
    }
};

template<typename T>
struct DotProductT<T, 0> {
    static inline T result(T*, T*) {
        return T{};
    }
};

template<typename T, std::size_t N>
auto dotProduct(std::array<T, N> const& x, std::array<T, N> const& y)
{
    return DotProductT<T, N>::result(x.begin(), y.begin());
}

參考資料:

1.c++ templates 第二版

2.c++ templates 第二版翻譯.知乎

<END>
本站僅提供存儲服務(wù),所有內容均由用戶(hù)發(fā)布,如發(fā)現有害或侵權內容,請點(diǎn)擊舉報。
打開(kāi)APP,閱讀全文并永久保存 查看更多類(lèi)似文章
猜你喜歡
類(lèi)似文章
C++中的類(lèi)模板詳細講述
C++ 模板基礎談 - C/C++ / C++ 語(yǔ)言
類(lèi)模板(原創(chuàng )) - c++探索 - C++博客
<轉載>獨一無(wú)二的C++模板
C 模版的本質(zhì)
C 模板
更多類(lèi)似文章 >>
生活服務(wù)
分享 收藏 導長(cháng)圖 關(guān)注 下載文章
綁定賬號成功
后續可登錄賬號暢享VIP特權!
如果VIP功能使用有故障,
可點(diǎn)擊這里聯(lián)系客服!

聯(lián)系客服

欧美性猛交XXXX免费看蜜桃,成人网18免费韩国,亚洲国产成人精品区综合,欧美日韩一区二区三区高清不卡,亚洲综合一区二区精品久久