C++模板學習筆記


一個有趣的東西:實現一個函數print, 輸入一個數組, 輸出數組的各個維度長度。

eg.
int a[2], b[3][4], c[5][6][7];
print(a);   //(2, 4)
print(b);   //(3, 16) (4, 4)
print(c);   //(5, 168) (6, 28) (7, 4)
template<typename T>
void print(const T &A) {
    printf("\n");
}
template<typename T, int N>
void print(const T (&A)[N]) {
    printf("(%d, %d) ", N, sizeof(A[0]));
    print(A[0]);
}
View Code

 

學習版塊

https://github.com/wuye9036/CppTemplateTutorial  空明流轉

 

typename與class

在 C++ Template 中很多地方都用到了 typename 與 class 這兩個關鍵字,而且好像可以替換,是不是這兩個關鍵字完全一樣呢?
相信學習 C++ 的人對 class 這個關鍵字都非常明白,class 用於定義類,在模板引入 c++ 后,最初定義模板的方法為:
template<class T>......
這里 class 關鍵字表明T是一個類型,后來為了避免 class 在這兩個地方的使用可能給人帶來混淆,所以引入了 typename 這個關鍵字,它的作用同
class 一樣表明后面的符號為一個類型,這樣在定義模板的時候就可以使用下面的方式了:
template<typename
T>......
在模板定義語法中關鍵字 class 與 typename 的作用完全一樣。
typename 難道僅僅在模板定義中起作用嗎?其實不是這樣,typename 另外一個作用為:使用嵌套依賴類型(nested depended name),如下所示:
class MyArray 
{ 
    public:
    typedef int LengthType;
.....
}

template<class T>
void MyMethod( T myarr ) 
{ 
    typedef typename T::LengthType LengthType; 
    LengthType length = myarr.GetLength; 
}
這個時候 typename 的作用就是告訴 c++ 編譯器,typename 后面的字符串為一個類型名稱,而不是成員函數或者成員變量,這個時候如果前面沒有
typename,編譯器沒有任何辦法知道 T::LengthType 是一個類型還是一個成員名稱(靜態數據成員或者靜態函數),所以編譯不能夠通過。
View Code

eg1.

 1 template <typename T> struct X {};
 2 template <typename T> struct Y
 3 {
 4     // X可以查找到原型;
 5     // X<T>是一個依賴性名稱,模板定義階段並不管X<T>是不是正確的。
 6     typedef X<T> ReboundType;
 7     
 8     // X可以查找到原型;
 9     // X<T>是一個依賴性名稱,X<T>::MemberType也是一個依賴性名稱;
10     // 所以模板聲明時也不會管X模板里面有沒有MemberType這回事。
11     typedef typename X<T>::MemberType MemberType2;
12     
13     // UnknownType 不是一個依賴性名稱
14     // 而且這個名字在當前作用域中不存在,所以直接報錯。
15     // typedef UnknownType MemberType3;    
16 
17     void foo()
18     {
19         X<T> instance0;
20         typename X<T>::MemberType instance1;
21     }
22 };
View Code

eg2.

 1 struct A;
 2 template <typename T> struct B;
 3 template <typename T> struct X {
 4     typedef X<T> _A; // 編譯器當然知道 X<T> 是一個類型。
 5     typedef X    _B; // X 等價於 X<T> 的縮寫
 6     typedef T    _C; // T 不是一個類型還玩毛
 7     
 8     // !!!注意我要變形了!!!
 9     class Y {
10         typedef X<T>     _D;          // X 的內部,既然外部高枕無憂,內部更不用說了
11         typedef X<T>::Y  _E;          // 嗯,這里也沒問題,編譯器知道Y就是當前的類型,
12                                       // 這里在VS2015上會有錯,需要添加 typename,
13                                       // Clang 上順利通過。
14         typedef typename X<T*>::Y _F; // 這個居然要加 typename!
15                                       // 因為,X<T*>和X<T>不一樣哦,
16                                       // 它可能會在實例化的時候被別的偏特化給搶過去實現了。
17     };
18     
19     typedef A _G;                   // 嗯,沒問題,A在外面聲明啦
20     typedef B<T> _H;                // B<T>也是一個類型
21     typedef typename B<T>::type _I; // 嗯,因為不知道B<T>::type的信息,
22                                     // 所以需要typename
23     //typedef B<int>::type _J;        // B<int> 不依賴模板參數,
24                                     // 所以編譯器直接就實例化(instantiate)了
25                                     // 但是這個時候,B並沒有被實現,所以就出錯了
26 };
View Code

 自己的理解:什么時候需要typename?如果編譯器無法判斷當前名稱代表的是類型還是實例的時候,需要用typename來表示指代類型。如果遇到T::size_type * p; 無法知道是乘法運算還是定義指針。這時候,當我們希望通知編譯器一個名字表示類型時,必須用關鍵字typename,而不是class。則為typename T::size_type *p;

 

