远观像"类",近看不是
Template是编译时多态。所有的模板都是在编译时产生对应的代码,它没有面向对象中的虚表,无法实现动态多态。
模板是泛型编程的基础,泛型编程即以一种独立于任何特定类型的方式编写代码。
Function Template
A function template is a prescription for the compiler to generate particular instances of a function varying by type.
”变量类型“的部分放在一块,与“变量类型无关”的部分放在另一边。
放在"头文件“中,声明和实现建议这么做。其他方式不推荐。
模板函数
typename的使用,增强了函数的扩展性。
#include <iostream> #include <string> using namespace std; ------------------------------------------------------------------- template <typename T> inline T const& Max (T const& a, T const& b) { return a < b ? b:a; }
-------------------------------------------------------------------
int main () { int i = 39; int j = 20; cout << "Max(i, j): " << Max(i, j) << endl; double f1 = 13.5; double f2 = 20.7; cout << "Max(f1, f2): " << Max(f1, f2) << endl; string s1 = "Hello"; string s2 = "World"; cout << "Max(s1, s2): " << Max(s1, s2) << endl; return 0; }
Name Resolution
概念解析:
Templates and Name Resolution
中文版链接:模板和名称解析
在模板定义中,有三种类型的名称。
-
-
-
本部声明的名称,包括模板本身的名称以及在模板定义中声明的任何名称。
-
模板定义之外的封闭范围中的名称。
-
在某种程度上依赖于模板自变量的名称 (称为依赖名称) 。
-
-
参考:《C++语言程序设计(第4版)》(郑莉,董渊)
-
- designer & client
- Only three kinds of implicit conversions for type parameters:
参数的类型不同的数字比较的话,例如:int , double
使用例如:min(static_cast<double> i, d)的方式。
decltype: what's its type?
decltype 与 auto
Ref: auto和decltype的用法总结
Ref: 【有赞】
在使用过程中和const、引用和指针结合时需要特别小心。decltype和auto都可以用来推断类型,但是二者有几处明显的差异:
- auto忽略顶层const,decltype保留顶层const;
- 对引用操作,auto推断出原有类型,decltype推断出引用;【decltype更牛逼一些】
- 对解引用操作,auto推断出原有类型,decltype推断出引用;
- auto推断时会实际执行,decltype不会执行,只做分析。
(1) decltype 的作用
decltype只是为了推断出表达式的类型而不用这个表达式的值来初始化对象.
表达式:
decltype(func()) sum = x; // sum的类型是函数func()的返回值的类型, 但是这时不会实际调用函数func()
变量:
int i = 0;
decltype(i) j = 4; // i的类型是int, 所以j的类型也是int
(2) decltype 和 const
不论是顶层const还是底层const, decltype都会保留
const int i = 3;
decltype(i) j = i; // j的类型和i是一样的, 都是const int
作为对比,auto的特性则是:
auto会忽略掉顶层const,保留底层const. 举例:
const int i = 5; auto a = i; // 变量i是顶层const, 会被忽略, 所以b的类型是int auto b = &i; // 变量i是一个常量, 对常量取地址是一种底层const, 所以b的类型是const int *
因此,如果希望推断出的类型是顶层const的,那么就需要在auto前面加上cosnt:
const auto c = i;
(3) decltype 和 引用
① 如果表达式是引用类型, 那么decltype的类型也是引用
const int i = 3, &j = i; decltype(j) k = 5; // k的类型是 const int &
② 如果表达式是引用类型, 但是想要得到这个引用所指向的类型, 需要修改表达式: 【找引用指代的原类型】
int i = 3, &r = i; decltype(r + 0) t = 5; // 此时是int类型
③ 对指针的解引用操作返回的是引用类型【找指针指代的原类型】
int i = 3, j = 6, *p = &i; decltype(*p) c = j; // c是int类型的引用, c和j绑定在一起
④ 如果一个表达式的类型不是引用, 但是我们需要推断出引用, 那么可以加上一对括号, 就变成了引用类型了【凭空添加一个原变量的引用类型】
int i = 3; decltype((i)) j = i; // 此时j的类型是int类型的引用, j和i绑定在了一起
为啥用 decltype
auto 和 decltype 都是编译时类型推导。
auto 解决不了参数type不定的问题
auto means "the variable's type is deduced from the initialiser."
decltype refers to a type in an arbitrary context.
Here's an example where you can't use auto:
template <typename T, typename U, typename V> void madd(const T &t, const U &u, const V &v, decltype(t * u + v) &res) { res = t * u + v; }
(1) 解决模板函数返回值不确定情况:
template <typename T, typename U>
auto mymin(T a, U b) -> decltype(a < b ? a : b) {
return a < b ? a: b;
}
泛型编程中结合auto,用于追踪函数的返回值类型!
(2) 根据变量推导齐类型:
int i;
const &j = i;
int *p = i;
int k;
decltype(i) x; // int x: i is a variable
decltype(j) y = k; // int &y = k: j is an lvalue
decltype(*p) z = k; // int &z = k: *p is an lvalue
decltype((i)) w = k; // int &w = k: (i) is an lvalue
(3) 与using/typedef合用,用于定义类型:
Ref: C++11特性:decltype关键字
using size_t = decltype(sizeof(0));//sizeof(a)的返回值为size_t类型 using ptrdiff_t = decltype((int*)0 - (int*)0); using nullptr_t = decltype(nullptr);
vector<int >vec; typedef decltype(vec.begin()) vectype; for (vectype i = vec.begin; i != vec.end(); i++) { //... }
(4) 重用匿名类型:
在C++中,我们有时候会遇上一些匿名类型,而借助decltype,我们可以重新使用这个匿名的结构体:
struct
{
int d ;
doubel b;
}anon_s;
decltype(anon_s) as ; //定义了一个上面匿名的结构体
decltype推导四规则
推导规则
- 如果e是一个没有带括号的标记符表达式或者类成员访问表达式,那么的decltype(e)就是e所命名的实体的类型。此外,如果e是一个被重载的函数,则会导致编译错误。
- 否则 ,假设e的类型是T,如果e是一个将亡值,那么decltype(e)为T&&
- 否则,假设e的类型是T,如果e是一个左值,那么decltype(e)为T&。
- 否则,假设e的类型是T,则decltype(e)为T。
int decipline(void) { int i = 4; int arr[5] = { 0 }; int *ptr = arr; struct S{ double d; }s ; // void Overloaded(int); // void Overloaded(char);//重载的函数 int && RvalRef(); const bool Func(int); //规则一:推导为其类型 decltype (arr) var1; //int 标记符表达式【又是一个数组的bug】 decltype (ptr) var2; //int * 标记符表达式 decltype (s.d) var3; //doubel 成员访问表达式 //decltype(Overloaded) var4;//重载函数。编译错误。 //规则二:将亡值。推导为类型的右值引用。 decltype (RvalRef()) var5 = 1; //规则三:左值,推导为类型的引用。 decltype ((i))var6 = i; //int& decltype (true ? i : i) var7 = i; //int& 条件表达式返回左值。 { int x = 10; float y = 1.11; decltype(true?x:y) c = (false?x:y); cout << "c = " << c << endl; } decltype (++i) var8 = i; // int& ++i返回i的左值。 decltype(arr[5]) var9 = i; // int&. []操作返回左值 decltype(*ptr) var10 = i; // int& *操作返回左值 decltype("hello")var11 = "hello"; // const char(&)[9] 字符串字面常量为左值,且为const左值。 //规则四:以上都不是,则推导为本类型 decltype(1) var12;//const int decltype(Func(1)) var13=true;//const bool decltype(i++) var14 = i;//int i++返回右值 return 0; }
左值判断
cout << is_lvalue_reference<decltype(++i)>::value << endl;
模板的健壮性
(1) 若要比较字符串,使用函数override.
const char *s = "xyz";
const char *t = "abc";
/* 返回了数字,认为是地址,也就是数组的首地址 */
cout << min(s, t) << endl;
(2) 模板的设计 更加健壮。如下:
(3) template function的默认参数 也可以设置,实现健壮性:
(4) template's variadic function ,进一步健壮:
注意关键写法:
template <typename A, typename... B>
void print(A head, B... tail) {
...
}
Class Template
---- 类是不能重载的,要解决这个问题。
菜鸡教程
- 作为对比:函数模板
template <typename T> inline T const& Max (T const& a, T const& b) { return a < b ? b:a; }
- 类的模板
using namespace std;
template <typename> class Stack;
template <typename T> ostream& operator << (ostream &os, const Stack<T>&s);
template <typename T> class Stack {
public:
friend ostream& operator << <T>(ostream &, const Stack<T> &); //指明:是以上实现函数模板的实例
void push(const T &);
void pop();
T& top();
const T& top() const;
bool empty() const;
private:
vector<T> stack_; // 同时也表明了:在已有的类型的基础上 再扩展出一个类型。更加灵活的方式可使用参数法传入。
};
- 菜鸡实例
Ref: http://www.runoob.com/cplusplus/cpp-templates.html
一个栈的“类”,但不知道未来的元素可能会是什么类型,遂使用“模板”。
#include <iostream> #include <vector> #include <cstdlib> #include <string> #include <stdexcept> using namespace std; template <class T> // 【定义模板】 class Stack { private: vector<T> elems; // 元素 public: void push(T const&); // 入栈 void pop(); // 出栈 T top() const; // 返回栈顶元素 bool empty() const{ // 如果为空则返回真。 return elems.empty(); } };
---------------------------------------------------------------- template <class T> void Stack<T>::push (T const& elem) { // 追加传入元素的副本 elems.push_back(elem); } template <class T> void Stack<T>::pop () { if (elems.empty()) { throw out_of_range("Stack<>::pop(): empty stack"); } // 删除最后一个元素 elems.pop_back(); } template <class T> T Stack<T>::top () const { if (elems.empty()) { throw out_of_range("Stack<>::top(): empty stack"); } // 返回最后一个元素的副本 return elems.back(); } ----------------------------------------------------------------
int main() { try { Stack<int> intStack; // int 类型的栈 【使用模板】 Stack<string> stringStack; // string 类型的栈 // 操作 int 类型的栈 intStack.push(7); cout << intStack.top() <<endl; // 操作 string 类型的栈 stringStack.push("hello"); cout << stringStack.top() << std::endl; stringStack.pop(); stringStack.pop(); } catch (exception const& ex) { cerr << "Exception: " << ex.what() <<endl; return -1; } }
COMP6771 教程
模板的高一个层次的视角去看待这个问题:std::deque<T>
在头文件中实现:
友元相关的部分:
template <typename T>
std::ostream& operator<<(std::ostream &os, const Stack<T> &s) {
for (unsigned int i = 0; i < s.stack_.size(); i++) {
os << s.stack_[i] << " ";
}
return os;
}
//---test.h #ifndef test_h_ #define test_h_ #include <iostream> using namespace std; // 改动一:增加函数模板的声明——而这又需要先声明类模板 template <class T> class Test; template <class T> void display(Test<T> &t); template <class T> class Test { private: T x; public: Test (T x_): x(x_) {} friend void display<>(Test<T> &t); // 改动二:在函数名后面加上<>,指明它是之前声明的函数模板 的实例 }; template <class T> void display(Test<T> &t) { cout << t.x << endl; } #endif // test_h_