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]); }
学习版块
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 是一个类型还是一个成员名称(静态数据成员或者静态函数),所以编译不能够通过。
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 };
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 };
自己的理解:什么时候需要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 }
未完待续