Hi!这里是山幺幺的c++ primer系列。写这个系列的初衷是,虽然在学校学习了c++,但总觉得对这门语言了解不深,因此我想来啃啃著名的c++ primer,并在这里同步记录我的学习笔记。由于我啃的是英文版,所以笔记是中英文夹杂的那种。另外由于已有一定的编程基础,所以这个系列不会含有太基础的知识,想入门的朋友最好自己啃书嘻嘻~
概述
- OOP和模板都是deal with types that are not known at the time the program is written,区别在于:
- OOP deals with types that are not known until run time(动态绑定)
- 模板:the types become known during compilation(实例化)
定义模板及模板实例化
注意
- 在定义template时,template parameter list cannot be empty
- each type parameter must be preceded by the keyword class or typename(class 和 typename 无区别)
// ok: no distinction between typename and class in a template parameter list
template <typename T, class U> calc (const T&, const U&);
- 可以有nontype parameters,nontype parameters可以是integral type, or a pointer or (lvalue) reference to an object or to a function type;实例化时nontype parameters are replaced with a value supplied by the user or deduced by the compiler,因此必须用constant expressions来实例化integral type 的 nontype parameters、用有静态生存期的变量来实例化引用类型的 nontype parameters、用有静态生存期的变量/值为0的constant expression/nullptr来实例化指针类型的 nontype parameters;nontype parameter可以被当作constant expression使用
template<unsigned N, unsigned M>
int compare(const char (&p1)[N], const char (&p2)[M]) {
return strcmp(p1, p2);
}
compare("hi", "mom")
// 会把compare实例化为:
int compare(const char (&p1)[3], const char (&p2)[4]) // 编译器会在字符串字面量后插入'\0'
Function Template
定义及实例化
- 栗子:<typename T> 就是 template parameter list
template <typename T>
int compare(const T &v1, const T &v2) {
if (v1 < v2) return -1;
if (v2 < v1) return 1;
return 0;
}
// 实例化
cout << compare(1, 0) << endl; // T is int
- 在上面的栗子中,编译器会为最后一句 write and compile a version of compare函数 with T replaced by int
inline and constexpr Function Templates
- inline和constexpr写在template parameter list和返回类型中间
template <typename T> inline T min(const T&, const T&);
Class Template
定义及实例化
- 栗子:注意定义在类外的成员函数也要加上template关键字
template <typename T> class Blob {
public:
typedef T value_type;
typedef typename std::vector<T>::size_type size_type;
// constructors
Blob();
Blob(std::initializer_list<T> il);
// number of elements in the Blob
size_type size() const { return data->size(); }
bool empty() const { return data->empty(); }
// add and remove elements
void push_back(const T &t) {data->push_back(t);}
// move version; see § 13.6.3 (p. 548)
void push_back(T &&t);
void pop_back();
// element access
T& back();
T& operator[](size_type i); // defined in § 14.5 (p. 566)
private:
std::shared_ptr<std::vector<T>> data;
// throws msg if data[i] isn't valid
void check(size_type i, const std::string &msg) const;
};
template <typename T>
void Blob<T>::push_back(T &&t) { data->push_back(std::move(t)); }
template <typename T>
Blob<T>::Blob(): data(std::make_shared<std::vector<T>>()) { }
template <typename T>
Blob<T>::Blob(std::initializer_list<T> il): data(std::make_shared<std::vector<T>>(il)) { }
- 与函数模板不同,实例化class template时必须提供额外信息:a list of explicit template arguments that are bound to the template’s parameters,即<>中要指明类型
Blob<int> ia; // empty Blob<int>
Blob<int> ia2 = {0,1,2,3,4}; // Blob<int> with five elements
// these definitions instantiate two distinct Blob types
Blob<string> names; // Blob that holds strings
Blob<double> prices;// different element type
- the name of a class template is not the name of a type;实例化后的才是type
- a member function of a class template is instantiated only if the program uses that member function,因此存在只有部分成员函数被实例化的情况,所以我们可以instantiate a class with a type that may not meet the requirements for some of the template’s operations
class template name的简写
- inside the scope of a class template时,可以简写:
template <typename T> class BlobPtr
public:
BlobPtr& operator++();
BlobPtr& operator--();
};
// 等价于
template <typename T> class BlobPtr
public:
BlobPtr<T>& operator++();
BlobPtr<T>& operator--();
};
- 注意定义在类外的成员函数的return type不在类的scope中
template <typename T>
BlobPtr<T> BlobPtr<T>::operator++(int) {
BlobPtr ret = *this; // 等价于 BlobPtr<T> ret = *this;
++*this;
return ret;
}
class template与友元
- 概述:当class template的友元
- 不是模板时,该友元有 access to all the instantiations of the class template
- 是模板时,the class granting friendship controls whether friendship includes all instantiations of the template or only specific instantiation
- one-to-one friendship:只有与Blob<T>的T相同的BlobPtr<T>和operator==<T>才是Blob<T>的友元
// forward declarations needed for friend declarations in Blob template <typename> class BlobPtr; template <typename> class Blob; // needed for parameters in operator== template <typename T> bool operator==(const Blob<T>&, const Blob<T>&); template <typename T> class Blob { // each instantiation of Blob grants access to the version of // BlobPtr and the equality operator instantiated with the same type friend class BlobPtr<T>; friend bool operator==<T> (const Blob<T>&, const Blob<T>&); }; Blob<char> ca; // BlobPtr<char> and operator==<char> are friends of Blob<char> Blob<int> ia; // BlobPtr<int> and operator==<int> are friends of Blob<int>
- General and Specific Template Friendship
// forward declaration necessary to befriend a specific instantiation of a template template <typename T> class Pal; class C { // C is an ordinary, nontemplate class friend class Pal<C>; // Pal instantiated with class C is a friend to C // all instances of Pal2 are friends to C; // no forward declaration required when we befriend all instantiations template <typename T> friend class Pal2; }; template <typename T> class C2 { // C2 is itself a class template // each instantiation of C2 has the same instance of Pal as a friend friend class Pal<T>; // a template declaration for Pal must be in scope // all instances of Pal2 are friends of each instance of C2, prior declaration needed template <typename X> friend class Pal2; // Pal3 is a nontemplate class that is a friend of every instance of C2 friend class Pal3; // prior declaration for Pal3 not needed };
- whatever type is used to instantiate Bar is a friend
PS:even though a friend ordinarily must be a class or a function, it is okay for Bar to be instantiated with a built-in typetemplate <typename Type> class Bar { friend Type; // grants access to the type used to instantiate Bar };
类模板的别名
- typedef:只能给实例化的模板取别名
typedef Blob<string> StrBlob;
typedef Blob<T> TBlob; // 错误!
- using:c++ 11新特性
template<typename T> using twin = pair<T, T>;
twin<string> authors; // authors is a pair<string, string>
twin<int> win_loss; // win_loss is a pair<int, int>
template <typename T> using partNo = pair<T, unsigned>;
partNo<string> books; // books is a pair<string, unsigned>
partNo<Vehicle> cars; // cars is a pair<Vehicle, unsigned>
类模板中的静态成员
- 静态成员为 用同一个T实例化的所有对象 所共有
template <typename T> class Foo {
public:
static std::size_t count() { return ctr; }
private:
static std::size_t ctr;
};
// instantiates static members Foo<string>::ctr and Foo<string>::count
Foo<string> fs;
// all three objects share the same Foo<int>::ctr and Foo<int>::count members
Foo<int> fi, fi2, fi3;
- there must be exactly one definition of each static data member of a template class,但是一个模板有多个实例化对象,所以应该这么定义静态成员:这样当Foo被实例化为类型X时,其内的ctr也会被实例化为0
template <typename T>
size_t Foo<T>::ctr = 0; // define and initialize ctr
Foo<int> fi; // instantiates Foo<int> class and the static data member ctr
auto ct = Foo<int>::count(); // instantiates Foo<int>::count
ct = fi.count(); // uses Foo<int>::count
ct = Foo::count(); // error: which template instantiation?
模板形参
模板形参的scope
- 与普通name的scope类似,不过模板形参不可被reuse
typedef double A;
template <typename A, typename B> void f(A a, B b) {
A tmp = a; // tmp has same type as the template parameter A, not double
double B; // error: redeclares template parameter B
}
// error: illegal reuse of template parameter name V
template <typename V, typename V> // ...
默认形参
- 注意有默认值的形参放在右边
- 函数模板的栗子
// compare has a default template argument, less<T>
// and a default function argument, F()
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;
}
bool i = compare(0, 42); // uses less; i is -1
// result depends on the isbns in item1 and item2
Sales_data item1(cin), item2(cin);
bool j = compare(item1, item2, compareIsbn);
- 类模板的栗子
template <class T = int> class Numbers { // by default T is int
public:
Numbers(T v = 0): val(v) { }
private:
T val;
};
Numbers<long double> lots_of_precision;
Numbers<> average_precision; // empty <> says we want the default type
模板声明
- 栗子
// declares but does not define compare and Blob
template <typename T> int compare(const T&, const T&);
template <typename T> class Blob;
Typename
- 用T::可能得到一个类型或一个静态成员
- 不使用模板时,编译器已知T,所以会知道得到的是类型还是静态成员(比如编译器知道string::size_type是一个类型)
- 使用模板时,若T还没有实例化,则编译器不知道像 T::size_type * p; 这样的语句表达什么含义,它会默认T::得到的是静态成员,从而把这句话理解成静态成员与p相乘;除非使用关键字typename
template <typename T> typename T::value_type top(const T& c) { if (!c.empty()) return c.back(); else return typename T::value_type(); }
Member Templates
定义
- 是模板的成员函数称为member template
性质
- member template不能是虚函数
nontemplate 类的 member template
- 栗子
class DebugDelete {
public:
DebugDelete(std::ostream &s = std::cerr): os(s) { }
// as with any function template, the type of T is deduced by the compiler
template <typename T> void operator()(T *p) const {
os << "deleting unique_ptr" << std::endl; delete p;
}
private:
std::ostream &os;
};
double* p = new double;
DebugDelete d; // an object that can act like a delete expression
d(p); // calls DebugDelete::operator()(double*), which deletes p
int* ip = new int;
// calls operator()(int*) on a temporary DebugDelete object
DebugDelete()(ip);
// destroying the the object to which p points
// instantiates DebugDelete::operator()<int>(int *)
unique_ptr<int, DebugDelete> p(new int, DebugDelete());
// destroying the the object to which sp points
// instantiates DebugDelete::operator()<string>(string*)
unique_ptr<string, DebugDelete> sp(new string,
DebugDelete());
template类的member template
- 栗子:注意 definition of a2 uses the already instantiated Blob<int> class
template <typename T> class Blob {
template <typename It> Blob(It b, It e);
// ...
};
template <typename T> // type parameter for the class
template <typename It> // type parameter for the constructor
Blob<T>::Blob(It b, It e): data(std::make_shared<std::vector<T>>(b, e)) {
}
int ia[] = {0,1,2,3,4,5,6,7,8,9};
vector<long> vi = {0,1,2,3,4,5,6,7,8,9};
list<const char*> w = {"now", "is", "the", "time"};
// instantiates the Blob<int> class
// and the Blob<int> constructor that has two int* parameters
Blob<int> a1(begin(ia), end(ia));
// instantiates the Blob<int> constructor that has
// two vector<long>::iterator parameters
Blob<int> a2(vi.begin(), vi.end());
// instantiates the Blob<string> class and the Blob<string>
// constructor that has two (list<const char*>::iterator parameters
Blob<string> a3(w.begin(), w.end());
管理实例
explicit instantiation
- 作用:避免instantiating the same template in multiple files,因为实例化是有overhead的
- 两种用句:在文件A写了definition,在文件B写了declaration,则B可以用A中的模板实例;编译器看见definition才会生成code
// instantion declaration and definition
extern template class Blob<string>; // declaration
template int compare(const int&, const int&); // definition
- 栗子:Application.o中会包含Blob instantiated with int(包括initializer_list and copy constructors),但不会有对Blob<string>和compare的实例化(即使使用了它们),因为有declaration表明在其他文件中有对它们的实例化,所以没必要花overhead在这个文件中再去实例化了;templateBuild.o中会包含compare instantiated with int和Blob instantiated with string;连接器会link templateBuild.o with Application.o
// Application.cc
// these template types must be instantiated elsewhere in the program
extern template class Blob<string>;
extern template int compare(const int&, const int&);
Blob<string> sa1, sa2; // instantiation will appear elsewhere
// Blob<int> and its initializer_list constructor instantiated in this file
Blob<int> a1 = {0,1,2,3,4,5,6,7,8,9};
Blob<int> a2(a1); // copy constructor instantiated in this file
int i = compare(a1[0], a2[0]); // instantiation will appear elsewhere
// templateBuild.cc
template int compare(const int&, const int&);
template class Blob<string>; // instantiates all members of the class template
- 使用注意
- an instantiation definition for a class template instantiates all the members of that template(所以explicit instantiation 只能用于 types that can be used with all the members of that template)
- there must be an explicit instantiation definition somewhere in the program for every instantiation declaration
模板参数推断
执行conversion还是创造新的实例?
- 以下情况执行conversion,其他情况会创造新实例/报错
- 传参时 top-level consts in either the parameter or the argument are ignored,即 T*/& const 可指向T
- const conversion:形参/左边是a reference (or pointer) to a const时,实参/右边可以是非const对象
- array-to-pointer conversion:实参/右边是array时,形参/左边可以是指向array第一个元素的指针(前提:形参不是引用)
- function-to-pointer conversion:实参/右边是function时,形参/左边可以是指向function的指针(前提:形参不是引用)
PS:这些都是指被指代为T的类型的转换,normal type还是使用原来的类型转换规则,即各种conversion都可以执行,所以下面的第二块代码才可以converts f to ostream&
- 栗子
template <typename T> T fobj(T, T); // arguments are copied
template <typename T> T fref(const T&, const T&); // references
string s1("a value");
const string s2("another value");
fobj(s1, s2); // calls fobj(string, string); const is ignored
fref(s1, s2); // calls fref(const string&, const string&)
// uses premissible conversion to const on s1
int a[10], b[42];
fobj(a, b); // calls f(int*, int*)
fref(a, b); // error: array types don't match
template <typename T> ostream &print(ostream &os, const T &obj) {
return os << obj;
}
print(cout, 42); // instantiates print(ostream&, int)
ofstream f("output");
print(f, 10); // uses print(ostream&, int); converts f to ostream&
无法进行类型推断时需显示声明
- 栗子
// T1 cannot be deduced: it doesn't appear in the function parameter list
template <typename T1, typename T2, typename T3>
T1 sum(T2, T3);
auto val3 = sum<long long>(i, lng); // long long sum(int, long)
- 注意:explicit template argument may be omitted only for the trailing (right -most) parameters;声明的顺序与template<>中的顺序相同
// poor design: users must explicitly specify all three template parameters
template <typename T1, typename T2, typename T3>
T3 alternative_sum(T2, T1);
// error: can't infer initial template parameters
auto val3 = alternative_sum<long long>(i, lng);
// ok: all three parameters are explicitly specified
auto val2 = alternative_sum<long, int, long long>(i, lng);
- 显示声明后,可以执行所有种类的conversion
long lng;
compare(lng, 1024); // error: template parameters don't match
compare<long>(lng, 1024); // ok: instantiates compare(long, long)
compare<int>(lng, 1024); // ok: instantiates compare(int, int)
Trailing Return Types
// a trailing return lets us declare the return type after the parameter list is seen
template <typename It>
auto fcn(It beg, It end) -> decltype(*beg) {
// process the range
return *beg; // return a reference to an element from the range
}
Type Transformation Library
- 操作一览:
- 特点
- If it is not possible (or not necessary) to transform the template’s parameter, the type member is the template parameter type itself;比如:若T是一个指针,则remove_pointer<T>::type是T指向的类型,若T不是指针,则type is the same type as T
- each template has a public member named type that represents a type
- remove_reference:类模板;用T&实例化remove_reference模板后,会得到一个类,其中有一个public成员叫做type,是T类型;比如remove_reference<int&>的type成员是int
// must use typename to use a type member of a template parameter
template <typename It>
auto fcn2(It beg, It end) -> typename remove_reference<decltype(*beg)>::type {
// process the range
return *beg; // return a copy of an element from the range
}
函数指针相关
template <typename T> int compare(const T&, const T&);
// pf1 points to the instantiation int compare(const int&, const int&)
int (*pf1)(const int&, const int&) = compare;
// overloaded versions of func; each takes a different function pointer type
void func(int(*)(const string&, const string&));
void func(int(*)(const int&, const int&));
func(compare); // error: which instantiation of compare?
// ok: explicitly specify which version of compare to instantiate
func(compare<int>); // passing compare(const int&, const int&)
形参类型推断
- 注意:normal reference binding rules apply;consts are low level, not top level
- 形参是T&时:实参必须是左值;实参是const则T是low-level const
template <typename T> void f1(T&); // argument must be an lvalue
// calls to f1 use the referred-to type of the argument as the template parameter type
f1(i); // i is an int; template parameter T is int
f1(ci); // ci is a const int; template parameter T is const int
f1(5); // error: argument to a & parameter must be an lvalue
- 形参是const T&时:实参可以是任何值;实参是const时T不会带const(因为T前已经有一个const了)
template <typename T> void f2(const T&); // can take an rvalue
// parameter in f2 is const &; const in the argument is irrelevant
// in each of these three calls, f2's function parameter is inferred as const int&
f2(i); // i is an int; template parameter T is int
f2(ci); // ci is a const int, but template parameter T is int
f2(5); // a const & parameter can be bound to an rvalue; T is int
- 形参是T&&时:实参可以是任何值;若实参是左值,则T会被推断为该实参的引用:比如实参是int时,T会被推断为int&
template <typename T> void f3(T&&);
f3(42); // argument is an rvalue of type int; template parameter T is int
f3(i); // argument is an lvalue; template parameter T is int&
f3(ci); // argument is an lvalue; template parameter T is const int&
PS:间接定义(比如通过type aliase或上面一条中所说的形参推断)引用的引用时会发生引用折叠:X& &, X& &&, and X&& & all collapse to type X&;X&& && collapses to X&&
深入理解std::move
move的定义
template <typename T>
typename remove_reference<T>::type&& move(T&& t) {
return static_cast<typename remove_reference<T>::type&&>(t);
}
使用move的栗子
string s1("hi!"), s2;
s2 = std::move(string("bye!")); // ok: moving from an rvalue
s2 = std::move(s1); // ok: but after the assigment s1 has indeterminate value
- std::move(string("bye!")) 解析
- T 被推断为 string
- 用 string 实例化 remove_reference,生成的类的type成员是 string
- 返回 string&&(因为形参t本来就是string&&类型,所以static_cast does nothing)
- std::move(s1) 解析
- T 被推断为 string&
- 用 string& 实例化 remove_reference,生成的类的type成员是 string
- 返回 string&&(因为形参t是string&类型,所以static_cast把左值引用转换为了右值引用)
完美转发
定义
- 一个函数把它的部分实参传给另一个函数,保证实参们的类型不变
- 主要作用:保证实参是右值时,形参也是右值
不完美转发的栗子
- j被传给flip1时,T1被推断为了int,所以f中v2是绑定的flip1中的t1,但t1是j的拷贝而非引用
template <typename F, typename T1, typename T2>
void flip1(F f, T1 t1, T2 t2) {
f(t2, t1);
}
void f(int v1, int &v2) {
cout << v1 << " " << ++v2 << endl;
}
flip1(f, j, 42); // f called through flip1 leaves j unchanged
完美转发的实现尝试
- 使用右值引用作为形参:使用引用可以使const性质被完美转发;使用右值引用可以使左/右值性质被完美转发
- 栗子: j被传给flip2时,T1被推断为了int&
template <typename F, typename T1, typename T2>
void flip2(F f, T1 &&t1, T2 &&t2) {
f(t2, t1);
}
flip2(f, j, 42);
- 问题:当被调用的函数形参是右值引用时失败,因为右值引用不能绑定到左值上
void g(int &&i, int& j) {
cout << i << " " << j << endl;
}
flip2(g, i, 42); // error: can't initialize int&& from an lvalue
完美转发的实现方法(解决尝试的问题):std::forward
- 关于forward:return type of forward<T> is T&&
- 栗子
template <typename F, typename T1, typename T2>
void flip(F f, T1 &&t1, T2 &&t2) {
f(std::forward<T2>(t2), std::forward<T1>(t1));
}
flip(g, i, 42); // i will be passed to g as an int& and 42 will be passed as an int&&
模板与重载
基本规则
- 每个被实例化的函数都可以参与重载
- 普通函数支持为参数进行所有类型的conversion(见“补充:conversions”),模板实例化而成的函数只支持为参数进行部分类型的conversion(见“执行conversion还是创造新的实例?”)
- 若有多个函数provide an equally good match,则:
- 若只有一个是nontemplate function,选它
- 若无nontemplate function且one of the templates is more specialized than any of the others,选它
- 其他情况:报错,ambiguous call
栗子一:模板之间的选择
- candidate 1:general
template <typename T> string debug_rep(const T &t) {
ostringstream ret; // see § 8.3 (p. 321)
ret << t; // uses T's output operator to print a representation of t
return ret.str(); // return a copy of the string to which ret is bound
}
- candidate 2:指针(注意这个函数不能用于char,因为IO library defines a version of
the << for char values and that version of << assumes the pointer denotes a null-terminated character array, and prints the contents of the array, not its address)
// print pointers as their pointer value, followed by the object to which the pointer points
// NB: this function will not work properly with char*
template <typename T> string debug_rep(T *p) {
ostringstream ret;
ret << "pointer: " << p; // print the pointer's own value
if (p)
ret << " " << debug_rep(*p); // print the value to which p points
else
ret << " null pointer"; // indicate that the p is null
return ret.str(); // return a copy of the string to which ret is bound
}
-
调用函数尝试1
string s("hi"); cout << debug_rep(s) << endl;
- 只能调用candidate 1
-
调用函数尝试2
string s("hi"); cout << debug_rep(&s) << endl;
-
两个candidate都会generate viable instantiations:
- candidate 1会产生 debug_rep(const string* &),T=string*,requires a conversion of the plain pointer to a pointer to const
- candidate 2会产生 debug_rep(string*),T=string,exact match
所以调用candidate 2
-
-
调用函数尝试3
const string *sp = &s; cout << debug_rep(sp) << endl;
- 两个candidate都会generate viable instantiations:
- candidate 1会产生 debug_rep(const string* &),T=const string*,exact match
- candidate 2会产生 debug_rep(const string*),T=const string,exact match
因为candidate 2更specialized(只能be called on pointer types),所以选它
- 两个candidate都会generate viable instantiations:
栗子二:模板与非模板的选择
- candidate 3:普通函数
string debug_rep(const string &s) {
return '"' + s + '"';
}
- 调用函数尝试4
string s("hi"); cout << debug_rep(s) << endl;
-
有two equally good viable functions
- candidate 1会产生debug_rep<string>(const string&),T=string
- candidate 3
调用普通函数candidate 3
-
- 调用函数尝试5:实参是字符串字面量
cout << debug_rep("hi world!") << endl;
-
有三个可能
- debug_rep(const T&), with T bound to char[10]:exact match
- debug_rep(T*), with T bound to const char:exact match(需要conversion from array to pointer,但这个conversion被considered as an exact match for function-matching)
- debug_rep(const string&), which requires a user-defined conversion(from const char* to string)
调用更specialized的candidate 2
-
变参模板
一些定义
- 变参模板:a template function or class that can take a varying number of parameters
- 参数包:变参模板的参数们
- 模板参数包:模板的参数包
- 函数参数包:函数的参数包
// Args is a template parameter pack; rest is a function parameter pack // Args represents zero or more template type parameters // rest represents zero or more function parameters template <typename T, typename... Args> void foo(const T &t, const Args& ... rest); int i = 0; double d = 3.14; string s = "how now brown cow"; foo(i, s, 42, d); // three parameters in the pack foo(s, 42, "hi"); // two parameters in the pack foo(d, s); // one parameter in the pack foo("hi"); // empty pack // 以上四个调用会让编译器生成如下四个实例 void foo(const int&, const string&, const int&, const double&); void foo(const string&, const int&, const char[3]&); void foo(const double&, const string&); void foo(const char[3]&);
sizeof...
- returns a constant expression
- does not evaluate its argument
template<typename ... Args> void g(Args ... args) {
cout << sizeof...(Args) << endl; // number of type parameters
cout << sizeof...(args) << endl; // number of function parameters
}
栗子
// function to end the recursion and print the last element
// this function must be declared before the variadic version of print is defined
template<typename T>
ostream &print(ostream &os, const T &t) { // ①
return os << t; // no separator after the last element in the pack
}
// this version of print will be called for all but the last element in the pack
template <typename T, typename... Args>
ostream &print(ostream &os, const T &t, const Args&... rest) { // ②
os << t << ", "; // print the first argument
return print(os, rest...); // recursive call; print the other arguments
}
- print(cout, i, s, 42); 的执行过程:② print(cout, i, s, 42) -> ② print(cout, s, 42) -> ① print(cout, 42)【此时①和② provide equally good matches,但①更specialized】
使用函数来expand pack
// call debug_rep on each argument in the call to print
template <typename... Args>
ostream &errorMsg(ostream &os, const Args&... rest) {
// print(os, debug_rep(a1), debug_rep(a2), ..., debug_rep(an)
return print(os, debug_rep(rest)...);
}
// passes the pack to debug_rep; print(os, debug_rep(a1, a2, ..., an))
print(os, debug_rep(rest...)); // error: no matching function to call
转发参数包
class StrVec {
public:
template <class... Args> void emplace_back(Args&&...);
};
template <class... Args>
inline void StrVec::emplace_back(Args&&... args) {
chk_n_alloc(); // reallocates the StrVec if necessary
alloc.construct(first_free++,
std::forward<Args>(args)...); // 相当于调用 std::forward<Ti>(ti),其中Ti是ti的类型
}
svec.emplace_back(10, 'c'); // adds cccccccccc as a new last element
// 传给construct的后两个参数是 std::forward<int>(10), std::forward<char>(c)
svec.emplace_back(s1 + s2); // uses the move constructor
// 传给construct的第二个参数是 std::forward<string>(string("the end")),construct会把它完美转发给string类的对象,由于完美转发会保持右值性质,所以调用的是string的移动构造函数
模板特化
含义
- 使用模板时会遇到一些特殊的类型需要特殊处理,不能直接使用当前的模板函数/类,所以此时我们就需要对该类型特化出一个模板函数/类(就是写出一个模板函数/类专门给该类型使用)
函数模板的特化
- 需要模板特化的栗子
// first version; can compare any two types template <typename T> int compare(const T&, const T&); // ① // second version to handle string literals template<size_t N, size_t M> int compare(const char (&)[N], const char (&)[M]); // ② const char *p1 = "hi", *p2 = "mom"; compare(p1, p2); // calls the first template compare("hi", "mom"); // calls the template with two nontype parameters
- 由于version 1无法处理character pointers(因为比较char要使用strcmp,而不是像其他类型直接作比较就可以),所以将其重载为version 2
- 但version 2只能处理string literal或array,不能处理指向char型数组的指针,所以compare(p1, p2)实际上调用的还是version 1
- 所以,需要为version 1做模板特化
- 模板特化的栗子
// special version of compare to handle pointers to character arrays template <> int compare(const char* const &p1, const char* const &p2) { // ③ return strcmp(p1, p2); }
- const char* const&的含义:a reference to a const pointer to const char(因为我们想让T=const char*)
- 模板特化的本质
- 模板特化是替编译器执行了一次实例化,特化的模板并不是an overloaded instance of the function name;即:specializations instantiate a template and they do not overload it,因此specializations do not affect function matching
- 执行 compare(p1, p2) 时:
- 若代码里是①、②、③:在匹配函数时,candidate是①和②这两个模板,由于②不支持指针,所以①会被调用,最终使用③这个实例
- 若代码里是①、②和a version of compare(记为④) that takes character pointers as a plain nontemplate function (rather than as a specialization of the template) :在匹配函数时,candidate是①、②这两个模板和④这个普通函数,④会被调用
类模板的特化
- 完全特化
- 栗子:define a specialization of hash for Sales_data:template<>表明在定义一个fully specialized template;the template we’re specializing is named hash and the specialized version is hash<Sales_data>;to enable users of Sales_data to use the specialization of hash, we should define this specialization in the Sales_data header
// open the std namespace so we can specialize std::hash namespace std { template <> // we're defining a specialization with struct hash<Sales_data> { // the type used to hash an unordered container must define these types typedef size_t result_type; typedef Sales_data argument_type; // by default, this type needs == size_t operator()(const Sales_data& s) const; // our class uses synthesized copy control and default constructor }; size_t hash<Sales_data>::operator()(const Sales_data& s) const { return hash<string>()(s.bookNo) ^ hash<unsigned>()(s.units_sold) ^ hash<double>()(s.revenue); } } // close the std namespace; note: no semicolon after the close curly // 因为hash<Sales_data> uses the private members of Sales_data,所以要声明为友元 template <class T> class std::hash; // needed for the friend declaration class Sales_data { friend class std::hash<Sales_data>; // other members as before }; // uses hash<Sales_data> and Sales_data operator== unordered_multiset<Sales_data> SDset;
- 部分特化
- 含义:specify some, but not all, of the template parameters or some, but not all, aspects of the parameters
- 本质:部分特化后的类模板仍然是一个模板,we must supply arguments for those template parameters that are not fixed by the specialization
- 注意:部分特化的模板的参数是原模板参数的特殊化(比如从T到T&),所以部分特化后,模板参数的个数与原模板是相同的
- 栗子一:specify some, but not all, of the template parameters
// original, most general template template <class T> struct remove_reference { typedef T type; }; // partial specializations that will be used for lvalue and rvalue references template <class T> struct remove_reference<T&> { // lvalue references typedef T type; }; template <class T> struct remove_reference<T&&> { // rvalue references typedef T type; }; int i; // decltype(42) is int, uses the original template remove_reference<decltype(42)>::type a; // decltype(i) is int&, uses first (T&) partial specialization remove_reference<decltype(i)>::type b; // decltype(std::move(i)) is int&&, uses second (i.e., T&&) partial specialization remove_reference<decltype(std::move(i))>::type c;
- 栗子二:specify some, but not all, aspects of the parameters
template <typename T> struct Foo { Foo(const T &t = T()): mem(t) { } void Bar() { /* ... */ } T mem; // other members of Foo }; template<> // we're specializing a template void Foo<int>::Bar() { // we're specializing the Bar member of Foo<int> // do whatever specialized processing that applies to ints } Foo<string> fs; // instantiates Foo<string>::Foo() fs.Bar(); // instantiates Foo<string>::Bar() Foo<int> fi; // instantiates Foo<int>::Foo() fi.Bar(); // uses our specialization of Foo<int>::Bar()
使用注意
- in order to specialize a template, a declaration for the original template must be in scope
- templates and their specializations should be declared in the same header file
- 只有类模板可以部分特化
补充:conversions
隐式转换
- top-level const conversion:传参时,top-level consts in either the parameter or the argument are ignored;即const T 和 T可以自动互转
int i = 0; int *const p1 = &i; // we can't change the value of p1; const is top-level
- const conversion:形参/左边是a reference (or pointer) to a const时,实参/右边可以是非const对象;if T is a type, we can convert a pointer or a reference to T into a pointer or reference to const T
int i; const int &j = i; // convert a nonconst to a reference to const int const int *p = &i; // convert address of a nonconst to the address of a const
- array-to-pointer conversion:实参/右边是array时,形参/左边可以是指向array第一个元素的指针(前提:形参不是引用)
int ia[10]; // array of ten ints int* ip = ia; // convert ia to a pointer to the first element
- function-to-pointer conversion:实参/右边是function时,形参/左边可以是指向function的指针(前提:形参不是引用)
- arithmetic conversion:见第4章
- pointer conversion
- constant integral value of 0 and the literal nullptr can be converted to any pointer type
- pointer to any nonconst type can be converted to void*
- pointer to any type can be converted to a const void*
- conversion to bool:if the pointer or arithmetic value is zero, the conversion yields false; any other value yields true
- derived-to-base conversion:见第15章
- user-defined conversion:见第14章
显示转换
各种cast:见第4章