C++ Primer 第五版 ——《第十六章 》“ 模板与泛型编程 ” 学习笔记

Primer 2 同时被 2 个专栏收录
16 篇文章 0 订阅

目录

函数模板

模板类型参数 (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 <typename T>
int compare(const T &v1, const T &v2)
{
	if (v1 < v2) return -1;
	if (v2 < v1) return 1;
	return 0;
}
int main()
{
	cout << compare<int>(11, 0) << endl; // T is int, 显式指定模板实参类型, 将其绑定在 模板参数上
	cout << compare(151.5, 12.36) << endl; // T is double ,隐式指定模板实参类型, 将其绑定在 模板参数上
	system("pause");
	return 0;
}

编译器用模板实参的类型为我们实例化一个特定类型 版本的函数。当编译器实例化一个模板时, 它使用实际的模板实参类型代替对应的模板类型参数来创建出该模板的一个新“实例”。

上述的程序用两个不同的类型实例化了两个不同版本的 compare ,这些编译器生成的特定类型版本的函数通常被称为模板的实例( 有些书上也叫模板函数 )。


模板类型参数 (579P)


 模板类型参数 ( 就像上述程序中 “ < T > ”)有什么用处呢?:

  • 可以用来指定返回类型或函数的参数类型, 以及在函数体内用于变量声明或强制类型转换。

注意的是,声明每一个模板类型参数前都必须使用关键字 class 或 typename( 它们含有相同,可以同时互换使用)。

通常我们应该使用 typename 关键字来声明模板类型参数, 因为它比class 更加的直观,也能更加清楚地指出随后的名字是一个类型名称。


非类型模板参数 (580P)


可以通过一个特定的类型名而非关键字class 或 typename 来给模板定义一个非类型参数, 非类型参数必须是一个常量表达式。

当一个模板被实例化时, 非类型参数被一个用户提供的或编译器推断出的值所代替, 从而允许编译器在编译时实例化模板。

有哪些类型可以作为非类型参数呢?

  • bool、char、wchar_t、char16_t、char32_t、short、int、long、long long、
  • 指向对象类型的指针
  • 指向函数类型的指针
  • 左值引用

绑定到非类型的整型参数的实参必须是一个常量表达式。 绑定到指针或引用的非类型参数的实参必须具有静态的生存期。

有哪些类型不可以作为非类型参数呢?

  • double 、float、long double、类类型(比如:string)
  • 我们不能用一个普通 (非 static) 局部变量或动态对象作为指针或引用非类型模板参数的实参。
template<unsigned N, unsigned M>
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  关键字 (581P)


inline或 constexpr 说明符放在模板参数列表之后, 返回类型之前:

// ok: inline specifier follows the template parameter list
template <typename T> inline T min(const T&, const T&);

// error: incorrect placement of the inline specifier
inline template <typename T> T min(const T&, const T&);

记住编写泛型代码有两个重要原则:

  • 模板中的函数形参是对const的引用
  • 函数体中的条件判断仅使用<比较运算。

那为什么函数参数是对const的引用?

  • 因为将函数的参数设定为 const 引用可以保证该函数可以用于不能拷贝的类型( 比如: unique_ptr 和 IO 类型)。
  • 当该函数处理大的对象时,那么这种设计也会使函数运行得更快。

那为什么仅使用<比较运算?

  • 如果编写代码时只使用<运算符, 我们就降低了该函数对要处理的类型的要求。这些类型必须支持<, 但支不支持 > 并不重要。

 less 关键字 (581P)


 


模板编译 (582P)


当编译器遇到一个模板定义时, 它并不生成代码。只有当我们实例化出模板的一个特定版本时, 编译器才会生成代码。

当我们使用 (而不是定义,是实例化)模板时, 编译器才生成代码, 这一特性影响了我们如何组织代码以及何时检测到错误。

 为了生成一个实例化版本, 编译器需要有函数模板或类模板成员函数的定义代码。因此,与非模板代码不同, 模板的头文件通常既包括声明也包括定义。所以说 ,函数模板和类模板成员函数的定义通常放在头文件中。


模板的编译错误主要是在实例化过程中报告的 ( 582P)


练习16.1:

当调用一个函数模板时, 编译器会利用给定的函数实参来推断模板实参,它使用实际的模板实参来代替对应的模板参数来创建出模板的一个 “ 新实例 ”,也就是一个真正可以调用的函数, 这个过程称为实例化。

练习题16.3:

因为 Compare 函数中 使用的是 < 运算符进行操作的,需要类型 T 事先就定义该运算符, 但是我们的实际类型 Sales_data 它没有实现 < 运算符, 所以会发生错误

练习题16.4:


template <typename T,typename V>
T find_V( T v1,  T v2, const V &v3)
{
	while (v1 != v2 && *v1 != v3)
	{
		++v1;
	}
	return v1;
}
int main()
{
	vector<int> 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<string> 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 <typename T>
 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<string> v2{ "huang","chengt","tao" };
	print(v2);
	system("pause");
	return 0;
}

练习题16.6:

begin 函数是指向数组首元素的指针, 而 end 指向数组尾元素之后的指针。 当使用该函数时,可以很容易写出一个循环并且处理数组中的元素。


template <typename T>
 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 <typename T>
 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:

因为大多数标准库容器的迭代器都定义了 == 和 !=, 但是它们大多数都没有定义< 运算符, 因此只要使用 迭代器 和 !=的习惯, 就不在意用的是哪种容器。


类模板 (583P)


类模板与函数模板的不同之处是:

  • 编译器不能为类模板推断模板参数类型。我们创建类模板的实例化时,必须在模板名后的尖括号中提供 额外的信息, 它用来替换模板参数的模板实参列表。

当我们使用一个类模板时,我们必须提供显式的模板实参列表, 它们被绑定到模板参数使用实际的模板实参来代替对应的模板参数来创建出模板的一个 “ 新实例 ”,也就是一个特定类型的模板类。

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); }

	
	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<std::vector<T>> data;

	// throws msg if data[i] isn't valid
	void check(size_type i, const std::string &msg) const;
};
int main()
{
	
	Blob<int> ia; // empty Blob<int>
	Blob<int> ia2 = { 0,1,2,3,4 }; // Blob<int> with five elements


	system("pause");
	return 0;
}

