目录
函数模板
模板类型参数 (579P)
非类型模板参数 (580P)
给函数模板 定义 inline 和 constexpr 关键字 (581P)
less 关键字 (581P)
模板编译 (582P)
模板的编译错误主要是在实例化过程中报告的 ( 582P)
类模板 (583P)
在类模板作用域内引用其它模板类型 (585P)
对于一个实例化了的类模板, 类模板的成员函数只有在程序使用该成员函数时才会实例化。(587P)
使用一个类模板类型时,在类模板自身的作用域中,可以直接使用模板名而不提供实参 (587P)
类模板与友元 (588P)
使用 using 声明为模板声明别名 (590P)
类模板的 static 成员 (591P)
模板类型参数的作用域
使用类的类型成员
函数模板默认实参
类模板默认实参
成员模板
普通 ( 非模板 )类的成员模板
类模板的成员模板
控制实例化
模板实参推断
类型转换和模板类型参数
用一个模板类型参数作用于一个函数中的多个参数类型(602Page)
在函数模板中使用非模板类型参数的类型 (602Page)
在调用函数模板时指定显式模板实参 (603 Page)
正常类型转换应用于显式指定的函数实参
尾置返回类型用于确定函数模板的返回类型(605Page)
能够进行类型转换的标准库模板类(605Page)
用一个函数模板初始化一个函数指针或者对一个函数指针赋值 (607Page)
从左值引用函数参数推断类型( 608Page)
从右值引用函数参数推断类型( 608Page)
- 注意: 在模板的定义中(不管是类模板还是函数模板),模板参数列表都不能为空。
模板参数列表中表示的是在某类或函数定义中需要用到的类型或值,当使用模板时,我们需要隐式地或显式地指定模板实参。当我们调用一个函数模板时, 编译器(通常)用函数实参类型来为我们推断模板的实参类型。示例程序:
template
int compare(const T &v1, const T &v2)
{
if (v1 < v2) return -1;
if (v2 < v1) return 1;
return 0;
}
int main()
{
cout << compare(11, 0) << endl; // T is int, 显式指定模板实参类型, 将其绑定在 模板参数上
cout << compare(151.5, 12.36) << endl; // T is double ,隐式指定模板实参类型, 将其绑定在 模板参数上
system("pause");
return 0;
}
编译器用模板实参的类型为我们实例化一个特定类型 版本的函数。当编译器实例化一个模板时, 它使用实际的模板实参类型代替对应的模板类型参数来创建出该模板的一个新“实例”。
上述的程序用两个不同的类型实例化了两个不同版本的 compare ,这些编译器生成的特定类型版本的函数通常被称为模板的实例( 有些书上也叫模板函数 )。
模板类型参数 ( 就像上述程序中 “ < T > ”)有什么用处呢?:
- 可以用来指定返回类型或函数的参数类型, 以及在函数体内用于变量声明或强制类型转换。
注意的是,声明每一个模板类型参数前都必须使用关键字 class 或 typename( 它们含有相同,可以同时互换使用)。
通常我们应该使用 typename 关键字来声明模板类型参数, 因为它比class 更加的直观,也能更加清楚地指出随后的名字是一个类型名称。
可以通过一个特定的类型名而非关键字class 或 typename 来给模板定义一个非类型参数, 非类型参数必须是一个常量表达式。
当一个模板被实例化时, 非类型参数被一个用户提供的或编译器推断出的值所代替, 从而允许编译器在编译时实例化模板。
有哪些类型可以作为非类型参数呢?
- bool、char、wchar_t、char16_t、char32_t、short、int、long、long long、
- 指向对象类型的指针
- 指向函数类型的指针
- 左值引用
绑定到非类型的整型参数的实参必须是一个常量表达式。 绑定到指针或引用的非类型参数的实参必须具有静态的生存期。
有哪些类型不可以作为非类型参数呢?
- double 、float、long double、类类型(比如:string)
- 我们不能用一个普通 (非 static) 局部变量或动态对象作为指针或引用非类型模板参数的实参。
template
int compare(const char(&p1)[N], const char(&p2)[M])
{
return strcmp(p1, p2);
}
int main()
{
cout << compare("hi", "mom") << endl;
system("pause");
return 0;
}
在模板定义内, 模板非类型参数必须是一个常量值。在需要常量表达式的地方, 都可以使用非类型模板参数, 例如:指定数组大小。
inline或 constexpr 说明符放在模板参数列表之后, 返回类型之前:
// ok: inline specifier follows the template parameter list
template inline T min(const T&, const T&);
// error: incorrect placement of the inline specifier
inline template T min(const T&, const T&);
记住编写泛型代码有两个重要原则:
- 模板中的函数形参是对const的引用
- 函数体中的条件判断仅使用<比较运算。
那为什么函数参数是对const的引用?
- 因为将函数的参数设定为 const 引用可以保证该函数可以用于不能拷贝的类型( 比如: unique_ptr 和 IO 类型)。
- 当该函数处理大的对象时,那么这种设计也会使函数运行得更快。
那为什么仅使用<比较运算?
- 如果编写代码时只使用<运算符, 我们就降低了该函数对要处理的类型的要求。这些类型必须支持<, 但支不支持 > 并不重要。
当编译器遇到一个模板定义时, 它并不生成代码。只有当我们实例化出模板的一个特定版本时, 编译器才会生成代码。
当我们使用 (而不是定义,是实例化)模板时, 编译器才生成代码, 这一特性影响了我们如何组织代码以及何时检测到错误。
为了生成一个实例化版本, 编译器需要有函数模板或类模板成员函数的定义代码。因此,与非模板代码不同, 模板的头文件通常既包括声明也包括定义。所以说 ,函数模板和类模板成员函数的定义通常放在头文件中。
练习16.1:
当调用一个函数模板时, 编译器会利用给定的函数实参来推断模板实参,它使用实际的模板实参来代替对应的模板参数来创建出模板的一个 “ 新实例 ”,也就是一个真正可以调用的函数, 这个过程称为实例化。
练习题16.3:
因为 Compare 函数中 使用的是 < 运算符进行操作的,需要类型 T 事先就定义该运算符, 但是我们的实际类型 Sales_data 它没有实现 < 运算符, 所以会发生错误
练习题16.4:
template
T find_V( T v1, T v2, const V &v3)
{
while (v1 != v2 && *v1 != v3)
{
++v1;
}
return v1;
}
int main()
{
vector v1{ 0,5,6,7,8 };
if (!v1.empty())
{
auto temp = find_V(v1.begin(), v1.end(),6);
if (temp == v1.end())
{
cout << "没有找到6" << endl;
}
else
cout << "找到6 " << endl;
}
list v2 = { "huang","chengt","tao" };
if (!v2.empty())
{
auto temp = find_V(v2.begin(), v2.end(), "sda");
if (temp == v2.end())
{
cout << "没有找到sda" << endl;
}
else
cout << "找到sda " << endl;
}
system("pause");
return 0;
}
练习题16.5:
template
constexpr void print( T &v1)
{
for (auto tt : v1)
{
cout << tt << " ";
}
cout << endl;
}
int main()
{
int v1[] = { 5,6,7,8,9,10 };
print(v1);
vector v2{ "huang","chengt","tao" };
print(v2);
system("pause");
return 0;
}
练习题16.6:
begin 函数是指向数组首元素的指针, 而 end 指向数组尾元素之后的指针。 当使用该函数时,可以很容易写出一个循环并且处理数组中的元素。
template
constexpr void print( T v1,T v2) { while (v1 != v2) { cout << *v1 << " "; ++v1; } cout << endl; } int main() { int v1[]{ 5,6,7,8,9,5 }; int *p = v1; auto tt = end(v1) - begin(v1); // 计算数组中的数量 int *v = &v1[tt]; // 指向数组尾元素下一位置的指针 print(p, v); system("pause"); return 0; }
练习题16.7:
template
constexpr int find_V( T v1, T v2)
{
int temp = 0;
while (v1!=v2)
{
++v1;
++temp;
}
return temp;
}
int main()
{
int v1[] = { 5,6,7,8,9,10 };
cout << "数组的大小为:" << find_V(begin(v1), end(v1)) << endl;
system("pause");
return 0;
}
练习题16.8:
因为大多数标准库容器的迭代器都定义了 == 和 !=, 但是它们大多数都没有定义< 运算符, 因此只要使用 迭代器 和 !=的习惯, 就不在意用的是哪种容器。
类模板与函数模板的不同之处是:
- 编译器不能为类模板推断模板参数类型。我们创建类模板的实例化时,必须在模板名后的尖括号中提供 额外的信息, 它用来替换模板参数的模板实参列表。
当我们使用一个类模板时,我们必须提供显式的模板实参列表, 它们被绑定到模板参数。 使用实际的模板实参来代替对应的模板参数来创建出模板的一个 “ 新实例 ”,也就是一个特定类型的模板类。
template class Blob
{
public:
typedef T value_type;
typedef typename std::vector::size_type size_type;
// constructors
Blob();
Blob(std::initializer_list 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); }
void push_back(T &&t) { data->push_back(std::move(t)); }
void pop_back();
// element access
T& back();
T& operator[](size_type i); // defined in § 14.5 (p. 566)
private:
std::shared_ptr> data;
// throws msg if data[i] isn't valid
void check(size_type i, const std::string &msg) const;
};
int main()
{
Blob ia; // empty Blob
Blob ia2 = { 0,1,2,3,4 }; // Blob with five elements
system("pause");
return 0;
}
当编译器从 Blob 模板实例化一个类时,它会重写Blob模板,会用给定的模板实参替换模板参数T的每个实例,在本例中是int。
对我们指定的每一种元素类型, 编译器都生成一个不同的类:
// 下面的定义实例化出两个不同的Blob类型 Blob
names; // Blob that holds strings Blob prices;// different element type
- 这两个定义会实例化出两个不同的类。names的定义创建了一个Blob类, 每个T都被替换为string。 prices的定义生成了另一个Blob类, T被替换为double .
- 所以说一个类模板的每个实例都形成一个独立的类。类型 Blob
与任何其他 Blob 类型都没有关联, 也不会对任何其他Blob 类型的成员有特殊访问权限。
类模板不是一个类型,而是一个模板。 需要给类模板提供显式模板实参,它才能是一个类型
- 类模板的每个实例都有其自己版本的成员函数。
- 因此,在定一个类模板的成员函数时,该成员函数具有和类模板相同的模板类型参数。
- 所以定义在类模板之外的成员函数必须以关键字 template 开始,紧接 类模板的模板类型参数列表。
template class Blob
{
public:
typedef T value_type;
typedef typename std::vector::size_type size_type;
// constructors
Blob();
Blob(std::initializer_list 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); }
void push_back(T &&t) { data->push_back(std::move(t)); }
void pop_back();
// element access
T& back();
T& operator[](size_type i); // defined in § 14.5 (p. 566)
private:
std::shared_ptr> data;
// throws msg if data[i] isn't valid
void check(size_type i, const std::string &msg) const;
};
template
Blob::Blob() : data(std::make_shared>()) { }
template
Blob::Blob(std::initializer_list il) :data(std::make_shared>(il)) { }
template
void Blob::check(size_type i, const std::string &msg) const
{
if (i >= data->size())
throw std::out_of_range(msg);
}
template
T& Blob::back()
{
check(0, "back on empty Blob");
return data->back();
}
template
T& Blob::operator[](size_type i)
{
// if i is too big, check will throw, preventing access to a nonexistent element
check(i, "subscript out of range");
return (*data)[i];
}
template void Blob::pop_back()
{
check(0, "pop_back on empty Blob");
data->pop_back();
}
int main()
{
Blob ia; // empty Blob
Blob ia2 = { 0,1,2,3,4 }; // Blob with five elements
Blob articles = { "a", "an", "the" };
system("pause");
return 0;
}
默认情况下,对于一个实例化了的类模板, 类模板的成员函数只有在程序使用该成员函数时才会实例化。
// instantiates Blob and the initializer_list constructor
Blob squares = {0,1,2,3,4,5,6,7,8,9};
// instantiates Blob::size() const
for (size_t i = 0; i != squares.size(); ++i)
squares[i] = i*i; // instantiates Blob::operator[](size_t)
上述的代码实例化了 Blob
- operator[]、size 和 接受 initializer_1ist
的构造函数。
如果一个成员函数没有被使用, 则它不会被实例化。成员函数只有在被用到时才进行实例化, 这一特性使得即使某种类型不能完全符合模板操作的要求, 我们仍然能用该类型实例化类。
- 当一个类包含一个 friend声明时,类和 friend 可以相互独立为模板,也可以不是。
- 如果一个类模板包含一个非模板友元,则该友元被授权为可以访问该模板的所有实例。、
- 如果友元自身是一个模板,该类模板可以授权给所有友元模板的实例,也可以只授权给特定实例。
因为模板不是一个类型, 所以我们不能定义一个 typedef 引用一个模板。例如:无法定义一个typedef引用Blob
template using twin = std::pair;
twin authors; // // authors is a pair
就像使用类模板一样,当我们使用twin时, 需要指出希望使用哪种特定类型的twin。
在下面的代码中,每个 Foo 的实例都有其自己的 static 成员实例。即, 对任意给定类型x, 该类型都有一个Foo
template class Foo
{
public:
static std::size_t count() { return ctr; }
// other interface members
private:
static std::size_t ctr;
// other implementation members
};
template
std::size_t Foo::ctr = 0; // static 成员一定要定义成模板,因为类模板的每个类型实例都有一个独有的 static 对象
// 注意,模板类的每个 static 数据成员必须有且仅有一个定义
std::size_t Foo::count() // 在类外定义 static 成员函数
{
return ctr;
}
int main()
{
// instantiates static members Foo::ctr and Foo::count
Foo fs;
// all three objects share the same Foo::ctr and Foo::count members
Foo fi, fi2, fi3;
Foo fi; // instantiates Foo class and the static data member ctr
auto ct = Foo::count(); // instantiates Foo::count
ct = fi.count(); // uses Foo::count
ct = Foo::count(); // error: which template instantiation?
system("pause");
return 0;
}
- 记住 ,如果一个类模板中有静态成员,那么该模板的每一个实例都有其自己的 static 成员实例。
- 如果该模板的实例的类型都一样, 那么同类型实例化的对象都共享 static 成员
注意:类似任何其他成员函数, 一个static成员函数只有在使用时才会实例化。
typedef double A;
template 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
}
首先呢,模板类型参数的作用域跟普通的作用域规则一样。一个 模板类型参数的可用范围是在其声明之后,然后直到模板声明或定义结束之前。需要注意的是,模板类型参数也会隐藏外层作用域中声明的相同名称。 但是,在模板的定义中不能重新声明模板类型参数名。 上面的代码就可以看出来。
由于参数名不能重用, 所以一个模板类型参数名在一个特定模板参数列表中只能出现一次:
//错误:非法重用模板参数名v
template
默认情况下, C++语言假定通过作用域运算符访问的名字不是一个类型。因此, 如果我们希望使用一个模板类型参数的类型成员, 必须显示使用 typename 来告诉编译器该名字是一个类型。
template
typename T::value_type top(const T& c)
{
if (!c.empty())
return c.back();
else
return typename T::value_type();
}
注意: 当我们希望通知编译器一个名字表示类型时, 必须使用关键字 typename, 而不能使用class 来指出。
可以为函数模板 和 类模板提供默认模板实参。
// compare has a default template argument, less and a default function argument, F()
template >
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;
}
与函数默认实参一样, 对于一个模板参数, 只有当它右侧的所有参数都有默认实参时, 它才可以有默认实参。
template class Numbers
{ // by default T is int
public:
Numbers(T v = 0) : val(v) { }
private:
T val;
};
int main()
{
Numbers lots_of_precision;
Numbers<> average_precision; // 空的 <> 表示我们希望使用默认的模板实参
system("pause");
return 0;
}
记住: 无论何时使用类模板,都需要在模板名之后接上尖括号。
一个类 ( 无论是普通类或类模板) 中可能具有本身就是模板的成员函数。这些成员称为成员模板。成员模板可能不是虚函数。
// 函数对象类,它在给定指针上调用delete
class DebugDelete
{
public:
DebugDelete(std::ostream &s = std::cerr) : os(s) { }
// 与任何函数模板一样,T的类型由编译器推断
template void operator()(T *p) const
{
os << "deleting unique_ptr" << std::endl;
delete p;
}
private:
std::ostream &os;
};
int main()
{
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);
system("pause");
return 0;
}
我们也可以在类模板的定义中单独定义个成员模板,在此情况下,类和成员都有自己独立的模板参数。
构造函数也可以定义为模板。
template class Blob
{
public:
template Blob(It b, It e); // 构造函数定义为模板
};
// 当我们在类模板外定义一个成员模板时,必须同时为类模板和成员模板提供模板参数列表。类模板的参数列表在前,后跟成员自己的模板参数列表
template // type parameter for the class
template // type parameter for the constructor
Blob::Blob(It b, It e) :data(std::make_shared>(b, e)) { }
int main()
{
int ia[] = { 0,1,2,3,4,5,6,7,8,9 };
vector vi = { 0,1,2,3,4,5,6,7,8,9 };
list w = { "now", "is", "the", "time" };
// 实例化 Blob 类及其接受两个 int*参数的构造函数
Blob a1(std::begin(ia), std::end(ia));
// 实例化 Blob 类的接受两个vector::iterator的构造函数
Blob a2(vi.begin(), vi.end());
// 实例化 Blob 及其接受两个list: :iterator参数的构造函数
Blob a3(w.begin(), w.end());
system("pause");
return 0;
}
当模板被使用时才会进行实例化这一特性意味着, 相同的实例可能出现在多个目标文件中。当两个或多个独立编译的源文件使用了相同的模板, 并提供了相同的模板实参时, 每个源文件中就都会有该模板的一个实例。
我们可以通过使用 显式实例化 来避免在多个源文件中实例化相同模板的额外开销,
当编译器看到 extern 模板声明时,它不会为该源文件中的生成实例化代码。将实例化声明为 extern 是一种承诺,即在程序其他位置有该实例化的一个非extern声明(定义)。对于一个给定的实例化版本,可能有多个extern声明,但该实例化必须只有一个定义。
我们已经看到,默认情况下,编译器使用调用中的函数实参来确定函数模板的模板参数。以函数实参确定模板实参的过程称为模板实参推断。在模板实参推导过程中,编译器使用调用中的实参类型来查找模板实参,该模板实参生成与给定调用最匹配的函数版本。
与非模板函数一样, 我们在一次调用中传递给函数模板的实参会被用来初始化函数的形参。如果一个函数形参的类型使用了模板类型参数, 那么它采用特殊的初始化规则。 只有数量非常有限的类型转换会自动应用于这些实参。编译器通常不是对实参进行类型转换, 而是生成一个新的模板实例。
与往常一样, 顶层const无论是在形参中还是在实参中, 都会被忽略。在其他类型转换中,能在调用中应用于函数模板如下:
- const 转换
- 数组或函数指针的转换
注意:如算术转换、派生类向基类的转换以及用户定义的转换, 都不能应用于函数模板。
template T fobj(T, T)// 实参被拷贝
{
cout << "Called :: fobj " << endl;
return 0;
}
template T fref(const T&, const T&) // references
{
cout << "Called :: fref " << endl;
return 0;
}
int main()
{
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
system("pause");
return 0;
}
template
int compare(const T& v1, const T& v2)// 实参被拷贝
{
if (v1 < v2) return -1;
if (v2 < v1) return 1;
return 0;
}
int main()
{
long lng;
compare(lng, 1024); // error: cannot instantiate compare(long, int)
system("pause");
return 0;
}
因为只有几种类型转换可以应用到函数模板,所以传递给 compare 函数的两个实参必须要有相同的类型。
如果希望允许对函数实参进行正常的类型转换, 我们可以为函数模板定义两个不同类型的模板类型参数,可以把上面的程序改为如下:
// 实参类型可以不同,但必须兼容
template
int compare(const A& v1, const B& v2)
{
if (v1 < v2) return -1;
if (v2 < v1) return 1;
return 0;
}
int main()
{
long lng;
cout << "输出结果为:" << compare(lng, 1024) << endl; // ok: calls flexibleCompare(long, int)
system("pause");
return 0;
}
注意:当然,这些类型必须存在一个 < 操作符来比较这些类型的值。如果说是一个类类型,但它并没有重载 “ < ” 运算符, 那么错误。
函数模板可以具有使用普通类型 (即,不涉及模板类型参数的类型)定义的参数。这些实参不进行特殊处理; 它们通常被转换为形参的对应类型:
template std::ostream print(ostream &os, const T &obj)
{
return os << obj;
}
int main()
{
print(cout, 42); // instantiates print(ostream&, int)
std::ofstream f("output");
print(f, 10); // uses print(ostream&, int); converts f to ostream&
system("pause");
return 0;
}
注意:如果函数参数类型不是模板类型参数, 则对实参进行正常的类型转换。
有时候编译器不能推断出模板实参的类型,如下两种情况最常见:
- 我们希望允许用户控制模板实例化。
- 当函数返回类型与参数列表中使用的任何函数返回类型不同时
比如下面这个程序:
// 编译器无法推断T1 的类型,它未出现在函数形参列表中
template
T1 sum(T2 v1, T3 v2)
{
cout << "输出参数结果:" << v1 << " ," << v2 << endl;
return 0;
}
int main()
{
// T1 的实参类型是显式指定的, T2和T3是从函数实参类型推断而来的
auto val3 = sum(100, 2.5); // long long sum(int, long)
system("pause");
return 0;
}
template
T3 alternative_sum(T2 v1, T3 v2)
{
cout << "输出参数结果:" << v1 << " ," << v2 << endl;
return 0;
}
int main()
{
// error: can't infer initial template parameters
auto val3 = alternative_sum(i, lng);
// ok: all three parameters are explicitly specified
auto val2 = alternative_sum(i, lng);
system("pause");
return 0;
}
// 尾置返回允许我们在参数列表之后声明返回类型
template
auto fcn(It beg, It end) -> decltype(*beg)
{
// process the range
return *beg; // return a reference to an element from the range
}
int main()
{
vector vi = { 1,2,3,4,5 };
vector ca = { "hi", "bye" };
auto &i = fcn(vi.begin(), vi.end()); // fcn should return int&
auto &s = fcn(ca.begin(), ca.end()); // fcn should return string&
system("pause");
return 0;
}
当我们用一个函数模板初始化一个函数指针或对一个函数指针赋值时, 编译器使用指针的类型来推断模板实参。 如果编译器不能从函数指针类型确定模板实参, 则产生错误。
template int compare(const T& v1, const T& v2)
{
cout << v1 << " , " << v2 << endl;
return 0;
}
// pf1 points to the instantiation int compare(const int&, const int&)
int(*pf1)(const int&, const int&) = compare;
int main()
{
pf1(10, 200);
system("pause");
return 0;
}
pf1中参数的类型决定了 的模板实参的类型。在本例中, T的模板实参类型为int。指针 pf1 是指向 compare 的 int 版本实例。
template void f(T &p);
如果一个函数的参数是模板类型参数的一个普通(左值)引用时 ( 即, 形如T& )。
那么我们可以为这样的函数模板传递哪些实参类型呢?
- 只能给该函数模板传递一个左值 ( 如,一个变量或 一个返回引用类型的表达式)。
- 传递给该函数模板的实参可以是const类型, 也可以不是。如果实参是const的, 则 模板类型参数推断为const类型
template void f1(T& v1)// argument must be an lvalue
{
cout << "输出 v1:" <
如果一个函数模板的参数的类型是const T&。
那么我们可以为这样的函数模板传递哪些实参类型呢?
- 可以传递给它任何类型的实参 —— 一个对象 ( const 或 非const )、一个临时对象或是一个字面常量值。
注意:函数模板的参数本身是 const 时, T的类型推断的结果不会是一个const类型。因为 const 已经是函数参数类型的一部分;因此,如果函数模板的参数本身是 const 时,它不会成为推导模板参数类型的一部分。
template void f2(const T& v1) // can take an rvalue
{
cout << "输出 v1:" << v1 << endl;
}
int main()
{
int i = 9; const int ci = 1200;
// f2中的参数是 const &; 实参是否是 const 是无关紧要的
// 在这三个调用中,f2的函数参数都被推断为 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
system("pause");
return 0;
}
当一个函数参数是一个右值引用 (即,形如T&&)时。
那么我们可以为这样的函数模板传递哪些实参类型呢?
- 可以传递给它一个右值 ( 当我们这样做时, 类型推断过程类似普通左值引用函数参数的推断过程。推断出的T的类型是该右值实参的类型 )
template void f2( T&& v1) // can take an rvalue
{
cout << "输出 v1:" << v1 << endl;
}
int main()
{
f2(42); // argument is an rvalue of type int; template parameter T is int
system("pause");
return 0;
}