============================分割線============================

引用折疊

引用折疊只能應用於間接創建的引用的引用,如類型別名或模板參數。

& &&,&& &,& &折疊后都是左值引用&; && &&折疊后是右值引用&&。

模板實參推斷與引用

其中i是int,ci是const int.

template<typename T> void f1(T&); // 實參必須是左值,const屬性得到保持

f1(i); //T是int

f1(ci); //T是const int

f1(5); //error, 必須是左值

template<typename T> void f2(const T&);  //可以接受右值

f2(i);

f2(ci);

f2(5); //以上T均為int

template<typename T> void f3(T&&); //實參為左值,T為左值引用;實參為const左值,T為const左值引用;實參為右值,T為右值引用

f3(i); //T是int&

f3(ci); //T是const int&

f3(5); //T是int&& 

T&&,對應的const屬性和左值/右值屬性將得到保持。T&&可以實現轉發,保持轉發參數的所有性質,包括const和左右值。

移動與完美轉發

std::move 如下.

/// remove_reference
template<typename _Tp>
struct remove_reference
{ typedef _Tp   type; };

template<typename _Tp>
struct remove_reference<_Tp&>
{ typedef _Tp   type; };

template<typename _Tp>
struct remove_reference<_Tp&&>
{ typedef _Tp   type; };

template <typename T>
typename remove_reference<T>::type&& move(T&& t) {
    return static_cast<typename remove_reference<T>::type&&>(t);
}

std::forward.

template <typename T>
T&& forward(typename remove_reference<T>::type& param) 
{
    return static_cast<T&&>(param);    
}

eg.

template<typename T>
void foo(T&& fparam)
{
    std::forward<T>(fprams);    
}

int i = 7;
foo(i);//T為int&,std::forward<T>(fprams)的類型為int & &&,折疊后為int &
foo(47);//T為int, std::forward<T>(fprams)的類型為int &&

//通過完美轉發,可以簡化代碼,其中m_var1和m_var2是全局變量
void set(const string & var1, const string & var2) {
    //拷貝賦值
    m_var1 = var1;
    m_var2 = var2;
}
void set(string && var1, string && var2){
    //移動賦值
    m_var1 = std::move(var1);//move不能省略,具名變量都被當作左值
    m_var2 = std::move(var2);
}
//以上兩個函數可以用以下函數代替
template<typename T1, typename T2>
void set(T1 && var1, T2 && var2){
    m_var1 = std::forward<T1>(var1);
    m_var2 = std::forward<T2>(var2);
}
//注:forward常用於template函數中,必須要多帶一個參數forward<T>

 

可變參數函數模板(variadic function template)

主要使用了包擴展(pack expansion)的方式, 即"...",  把一個模式(pattern)擴展為包中每一個元素(element)的形式;

可變參數函數模板, 常使用遞歸(recursive)進行處理包(pack)參數, 

需要一個非可變參數(nonvariadic)函數模板,結束遞歸, 當最后一次調用時, 調用非可變參數版本, 則遞歸結束;

還需要一個綁定(bing)的參數, 處理包(pack)中的一些元素, 通過遞歸,順次處理包中的元素;

 

包擴展可以應用於各種形式, 如 

函數的模板參數, 例如: templae<typename... Args>    擴展后即 template<typename Args1, typename Args2, typename Args3>

函數的參數模板, 例如: cosnt Args&... rest 擴展后即const Args& rest1, const Args& rest2, const Args& rest3

函數的形參, 例如: rest... 擴展后即rest1, rest2, rest3

函數模板, 例如: debug_rep(rest)...擴展后即debug_rep(rest1), debug_rep(rest2), debug_rep(rest3) //debug_rep是模板函數名

