非类型模板参数详解
概念介绍
非类型模板参数(Non-type template parameters),顾名思义在模板中这个参数不是表示一个类型,而是表示一个值,它必须是编译时常量。
基本语法如下所示:
// 基本形式
template <type-name parameter-name>
template <typename T, int N> // N 是非类型模板参数
如上所示,T作为模板参数表示一个类型,而N作为一个模板参数,表示的并不是一个类型,而是一个数值。
template<T, int N>
class array{
private:
int buffer[N];
....
}
从上面的例子可以看到非类型模板参数N作为一个数值,供模板参数使用。
非类型模板参数在c++98就已经引入了,只是我们平时的使用中可能对模板参数是一个类型这种形似见的比较多,所以对非类型模板参数有所忽略。
非类型模板参数随着版本的演进
随着版本的变化,非类型模板参数的特性不断丰富,总体来说就是早期版本非类型模板参数仅支持部分基本的数据类型,而随着版本的不断发展,支持的数据类型越来越多,甚至到了c++20版本已经支持将自定义的类类型作为非类型模板参数传入。
c++98版本
在早期版本仅支持整型、枚举类型、指针、引用、成员函数指针作为非类型模板参数。
//基本数据类型
template<int N>
struct array{
int data[N];
}
//指针和引用
template<int* p>
struct pointer
{
//使用P
}
//枚举类型
enum Color{RED, GREEN, BLACK};
template<Color C>
struct ColorHolder{
//内部使用C
}
c++11版本
引入了nullptr和constexpr的支持
template <int* P = nullptr>
struct Pointer {};
constexpr int getValue() { return 42; }
template <int N = getValue()>
struct Value {};
c++14变量模板支持
template <int N>
constexpr int value = N;
static_assert(value<42> == 42);
可以看到支持对一个变量设置模板参数。
c++17版本带来的变化
c++17相对于之前的版本引入了一个很重要的改进,即在定义模板时,对于非类型模板参数来说无需使用显式的类型来声明非类型模板参数,而是可以使用auto关键即可。请看下面的例子:
// 自动推导类型
template <auto N>
struct Value {
using type = decltype(N);
static constexpr auto value = N;
};
Value<42> v1; // int
Value<'c'> v2; // char
Value<true> v3; // bool
请看上面的例子,在c++17之前在声明时不能使用auto关键字,而是必须显式的将非类型模板参数的类型声明出来,下面在看一个更具体的例子:
//c++17之前
// 使用auto作为非类型模板参数
template <int N>
class Array {
int data[N]; // N的类型会被自动推导
};
Array<42> arr1; // N 被推导为 int
Array<'c'> arr2; // 错误,与声明不一致
//c++17之后
// 使用auto作为非类型模板参数
template <auto N>
class Array {
int data[N]; // N的类型会被自动推导
};
Array<42> arr1; // N 被推导为 int
Array<'c'> arr2; // N 被推导为 char
c++20版本
这个版本对于非类型模板参数也是一个重大的改进,在该本支持了将类类型和浮点类型。在这之前非类型模板参数是不支持类类型和浮点数类型的。请看下面的例子:
class Point
{
private:
int x;
int y;
}
//c++20之前
template<Point p> //错误,不支持类类型,浮点数同理
struct PointTemplate
{
static constexpr Point value = p;
}
//c++20之后
template <Point p>
struct PointTemplate {
static constexpr Point value = p;
};
static constexpr Point origin{0, 0};
PointTemplate<origin> pt;
注意事项
无论版本如何演进,非类型模板参数,都必须是在编译时确定非类型模板参数的数值的,不能在运行时通过变量的方式确定非类型模板参数的值。请看下面的例子:
template<int N>
struct Array
{
int buffer[N];
}
int var = 10;
Array<var> a1; //编译错误,必须在编译时确定该值
Array<10> a2; //编译正确,在编译时非类型模板参数的数值已经确定
constexpr int var1 = 20;
Array<var1> a3; //编译正确,因为constexpr修饰的变量就是在编译阶段生效的。