C++11:模板的改进

右尖括号 > 改进

  在 C++ 98/03 的泛型编程中,模板实例化有一个很繁琐的地方,就是连续两个右尖括号>>会被编译解释成右移操作符,而不是模板参数表的形式,需要一个空格进行分割,以避免发生编译时的错误。

vector<vector<int>> a;// err, 编译失败
vector<vector<int> > a; // ok, 编译成功

  在实例化模板时会出现连续两个右尖括号,同样 static_castdynamic_castreinterpret_castconst_cast 表达式转换时也会遇到相同的情况。C++ 98 标准是让程序员在 >> 之间填上一个空格,在 C++ 11中,这种限制被取消了。在 C++ 11标准中,要求编译器对模板的右尖括号做单独处理,使编译器能够正确判断出 >> 是一个右移操作符还是模板参数表的结束标记。

模板的别名

  对于冗长或者复杂的标识符,C++ 提供了 typedef 用来创建其别名

//原来的C++ 
typedef vector<string>::iterator itType;

//C++ 11
using itType = vector<string>::iterator;

  二者的差别在于,新语法也可以用于模板部分的具体化,但是 typedef 不能

template<typename T>
using arr12 = array<T, 12>;
//默认的使用方法
array<double, 12> a1;
array<string, 12> a2;
//替换为
arr12<double> b1;
arr12<string> b2;

函数模板的默认模板参数

  C++11之前,类模板是支持默认的模板参数,却不支持函数模板的默认模板参数:

//1、普通函数带默认参数,c++98 编译通过,c++11 编译通过
void DefParm(int m = 3) {}

//2、类模板是支持默认的模板参数,c++98 编译通过,c++11 编译通过
template <typename T = int>
class DefClass {};

//3、函数模板的默认模板参数, c++98 - 编译失败,c++11 - 编译通过
template <typename T = int> void DefTempParm() {}

  类模板的默认模板参数必须从右往左定义,函数模板的默认模板参数则没这个限定:

template<class T1, class T2 = int> class DefClass1;
template<class T1 = int, class T2> class DefClass2;   // 无法通过编译

template<class T, int i = 0> class DefClass3;
template<int i = 0, class T> class DefClass4;         // 无法通过编译

template<class T1 = int, class T2> void DefFunc1(T1 a, T2 b);
template<int i = 0, class T> void DefFunc2(T a);

变长参数模板

  在 C++ 11 之前,类模板和函数模板只能含有固定数量的模板参数。C++ 11 增强了模板功能,允许模板定义中包含 0 到任意个模板参数,这就是可变参数模板。

可变参数模板和普通模板的语义是一样的,只是写法上稍有区别,声明可变参数模板时需要在 typenameclass后面带上省略号

#include <iostream>
using namespace std;

template<typename... T>
void func(T ... args) {

}

int main() {
	func(); // OK:args不含有任何实参
	func(1);  // OK:args含有一个实参:int
	func(1, 1.1); // OK:args含有两个实参int和double 
	return 0;
}

T 叫模板参数包,args 叫函数参数包。
省略号 的作用有两个:

  • 声明一个参数包,这个参数包中可以包含0到任意个模板参数
  • 在模板定义的右边,可以将参数包展开成一个一个独立的参数

可变参数模板函数的定义

一个可变参数模板函数的定义如下:

#include <iostream>
using namespace std;

template<typename... T>
void func(T ... args) {
	cout << "num = " << sizeof...(args) << endl;
}

int main() {
	func();// num = 0
	func(1);// num = 1
	func(1, 1.1);// num = 2
	return 0;
}

参数包的展开

递归方式展开

通过递归函数展开参数包,需要提供一个参数包展开的函数和一个递归终止函数。

void print() {
	cout << "empty" << endl;
}
template<class T, class... Args>
void print(T first,Args ... args) {
	cout << "parameter " << first << endl;
	print(args...);
}

int main() {
	print(1, 2, 3.3, 'c', "test");
	return 0;
}

运行结果如下:
image.png

递归调用过程如下:

print(1, 2, 3.3, 'c', "test");
print(2, 3.3, 'c', "test");
print(3.3, 'c', "test");
print( 'c', "test");
print("test");
print();

通过可变参数模板实现打印函数:

#include <iostream>
#include <vector>
using namespace std;

void Print(const char* s) {
	while (*s) {
		if (*s == '%' && *++s != '%') {
			throw runtime_error("invalid format string: missing arguments");
		}
		cout << *s++;
	}
}

template<class T, class... Args>
void Print(const char* s, T value, Args... args) {
	while (*s) {
		if (*s == '%' && *++s != '%') {
			cout << value;
			return Print(++s, args...);
		}
		cout << *s++;
	}
	throw runtime_error("extra arguments provided to Debug");
}


int main() {
	Print("a = %d, b = %c, c = %s\n", 250, 't', "test");
	return 0;
}

非递归方式展开

template<class T>
void Print(T arg) {
	cout << arg << endl;
}

template<class... Args>
void expand(Args... args) {
	int a[] = { (Print(args), 0)... };
}
int main() {
	expand("a = %d, b = %c, c = %s\n", 250, 't', "test");
	return 0;
}

可变参数模板类

补课ing...