eg.

 1 #include <iostream>  
 2 #include <sstream>  
 3   
 4 using namespace std;  
 5   
 6 //返回bug信息  
 7 template <typename T> 
 8 std::string debug_rep (const T& t)  
 9 {  
10     std::ostringstream ret;  
11     ret << t;  
12     return ret.str();  
13 }  
14   
15 //非可變參數模板  
16 template<typename T>  
17 std::ostream &print(std::ostream &os, const T &t)  
18 {  
19     //std::cout << "This is nonvariadic function! ";  
20     return os << t;  
21 }  
22   
23 //可變參數模板, "..."是包擴展(Pack Expansion)  
24 template <typename T, typename... Args>  
25 std::ostream &print(std::ostream &os, const T &t, const Args&... rest)  
26 {  
27     os << t << ", ";  
28     return print(os, rest...);  
29 }  
30   
31 //函數模板的包擴展  
32 template <typename... Args>  
33 std::ostream &errorMsg(std::ostream &os, const Args&... rest)  
34 {  
35     return print(os, debug_rep(rest)...); //使用模板的包擴展  
36 }  
37   
38 int main()  
39 {  
40     int i(10); std::string s("girls");  
41     //print(std::cout, i, s, 42);  
42     errorMsg(std::cout, i, s, 10, "ladies");  
43   
44     return 0;  
45 }

C++11中的tuple就是可變參數類模板,在github找到了一份實現代碼。(這個人好像還實現了好多別的東西,比如紅黑樹,堆排序,哈希表)

 1 #include <iostream>
 2 
 3 template<typename ... Types> class Tuple;
 4 template<> class Tuple<> {};
 5 template<typename First, typename ... Rest>
 6 class Tuple<First, Rest...>: private Tuple<Rest...> {
 7   First Member;
 8  public:
 9   Tuple(const First& first, const Rest& ... rest):
10       Tuple<Rest...>(rest...), Member(first) {}
11   
12   const First& Head() const {
13     return Member;
14   }
15   
16   const Tuple<Rest...>& Tail() const {
17     return *this;
18   }
19 };
20 
21 
22 // As a return value of Get<>(Tuple) method is not a reference,
23 // in order to make it available to return POD types.
24 
25 template<size_t I, class T>
26 struct TupleElement;
27 
28 template<size_t I, class Head, class ... Rest>
29 struct TupleElement<I, Tuple<Head, Rest...> >:
30     public TupleElement<I - 1, Tuple<Rest...> > {
31   static typename TupleElement<I, Tuple<Head, Rest...> >::Type
32       Get(const Tuple<Head, Rest...>& t) {
33     return TupleElement<I - 1, Tuple<Rest...> >::Get(t.Tail());
34   }
35 };
36  
37 template<class Head, class ... Rest>
38 struct TupleElement<0, Tuple<Head, Rest...> > {
39   typedef Head Type;
40   static typename TupleElement<0, Tuple<Head, Rest...> >::Type
41       Get(const Tuple<Head, Rest...>& t) {
42     return t.Head();
43   }
44 };
45 
46 template<size_t Pos, class Head, class ... Rest>
47 typename TupleElement<Pos, Tuple<Head, Rest...> >::Type
48     Get(const Tuple<Head, Rest...>& t) {
49   return TupleElement<Pos, Tuple<Head, Rest...> >::Get(t);
50 }
51 
52 int main() {
53   Tuple<int, double, char> t(42, 3.14, 'a');
54   std::cout << Get<0>(t) << std::endl;
55   std::cout << Get<1>(t) << std::endl;
56   std::cout << Get<2>(t) << std::endl;
57   return 0;
58 }

轉發參數包

 組合使用forward機制與可變參數模板實現轉發參數包。《C++ primer 5th》16.4.3

 ============================分割線============================

模板元編程

(黑魔法:編譯期間完成計算,如斐波那契,平方根等)

