C++ 学习笔记(16)模板、引用折叠、std::forward、可变参数模板、模板特例化

本文深入讲解C++模板的基础概念及高级应用,包括函数模板、类模板、模板特例化等,并探讨了模板实参推断、可变参数模板等高级特性。

C++ 学习笔记(16)模板、引用折叠、std::forward、可变参数模板、模板特例化

参考书籍:《C++ Primer 5th》
API:模板


16.1 定义模版

16.1.1 函数模版

template <typename T> int compare(const T &v1, const T &v2)
{
	if (v1 < v2) return -1;
	if (v2 < v1) return -1;
	return 0;
}

template <typename T, class U> calc (const T&, const U&);		// 每个模版参数都要带 typename 或 class 关键字

template <typename T> inline T func(const T&);		// inline 或 constexpr 说明符 放在模版参数列表之后
  • 非类型模版参数:表示值而非类型。可以是整形,指向对象或函数的指针或左值引用。而绑定的实参必须是常量表达式,绑定到指针或引用实参必须在静态内存里。
// 比较两个字符串
template <unsigned M, unsigned N> int compare(const char(&p1)[M], const char(&p2)[N])
{
	retrun strcpy(p1, p2);
}
  • 函数模版和类模版成员函数的定义通常放在头文件。

16.1.2 类模版

  • 不同于函数模版,编译器无法为类模版推断模版参数类型。
template <typename T> A<T>::A() { }		// A类的函数在类外定义时,也需要指名模版参数
  • 默认情况下,对于一个实例化了类模板,其成员只有在使用时才被实例化。
  • 在类模版作用域内,编译器处理模板自身引用时可以省略模版参数。
template <typename T> class A
{
public:
	// 定义时省略<T> 模版参数
	A& operator++()
	{
		A a = this;		// 使用时也省略参数模版
		return a;
	}
};
template<typename T> using twin = pair<T, T>;	// 使用别名
twin<int> ti;		// ti是 pair<int, int>

16.1.3 模版参数

  • 使用类的类型成员,直接编写其成员,如果类型不包含该成员时,编译器会报错。
// 返回T的value_type成员,声明定义时如果没有该成员,编译时出错
template <typename T> typename T::value_type top(const T& c)
{
	if (!c.empty())		// 执行该类的成员函数,(如果没有,运行错误)
		return c.back();
	else
		return typename T::value_type();	// 若无,值初始化
}

void main()
{
	vector<int> vi = { 3,4,5,2 };
	cout << top<vector<int>>(vi);		// 输出2
}
// 可以使用默认实参,如下,默认比较函数使用标准库的less
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;
}

16.1.4 成员模版

  • 一个类可以包含本身是模版的成员函数,这个成员称为成员模版,不可以是虚函数。
struct Printer 
{
	std::ostream& os;
	Printer(std::ostream& os) : os(os) {}
	template<typename T> void operator()(const T& obj) { os << obj << ' '; }	// 成员模板
};

void main()
{
	std::vector<int> v = { 1,2,3 };
	std::for_each(v.begin(), v.end(), Printer(std::cout));	// 使用临时Printer对象
	Printer pinter = Printer(std::cout);
	std::string s = "abc";
	std::for_each(s.begin(), s.end(), pinter);		// 使用Printer对象
}

16.1.5 控制实例化

  • 模版被使用时才会实例化。多个相同模版就会生成多个实例,可以使用显示实例化避免这种开销,即使用extern声明实例,但也必须实例化定义。
  • 实例化一个类模版时,所有成员都会实例化(包括内联成员函数)。

16.1.6 效率与灵活性

  • unique_ptr在编译时绑定删除器,避免间接调用删除器的运行时开学。
  • shared_ptr在运行时绑定删除器,使用户重载删除器更方便。

16.2 模版实参推断

16.2.1 类型转换与模版类型参数

  • const转换:可以将一个非const对象的引用(或指针)传递给const的形参。
  • 数组或函数指针转换:函数形参不是引用类型时,可以用数组或函数类型做指针转换。(数组转指向第一个元素的指针,函数转指向该函数类型的指针)
  • 传递数组实参时:
    • 实参传递是引用时:数组大小不同时,类型不同。
    • 实参传递非引用时:数组转换成指针,不在乎数组大小。

16.2.2 函数模版显式实参

template <typename T1, typename T2, typename T3> T1 sum1(T2, T3) {}
template <typename T1, typename T2, typename T3> T3 sum2(T2, T1) {}		// 糟糕的设计

void main()
{
	// T1显示指定(必须指定),T2、T3从实参类型中推断。
	auto v1 = sum1<int>(1.0, 2.0);	// int sum<int, double, double>(double, double)

	// 因为T3才是返回值,所以必须要指定T1、T2。
	auto v2 = sum2<float, float, int>(2, 4);	// 正确
	auto v3 = sum3<int>(3, 5);		// 错误,返回值无法推断出来。
}

