非类型类模板参数

在模板编程中,除了将类型作为模板的参数外,还可以将非类型的模板参数作为参数传递:

template<typename T, int val>
class example {
public:
	void show() {
		std::cout << "val = " << val << std::endl;
	}
};

将int类型的val值传入模板,作为一个非类型模板参数;

对于该模板实例化出的对象,属于相互独立的类型,二者之间不存在隐式或显式的类型转换;

非类型函数模板参数

同样,对于函数也有对应的非类型函数模板参数:

#include <iostream>
#include <vector>
#include <algorithm>

template<int val, typename T>
T add_value(T x) {
	return x + val;
}

std::vector<int> vec;

void Print_Vector(std::vector<int> vec) {
	for (auto element : vec) {
		std::cout << element << ' ';
	}
}

int main(void) {
	for (int i = 0; i < 10; i++) {
		vec.push_back(i);
	}
	Print_Vector(vec);
	std::vector<int> new_vec;
	std::transform(vec.begin(), vec.end(), std::back_inserter(new_vec), add_value<10, int>);
	std::cout << std::endl;
	Print_Vector(new_vec);

	return 0;
}

这里传递了一个函数模板,用于transform内的使用,注意这里的函数模板必须进行特化,因为对于std::transform操作接受的对象类型来看,其支持传入一个函数对象和函数指针类型,所以为了匹配参数类型,必须对传入的函数模板进行特化操作add_value<10, int>

同时,我们也可以指定从前面参数推导而来的模板参数类型:

template<auto val, typename T = decltype(val)>
T Func();

这里的decltype用于在编译期获取参数类型,其可对一个变量名、函数调用、操作符、或者其他类型的表达式进行操作,返回该对象的参数类型;

非类型模板参数的限制

对于非类型模板参数的具体类型有具体的类型限制,它们只能是int类型常量(包括枚举常量)、指向对象、函数、成员的指针,对象或函数的左值引用,或者是std::nullptr(nullptr类型);

注意,浮点数或类类型的对象不可作为非类型模板参数传递:

对于类类型很好理解,对于浮点数,解释如下:

在 C++ 中,浮点数不能作为非类型模板参数传递的原因主要是因为模板参数必须在编译时就确定其值,而浮点数的精度和范围是无限的,因此编译器无法在编译时精确地确定浮点数的值;

对于传递对象为指针或引用时,对象不可以是字符串字面量、临时变量或数据成员以及其他子对象;

而对于不同的C++版本,其具体的限制也不相同,具体如下:

  1. C++11版本中,对象必须有外部链接;
  2. C++14版本中,对象必须具有外部或内部链接;
  3. C++17版本中,对象则没有以上限制;

避免无效表达式

非类型模板参数的实参可以是任何编译期表达式,但是注意,如果在表达式使用了运算符>(operator>),则必须将整个表达式放入圆括号内,以便让嵌套外层的符号>来终止实参列表;

Example<42, (sizeof(int) > 4)>; // 正确
Example<42, sizeof(int) > 4>; //错误,第一个>被误认为是模板实参列表的终止 

模板参数类型auto

C++17开始,支持定义一个非类型模板参数来普遍接受非类型参数所允许的任意类型;

所以,我们可以对上述的非类型模板参数进行进一步泛化操作:

template<typename T, auto val>
class example {
public:
	void show() {
		std::cout << "val = " << val << std::endl;
	}
};

这里的auto被称为占位符类型,它可以是非类型模板参数所允许的任何类型;

在C++14开始,auto也可被用于返回类型,让编译期推导出返回类型;

#include <iostream>
#include <type_traits>

auto show(auto T) {
	return T;
}

int main(void) {
	auto a = show(10);
	auto b = show(10.1);
	if (!std::is_same<decltype(a), decltype(b)>::value){
		std::cout << "Different type." << std::endl;
	}

	return 0;
}

这里对于函数show,让编译器自动推导参数类型和返回类型,并调用std::is_same函数进行类型判断;

这里介绍一下std::is_same函数,该函数定义于头文件<type_traits>中,用于判断两个参数类型是否相同,其返回值是布尔类型,从C++17开始,可以通过后缀_v省略::value对返回值类型特征进行处理;

std::is_same_v<decltype(a), decltype(b)>

注意,使用auto对浮点数等的限制依然存在(非类型模板参数);

总结

  1. 模板可以具有数值的模板参数而不只是类型的模板参数;
  2. 不能将浮点数或类类型对象用作非类型模板参数;对于字符串字面量、临时变量和子对象的指针或引用也有限制;
  3. 使用auto关键字可以是模板具有泛型类型值的非类型模板参数;