C++ primer 第十六章-模板与泛型编程

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
      1. 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>
      
      1. 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
      };
      
      1. whatever type is used to instantiate Bar is a friend
      template <typename Type> class Bar {
        friend Type; // grants access to the type used to instantiate Bar
      };
      
      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 type

类模板的别名

  • 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,其他情况会创造新实例/报错
    1. 传参时 top-level consts in either the parameter or the argument are ignored,即 T*/& const 可指向T
    2. const conversion:形参/左边是a reference (or pointer) to a const时,实参/右边可以是非const对象
    3. array-to-pointer conversion:实参/右边是array时,形参/左边可以是指向array第一个元素的指针(前提:形参不是引用)
    4. 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

  • 操作一览:
  • 特点
    1. 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
    2. 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!")) 解析
    1. T 被推断为 string
    2. 用 string 实例化 remove_reference,生成的类的type成员是 string
    3. 返回 string&&(因为形参t本来就是string&&类型,所以static_cast does nothing)
  • std::move(s1) 解析
    1. T 被推断为 string&
    2. 用 string& 实例化 remove_reference,生成的类的type成员是 string
    3. 返回 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:

      1. candidate 1会产生 debug_rep(const string* &),T=string*,requires a conversion of the plain pointer to a pointer to const
      2. 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:
      1. candidate 1会产生 debug_rep(const string* &),T=const string*,exact match
      2. candidate 2会产生 debug_rep(const string*),T=const string,exact match

    因为candidate 2更specialized(只能be called on pointer types),所以选它

栗子二:模板与非模板的选择

  • candidate 3:普通函数
string debug_rep(const string &s) {
  return '"' + s + '"';
}
  • 调用函数尝试4
    string s("hi");
    cout << debug_rep(s) << endl;
    
    • 有two equally good viable functions

      1. candidate 1会产生debug_rep<string>(const string&),T=string
      2. candidate 3

      调用普通函数candidate 3

  • 调用函数尝试5:实参是字符串字面量
    cout << debug_rep("hi world!") << endl; 
    
    • 有三个可能

      1. debug_rep(const T&), with T bound to char[10]:exact match
      2. 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)
      3. 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
    1. constant integral value of 0 and the literal nullptr can be converted to any pointer type
    2. pointer to any nonconst type can be converted to void*
    3. 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章

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,937评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,503评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,712评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,668评论 1 276
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,677评论 5 366
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,601评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,975评论 3 396
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,637评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,881评论 1 298
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,621评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,710评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,387评论 4 319
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,971评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,947评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,189评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 44,805评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,449评论 2 342