当编译器从 Blob 模板实例化一个类时,它会重写Blob模板,会用给定的模板实参替换模板参数T的每个实例,在本例中是int。

对我们指定的每一种元素类型, 编译器都生成一个不同的类:

// 下面的定义实例化出两个不同的Blob类型
Blob<string> names; // Blob that holds strings
Blob<double> prices;// different element type
  • 这两个定义会实例化出两个不同的类。names的定义创建了一个Blob类, 每个T都被替换为string。 prices的定义生成了另一个Blob类, T被替换为double . 
  • 所以说一个类模板的每个实例都形成一个独立的类。类型 Blob<string> 与任何其他 Blob 类型都没有关联, 也不会对任何其他Blob 类型的成员有特殊访问权限。

在类模板作用域内引用其它模板类型 (585P)


类模板不是一个类型,而是一个模板。 需要给类模板提供显式模板实参,它才能是一个类型


对于一个实例化了的类模板, 类模板的成员函数只有在程序使用该成员函数时才会实例化。(587P)


  • 类模板的每个实例都有其自己版本的成员函数。
  • 因此,在定一个类模板的成员函数时,该成员函数具有和类模板相同的模板类型参数。
  • 所以定义在类模板之外的成员函数必须以关键字 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); }

	
	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<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>
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)) { }

template <typename T>
void Blob<T>::check(size_type i, const std::string &msg) const
{
	if (i >= data->size())
		throw std::out_of_range(msg);
}
template <typename T>
T& Blob<T>::back()
{
	check(0, "back on empty Blob");
	return data->back();
}
template <typename T>
T& Blob<T>::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 <typename T> void Blob<T>::pop_back()
{
	check(0, "pop_back on empty Blob");
	data->pop_back();
}
int main()
{
	
	Blob<int> ia; // empty Blob<int>
	Blob<int> ia2 = { 0,1,2,3,4 }; // Blob<int> with five elements
	Blob<string> articles = { "a", "an", "the" };

	system("pause");
	return 0;
}