eg.斐波那契等

 1 #include <bits/stdc++.h>
 2 
 3 template<int N>
 4 struct Fac{
 5     static int const result = Fac<N-1>::result + Fac<N-2>::result;
 6 };
 7 template<>
 8 struct Fac<0>{
 9     static int const result = 0;
10 };
11 template<>
12 struct Fac<1>{
13     static int const result = 1;
14 };
15 /*
16 推薦使用enum
17 靜態成員變量只能是左值
18 如果遇到void foo(int const&);
19 foo(Fac<7>::result);
20 編譯器將傳遞Fac<7>::result的地址,
21 會強制編譯期實例化靜態成員的定義,
22 並為該定義分配內存,
23 於是該計算將不局限於完全的“編譯期”效果
24 而enum不是左值, 會以常量的形式傳遞參數
25 以上抄自<<C++ template(中文版)>>P296
26 */
27 
28 template<int N>
29 struct Sum{
30     enum{result = N+Sum<N-1>::result};
31 };
32 template<>
33 struct Sum<0>{
34     enum{result = 0};
35 };
36 
37 int main(){
38     std::cout << Fac<46>::result << std::endl;
39     //std::cout << Fac<47>::result << std::endl; // without Fac<46>, compile error
40     std::cout << Sum<900>::result << std::endl;
41     //std:cout << Sum<901>::result << std::endl; // without Sum<900>, compile error
42     std::cout << Sum<1800>::result << std::endl; // OK
43     //std::cout << Sum<1801>::result << std::endl; // without Sum<1800>, compile error
44     return 0;
45 }

 

eg. Sqrt簡易版本

 1 //easy版本, :?條件表達式會同時實例化Sqrt<20, 0, 9>, Sqrt<20, 10, 20>
 2 #include <bits/stdc++.h>
 3 template<int N, int L = 0, int R = N>
 4 class Sqrt{
 5 public:
 6     enum{m = (L+R+1) >> 1};
 7     enum{result = m*m > N? Sqrt<N, L, m-1>::result : Sqrt<N, m, R>::result};
 8 };
 9 template<int N, int L>
10 class Sqrt<N, L, L>{
11 public:
12     enum{result = L};
13 };
14 
15 int main(){
16     std::cout << Sqrt<20>::result;
17     return 0;
18 }

tips:可以通過typedef+IfThenElse模板優化條件表達式

 1 #include <bits/stdc++.h>
 2 
 3 #ifndef IFTHENELSE
 4 #define IFTHENELSE
 5 template<bool C, typename Ta, typename Tb>
 6 class IfThenElse;
 7 
 8 //局部特化
 9 template<typename Ta, typename Tb>
10 class IfThenElse<true, Ta, Tb>{
11 public:
12     typedef Ta Result;
13 };
14 template<typename Ta, typename Tb>
15 class IfThenElse<false, Ta, Tb>{
16 public:
17     typedef Tb Result;
18 };
19 #endif
20 
21 template<int N, int L = 0, int R = N>
22 class Sqrt{
23 public:
24     enum{m = (L+R+1) >> 1};
25     typedef typename IfThenElse<
26         (m*m > N), 
27         Sqrt<N, L, m-1>,
28         Sqrt<N, m, R>
29     >::Result SubT;    //定義typedef不會實例化
30     enum{result = SubT::result};
31 };
32 template<int N, int L>
33 class Sqrt<N, L, L>{
34 public:
35     enum{result = L};
36 };
37 
38 int main(){
39     std::cout << Sqrt<20>::result;
40     return 0;
41 }

 迭代版本

 1 #include <bits/stdc++.h>
 2 
 3 #ifndef IFTHENELSE
 4 #define IFTHENELSE
 5 template<bool C, typename Ta, typename Tb>
 6 class IfThenElse;
 7 
 8 //局部特化
 9 template<typename Ta, typename Tb>
10 class IfThenElse<true, Ta, Tb>{
11 public:
12     typedef Ta Result;
13 };
14 template<typename Ta, typename Tb>
15 class IfThenElse<false, Ta, Tb>{
16 public:
17     typedef Tb Result;
18 };
19 #endif
20 
21 template<int N>
22 class Value{
23 public:
24     enum{result = N};
25 };
26 template<int N, int I = 0>
27 class Sqrt{
28 public:
29     typedef typename IfThenElse<
30         ((I+1)*(I+1) > N), 
31         Value<I>, //i, 特意構造一個Value類
32         Sqrt<N, I+1>
33     >::Result SubT;
34     
35     enum{result = SubT::result};
36 };
37 
38 int main(){
39     std::cout << Sqrt<20>::result;
40     return 0;
41 }

 

未完待續


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM