C++ 学习笔记(16)模板、引用折叠、std::forward、可变参数模板、模板特例化
2018-02-19 19:25
597 查看
C++ 学习笔记(16)模板、引用折叠、std::forward、可变参数模板、模板特例化
参考书籍:《C++ Primer 5th》API:模板
16.1 定义模版
16.1.1 函数模版
template <typename T> int compare(const T &v1, const T &v2) { if (v1 < v2) return -1; if (v2 < v1) return -1; return 0; } template <typename T, class U> calc (const T&, const U&); // 每个模版参数都要带 typename 或 class 关键字 template <typename T> inline T func(const T&); // inline 或 constexpr 说明符 放在模版参数列表之后
非类型模版参数:表示值而非类型。可以是整形,指向对象或函数的指针或左值引用。而绑定的实参必须是常量表达式,绑定到指针或引用实参必须在静态内存里。
// 比较两个字符串 template <unsigned M, unsigned N> int compare(const char(&p1)[M], const char(&p2) ) { retrun strcpy(p1, p2); }
函数模版和类模版成员函数的定义通常放在头文件。
16.1.2 类模版
不同于函数模版,编译器无法为类模版推断模版参数类型。template <typename T> A<T>::A() { } // A类的函数在类外定义时,也需要指名模版参数
默认情况下,对于一个实例化了类模板,其成员只有在使用时才被实例化。
在类模版作用域内,编译器处理模板自身引用时可以省略模版参数。
template <typename T> class A { public: // 定义时省略<T> 模版参数 A& operator++() { A a = this; // 使用时也省略参数模版 return a; } };
别名模版
template<typename T> using twin = pair<T, T>; // 使用别名 twin<int> ti; // ti是 pair<int, int>
16.1.3 模版参数
使用类的类型成员,直接编写其成员,如果类型不包含该成员时,编译器会报错。// 返回T的value_type成员,声明定义时如果没有该成员,编译时出错 template <typename T> typename T::value_type top(const T& c) { if (!c.empty()) // 执行该类的成员函数,(如果没有,运行错误) return c.back(); else return typename T::value_type(); // 若无,值初始化 } void main() { vector<int> vi = { 3,4,5,2 }; cout << top<vector<int>>(vi); // 输出2 }
// 可以使用默认实参,如下,默认比较函数使用标准库的less 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; }
16.1.4 成员模版
一个类可以包含本身是模版的成员函数,这个成员称为成员模版,不可以是虚函数。struct Printer { std::ostream& os; Printer(std::ostream& os) : os(os) {} template<typename T> void operator()(const T& obj) { os << obj << ' '; } // 成员模板 }; void main() { std::vector<int> v = { 1,2,3 }; std::for_each(v.begin(), v.end(), Printer(std::cout)); // 使用临时Printer对象 Printer pinter = Printer(std::cout); std::string s = "abc"; std::for_each(s.begin(), s.end(), pinter); // 使用Printer对象 }
16.1.5 控制实例化
模版被使用时才会实例化。多个相同模版就会生成多个实例,可以使用显示实例化避免这种开销,即使用extern声明实例,但也必须实例化定义。
实例化一个类模版时,所有成员都会实例化(包括内联成员函数)。
16.1.6 效率与灵活性
unique_ptr在编译时绑定删除器,避免间接调用删除器的运行时开学。shared_ptr在运行时绑定删除器,使用户重载删除器更方便。
16.2 模版实参推断
16.2.1 类型转换与模版类型参数
const转换:可以将一个非const对象的引用(或指针)传递给const的形参。数组或函数指针转换:函数形参不是引用类型时,可以用数组或函数类型做指针转换。(数组转指向第一个元素的指针,函数转指向该函数类型的指针)
传递数组实参时:
实参传递是引用时:数组大小不同时,类型不同。
实参传递非引用时:数组转换成指针,不在乎数组大小。
16.2.2 函数模版显式实参
template <typename T1, typename T2, typename T3> T1 sum1(T2, T3) {} template <typename T1, typename T2, typename T3> T3 sum2(T2, T1) {} // 糟糕的设计 void main() { // T1显示指定(必须指定),T2、T3从实参类型中推断。 auto v1 = sum1<int>(1.0, 2.0); // int sum<int, double, double>(double, double) // 因为T3才是返回值,所以必须要指定T1、T2。 auto v2 = sum2<float, float, int>(2, 4); // 正确 auto v3 = sum3<int>(3, 5); // 错误,返回值无法推断出来。 }
16.2.3 尾置返回类型与类型转换
使用标准库type_traits,实现类型转换。// 指名返回值为参数类型的解引用,如果传入int序列,那就是返回int& template <typename It> auto func1(It beg, It end) -> decltype(*beg) {} // 使用标准库type_traits的模版,必须使用typename告诉编译器type是类型,传入int序列时,返回int类型值 template <typename It> auto func2(It beg, It end) -> typename remove_reference<decltype(*beg)>::type {}
16.2.5 模版实参推断和引用
右值引用参数:被传入左值时,类型是左值引用。引用的引用:会被折叠成普通引用。
X& &、
X& &&和
X&& &都会折叠成类型
X&。
X&& &&折叠成
X&&。
引用折叠只能用于间接创建的引用的引用,如类型别名或模版参数。
模版参数类型为右值引用时(T&&):可以传递任意类型实参。且传递左值时,会被实例化成左值引用。
右值引用常用于:
模版转发实参。
模版重载。
template <typename T> void f(T&&) {} // 右值形参 void main() { int i = 123; const int ci = 345; f(123); // T为int。(传入右值) f(i); // T为int &。(传入左值) f(ci); // T为const int &。(传入左值) }
16.2.6 理解std::move
参数为右值,所以可以传入任何实参(包括左值)。remove_reference移除参数的左值引用(如果有)。
使用static_cast:将左值类型转换为右值引用。
template <typename T> typename remove_reference<T>::type && move(T&& t) { return static_cast<typename remove_reference<T>::type&&>(t); }
16.2.7 转发
使用std::forward保持实参传递的左值/右值属性。配合传入模版参数类型为右值引用(T&&)时,forward会保持实参类型的所有细节。
// 调用函数f,按t2、t1顺序传入参数。 template <typename F, typename T1, typename T2> void flip(F f, T1 &&t1, T2 &&t2) { // 使用forward,传入实参的左值右值属性都会被保留。 f(std::forward<T2>(t2), std::forward<T1>(t1)); }
16.3 重载与模板
如果多个函数提供同样好的匹配:只有一个是非模板函数,选择这个。
没有非模板函数,在多个最特例化的模版。
否则,调用有歧义。
cout << debug("hello") << endl; // 传入const char *,调用 debug(T *) // 下面三者都满足,调用优先级依次降低 // debug(T*),T为const char,比下面的更特例化 // debug(const T&),T为char[6] // debug(const string&),从const char * 转换到 string,可以,但不是精确匹配
16.4 可变参数模板
可变数目的参数成为参数包。模板参数包:零个或多个模板参数。
函数参数包:零个或多个函数参数。
template <typename... Args> void foo(const Args &... rest) { cout << sizeof...(Args) << endl; // 类型参数的数目 cout << sizeof...(rest) << endl; // 函数参数的数目 }
16.4.1 编写可变参数函数模板
template <typename T,typename... Args> void print(ostream &os,const T& t, const Args &... rest) { os << t << ", "; print(os, rest...); // 递归调用,每次调用减少一个参数(t) } // 递归最内层的函数。非可变参数版本。必须有该函数,否则可变参数函数调用出错。 template <typename T> void print(ostream &os, const T& t) { os << t << "!! "; } void main() { // 输出:123, abc, 2.333!! print(cout, 123, "abc", 2.333f); }
16.4.2 包扩展
template <typename... Args> void print(ostream &os, const Args &... rest) { print(os, debug(rest) ...); // 对每个参数都作为debug()函数的参数,递归调用 }
16.4.3 转发参数包
// 模仿构造函数的参数列表 template <class... Args> void emplace_back(Args&&...);
16.5 模板特例化
模板及其特例化版本应该声明在同一个头文件,所有同名模板的声明应该放在前面,然后是这些模板的特例化版本。只能部分特例化类模板,不能部分特例化函数模板。
// 版本1:比较任意两个类型 template <typename T> int compare(const T &v1, const T &v2); // 版本2:处理字符串字面常量,处理的是数组,不能是指针 template <unsigned M, unsigned N> int compare(const char(&p1)[M], const char(&p2) ); // 版本3:模板特例化,可直接处理字符串(compare("sd","ab") ) template <> int compare(const char* const &p1, const char* const &p2);
相关文章推荐
- C++学习笔记十六-模板和泛型编程(二)
- [置顶]C++学习笔记之模板篇
- C++学习笔记_9:类模板
- c++ 学习笔记 c++ 引用C库注意点:#ifdef __cplusplus 倒底是什么意思?
- C++11学习笔记:std::move和std::forward源码分析
- <C++学习笔记>函数模板 template
- c++学习笔记1(指针和引用)
- C++学习笔记与思考_2 --const限定符及其引用,typedef符号
- c++学习笔记(四):函数按指针调用和按引用调用
- c++学习笔记--引用&
- C++学习笔记-4-引用
- C++学习笔记十六-模板和泛型编程(一)
- C/C++中关于地址、指针和引用变量的学习笔记(四) : 函数
- 【CodeSmith学习笔记】:特殊字符如何包含在模板中和模板引用其他文件
- reference引用笔记----C++学习之路
- c++学习笔记-------《c++自学通》第十二章 高级引用和指针
- C++ STL 学习笔记 list,forward_list
- C++学习笔记之 引用
- C++学习笔记之——引用 内联函数
- C++ Standard Stl -- SGI STL源码学习笔记(03) STL中的模板编译期检查与偏特化编译期检查