16.2.3 尾置返回类型与类型转换

在这里插入图片描述

// 指名返回值为参数类型的解引用,如果传入int序列,那就是返回int&
template <typename It> auto func1(It beg, It end) -> decltype(*beg) {}

// 使用标准库type_traits的模版,必须使用typename告诉编译器type是类型,传入int序列时,返回int类型值
template <typename It> auto func2(It beg, It end) -> typename remove_reference<decltype(*beg)>::type {}

16.2.5 模版实参推断和引用

  • 右值引用参数:被传入左值时,类型是左值引用。
  • 引用的引用:会被折叠成普通引用。
    • X& &X& &&X&& & 都会折叠成类型 X&
    • X&& && 折叠成 X&&
  • 引用折叠只能用于间接创建的引用的引用,如类型别名或模版参数。
  • 模版参数类型为右值引用时(T&&):可以传递任意类型实参。且传递左值时,会被实例化成左值引用。
  • 右值引用常用于:
    • 模版转发实参。
    • 模版重载。
template <typename T> void f(T&&) {}	// 右值形参

void main()
{
	int i = 123;
	const int ci = 345;
	f(123);		// T为int。(传入右值)
	f(i);		// T为int &。(传入左值)
	f(ci);		// T为const int &。(传入左值)
}

16.2.6 理解std::move

  1. 参数为右值,所以可以传入任何实参(包括左值)。
  2. remove_reference移除参数的左值引用(如果有)。
  3. 使用static_cast:将左值类型转换为右值引用。
template <typename T> typename remove_reference<T>::type && move(T&& t)
{
	return static_cast<typename remove_reference<T>::type&&>(t);
}

16.2.7 转发

  • 使用std::forward保持实参传递的左值/右值属性。
  • 配合传入模版参数类型为右值引用(T&&)时,forward会保持实参类型的所有细节。
// 调用函数f,按t2、t1顺序传入参数。
template <typename F, typename T1, typename T2> void flip(F f, T1 &&t1, T2 &&t2)
{
	// 使用forward,传入实参的左值右值属性都会被保留。
	f(std::forward<T2>(t2), std::forward<T1>(t1));
}

16.3 重载与模板

  • 如果多个函数提供同样好的匹配:
    • 只有一个是非模板函数,选择这个。
    • 没有非模板函数,在多个最特例化的模版。
    • 否则,调用有歧义。
cout << debug("hello") << endl;		// 传入const char *,调用 debug(T *)
// 下面三者都满足,调用优先级依次降低
// debug(T*),T为const char,比下面的更特例化
// debug(const T&),T为char[6]
// debug(const string&),从const char * 转换到 string,可以,但不是精确匹配

16.4 可变参数模板

  • 可变数目的参数成为参数包
    • 模板参数包:零个或多个模板参数。
    • 函数参数包:零个或多个函数参数。
template <typename... Args> void foo(const Args &... rest) 
{
	cout << sizeof...(Args) << endl;	// 类型参数的数目
	cout << sizeof...(rest) << endl;	// 函数参数的数目
}

16.4.1 编写可变参数函数模板

template <typename T,typename... Args> void print(ostream &os,const T& t, const Args &... rest)
{
	os << t << ", ";
	print(os, rest...);		// 递归调用,每次调用减少一个参数(t)
}

// 递归最内层的函数。非可变参数版本。必须有该函数,否则可变参数函数调用出错。
template <typename T> void print(ostream &os, const T& t)
{
	os << t << "!! ";
}

void main()
{
	// 输出:123, abc, 2.333!! 
	print(cout, 123, "abc", 2.333f);
}

16.4.2 包扩展

template <typename... Args> void print(ostream &os, const Args &... rest)
{
	print(os, debug(rest) ...);		// 对每个参数都作为debug()函数的参数,递归调用
}

16.4.3 转发参数包

// 模仿构造函数的参数列表
template <class... Args> void emplace_back(Args&&...);

16.5 模板特例化

  • 模板及其特例化版本应该声明在同一个头文件,所有同名模板的声明应该放在前面,然后是这些模板的特例化版本。
  • 只能部分特例化类模板,不能部分特例化函数模板。
// 版本1:比较任意两个类型
template <typename T> int compare(const T &v1, const T &v2);

// 版本2:处理字符串字面常量,处理的是数组,不能是指针
template <unsigned M, unsigned N> int compare(const char(&p1)[M], const char(&p2)[N]);

// 版本3:模板特例化,可直接处理字符串(compare("sd","ab") )
template <> int compare(const char* const &p1, const char* const &p2);
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值