默认情况下,对于一个实例化了的类模板, 类模板的成员函数只有在程序使用该成员函数时才会实例化。

// instantiates Blob<int> and the initializer_list<int> constructor
Blob<int> squares = {0,1,2,3,4,5,6,7,8,9};

// instantiates Blob<int>::size() const
for (size_t i = 0; i != squares.size(); ++i)
squares[i] = i*i; // instantiates Blob<int>::operator[](size_t)

上述的代码实例化了 Blob<int>类 和它的三个成员函数:

  • operator[]、size 和 接受 initializer_1ist<int>的构造函数。

如果一个成员函数没有被使用, 则它不会被实例化。成员函数只有在被用到时才进行实例化, 这一特性使得即使某种类型不能完全符合模板操作的要求, 我们仍然能用该类型实例化类。

 


使用一个类模板类型时,在类模板自身的作用域中,可以直接使用模板名而不提供实参 (587P)



类模板与友元 (588P)


  • 当一个类包含一个 friend声明时,类和 friend 可以相互独立为模板,也可以不是。
  • 如果一个类模板包含一个非模板友元,则该友元被授权为可以访问该模板的所有实例。、
  • 如果友元自身是一个模板,该类模板可以授权给所有友元模板的实例,也可以只授权给特定实例。

使用 using 声明为模板声明别名 (590P)


因为模板不是一个类型, 所以我们不能定义一个  typedef 引用一个模板。例如:无法定义一个typedef引用Blob<T>. 

template<typename T> using twin = std::pair<T, T>;
twin<string> authors; // // authors is a pair<string, string>

就像使用类模板一样,当我们使用twin时,  需要指出希望使用哪种特定类型的twin。


类模板的 static 成员 (591P)


在下面的代码中,每个 Foo 的实例都有其自己的 static 成员实例。即, 对任意给定类型x, 该类型都有一个Foo<x>: :ctr 和一个Foo<X ::count成员。所有Foo<x> 相同类型的对象共享相同的 ctr 对象和 count 函数。例如:


template <typename T> class Foo
{
public:
	static std::size_t count() { return ctr; }
	// other interface members
private:
	static std::size_t ctr;
	// other implementation members
};
template <typename T>
std::size_t Foo<T>::ctr = 0; // static 成员一定要定义成模板,因为类模板的每个类型实例都有一个独有的 static 对象
// 注意,模板类的每个 static 数据成员必须有且仅有一个定义

std::size_t Foo<T>::count() // 在类外定义 static 成员函数
{
	return ctr;
}
int main()
{

	// 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;

	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?

	system("pause");
	return 0;
}
  • 记住 ,如果一个类模板中有静态成员,那么该模板的每一个实例都有其自己的 static 成员实例。
  • 如果该模板的实例的类型都一样,  那么同类型实例化的对象都共享 static 成员

注意:类似任何其他成员函数, 一个static成员函数只有在使用时才会实例化。


模板类型参数的作用域


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
}

首先呢,模板类型参数的作用域跟普通的作用域规则一样。一个 模板类型参数的可用范围是在其声明之后,然后直到模板声明或定义结束之前。需要注意的是,模板类型参数也会隐藏外层作用域中声明的相同名称。 但是,在模板的定义中不能重新声明模板类型参数名。 上面的代码就可以看出来。

由于参数名不能重用, 所以一个模板类型参数名在一个特定模板参数列表中只能出现一次:

//错误:非法重用模板参数名v
template <typename V, typename V>

 


使用类的类型成员


默认情况下, C++语言假定通过作用域运算符访问的名字不是一个类型。因此, 如果我们希望使用一个模板类型参数的类型成员, 必须显示使用 typename 来告诉编译器该名字是一个类型。

template <typename T>
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<T> and a default function argument, F()
template <typename T, typename F = std::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;
}

与函数默认实参一样, 对于一个模板参数, 只有当它右侧的所有参数都有默认实参时, 它才可以有默认实参。

 


类模板默认实参


template <class T = int> class Numbers 
{ // by default T is int
public:
	Numbers(T v = 0) : val(v) { }
private:
	T val;
};

int main()                             
{
	Numbers<long double> lots_of_precision;
	Numbers<> average_precision; // 空的 <> 表示我们希望使用默认的模板实参
	system("pause");
	return 0;
}

记住: 无论何时使用类模板,都需要在模板名之后接上尖括号。

 


成员模板


一个类 ( 无论是普通类或类模板) 中可能具有本身就是模板的成员函数。这些成员称为成员模板。成员模板可能不是虚函数。

 


普通 ( 非模板 )类的成员模板


// 函数对象类,它在给定指针上调用delete
class DebugDelete 
{
public:
	DebugDelete(std::ostream &s = std::cerr) : os(s) { }
	// 与任何函数模板一样,T的类型由编译器推断
	template <typename T> 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 <typename T> class Blob
{
public:
	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 main()                             
{
	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" };

	// 实例化 Blob<int> 类及其接受两个 int*参数的构造函数
	Blob<int> a1(std::begin(ia), std::end(ia));

	// 实例化 Blob<int> 类的接受两个vector<long>::iterator的构造函数
	Blob<int> a2(vi.begin(), vi.end());

	// 实例化 Blob<string> 及其接受两个list<const char*>: :iterator参数的构造函数
	Blob<string> a3(w.begin(), w.end());
	system("pause");
	return 0;
}

 


控制实例化


当模板被使用时才会进行实例化这一特性意味着, 相同的实例可能出现在多个目标文件中。当两个或多个独立编译的源文件使用了相同的模板, 并提供了相同的模板实参时, 每个源文件中就都会有该模板的一个实例。

 

我们可以通过使用 显式实例化 来避免在多个源文件中实例化相同模板的额外开销,

 

当编译器看到 extern 模板声明时,它不会为该源文件中的生成实例化代码。将实例化声明为 extern 是一种承诺,即在程序其他位置有该实例化的一个非extern声明(定义)。对于一个给定的实例化版本,可能有多个extern声明,但该实例化必须只有一个定义。

 


模板实参推断


我们已经看到,默认情况下,编译器使用调用中的函数实参来确定函数模板的模板参数。以函数实参确定模板实参的过程称为模板实参推断。在模板实参推导过程中,编译器使用调用中的实参类型来查找模板实参,该模板实参生成与给定调用最匹配的函数版本。

 


类型转换和模板类型参数


与非模板函数一样, 我们在一次调用中传递给函数模板的实参会被用来初始化函数的形参。如果一个函数形参的类型使用了模板类型参数, 那么它采用特殊的初始化规则。 只有数量非常有限的类型转换会自动应用于这些实参。编译器通常不是对实参进行类型转换, 而是生成一个新的模板实例。

与往常一样, 顶层const无论是在形参中还是在实参中, 都会被忽略。在其他类型转换中,能在调用中应用于函数模板如下:

  • const 转换
  • 数组或函数指针的转换

注意:如算术转换、派生类向基类的转换以及用户定义的转换, 都不能应用于函数模板。

template <typename T> T fobj(T, T)// 实参被拷贝
{
	cout << "Called :: fobj " << endl;
	return 0;
}
template <typename T> 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;
}

 


用一个模板类型参数作用于一个函数中的多个参数类型(602Page)


 

template <typename T> 
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 <typename A, typename B>
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;
}

注意:当然,这些类型必须存在一个 < 操作符来比较这些类型的值。如果说是一个类类型,但它并没有重载 “ < ” 运算符, 那么错误。

 


在函数模板中使用非模板类型参数的类型 (602Page)


函数模板可以具有使用普通类型 (即,不涉及模板类型参数的类型)定义的参数。这些实参不进行特殊处理; 它们通常被转换为形参的对应类型:

template <typename T> 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;
}

注意:如果函数参数类型不是模板类型参数, 则对实参进行正常的类型转换。


在调用函数模板时指定显式模板实参 (603 Page)


有时候编译器不能推断出模板实参的类型,如下两种情况最常见:

  • 我们希望允许用户控制模板实例化。
  • 当函数返回类型与参数列表中使用的任何函数返回类型不同时

比如下面这个程序:

// 编译器无法推断T1 的类型,它未出现在函数形参列表中
template <typename T1, typename T2, typename T3>
T1 sum(T2 v1, T3 v2)
{
	cout << "输出参数结果:" << v1 << " ," << v2 << endl;
	return 0;
}



int main()
{
	// T1 的实参类型是显式指定的, T2和T3是从函数实参类型推断而来的
	auto val3 = sum<long long>(100, 2.5); // long long sum(int, long)
	system("pause");
	return 0;
}

 

template <typename T1, typename T2, typename T3>
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<long long>(i, lng);

	// ok: all three parameters are explicitly specified
	auto val2 = alternative_sum<long long, int, long>(i, lng);
	system("pause");
	return 0;
}

 


正常类型转换应用于显式指定的函数实参



尾置返回类型用于确定函数模板的返回类型(605Page)


// 尾置返回允许我们在参数列表之后声明返回类型
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
}

int main()
{
	vector<int> vi = { 1,2,3,4,5 };
	vector<string> 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;
}

 


能够进行类型转换的标准库模板类(605Page)



用一个函数模板初始化一个函数指针或者对一个函数指针赋值 (607Page)


当我们用一个函数模板初始化一个函数指针或对一个函数指针赋值时,  编译器使用指针的类型来推断模板实参。 如果编译器不能从函数指针类型确定模板实参,  则产生错误。

 

template <typename T> 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 版本实例。

 


从左值引用函数参数推断类型( 608Page)


template <typename T> void f(T &p);

如果一个函数的参数是模板类型参数的一个普通(左值)引用时 ( 即, 形如T& )。

那么我们可以为这样的函数模板传递哪些实参类型呢?

  • 只能给该函数模板传递一个左值 ( 如,一个变量或 一个返回引用类型的表达式)。
  • 传递给该函数模板的实参可以是const类型, 也可以不是。如果实参是const的, 则 模板类型参数推断为const类型

 

template <typename T> void f1(T& v1)// argument must be an lvalue
{
	cout << "输出 v1:" <<v1 << endl;
}


int main()
{
	int i = 9; const int ci = 1200;
	// 下面的所有调用是 对f1的调用是使用实参所引用的类型作为模板参数类型
	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: pass argument to a & parameter must be an lvalue
	system("pause");
	return 0;
}

 

如果一个函数模板的参数的类型是const T&。

那么我们可以为这样的函数模板传递哪些实参类型呢?

  • 可以传递给它任何类型的实参 —— 一个对象 ( const 或 非const )、一个临时对象或是一个字面常量值。

注意:函数模板的参数本身是 const 时,  T的类型推断的结果不会是一个const类型。因为 const 已经是函数参数类型的一部分;因此,如果函数模板的参数本身是 const 时,它不会成为推导模板参数类型的一部分。


template <typename T> 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;
}

从右值引用函数参数推断类型( 608Page)


当一个函数参数是一个右值引用 (即,形如T&&)时。

那么我们可以为这样的函数模板传递哪些实参类型呢?

  • 可以传递给它一个右值 ( 当我们这样做时, 类型推断过程类似普通左值引用函数参数的推断过程。推断出的T的类型是该右值实参的类型 )
template <typename T> 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;
}

 

 

 

 

 

  • 1
    点赞
  • 0
    评论
  • 2
    收藏
  • 一键三连
    一键三连
  • 扫一扫,分享海报

©️2021 CSDN 皮肤主题: 技术黑板 设计师:CSDN官方博客 返回首页
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、C币套餐、付费专栏及课程。

余额充值