在前面的课程中,我们已经学习了如何使用模板类型参数来创建与具体类型无关的函数与类。模板类型参数是一种占位类型,它会被调用者传入的实参类型所替换。
然而,模板类型参数并非唯一的模板参数形式。模板类与模板函数还可以使用另一种称为“非类型参数”的模板参数。
非类型参数
模板非类型参数是一种类型已预先确定的模板参数,它会被调用者传入的 constexpr 值所替换。
非类型参数可以是以下任意类型:
- 整数类型
- 枚举类型
- 指向类对象的指针或引用
- 指向函数的指针或引用
- 指向类成员函数的指针或引用
- std::nullptr_t
- 浮点类型(自 C++20 起)
在下面的示例中,我们实现了一个非动态(静态)数组类,该类同时使用类型参数与非类型参数:类型参数控制静态数组的元素类型,整型非类型参数控制数组的大小。
#include <iostream>
template <typename T, int size> // size 为整型非类型参数
class StaticArray
{
private:
// 非类型参数决定数组大小
T m_array[size]{};
public:
T* getArray();
T& operator[](int index)
{
return m_array[index];
}
};
// 演示如何为非类型参数类在类外定义成员函数
template <typename T, int size>
T* StaticArray<T, size>::getArray()
{
return m_array;
}
int main()
{
// 声明一个可容纳 12 个整数的整型数组
StaticArray<int, 12> intArray;
// 按顺序填充,再逆序输出
for (int count{ 0 }; count < 12; ++count)
intArray[count] = count;
for (int count{ 11 }; count >= 0; --count)
std::cout << intArray[count] << ' ';
std::cout << '\n';
// 声明一个可容纳 4 个 double 的缓冲区
StaticArray<double, 4> doubleArray;
for (int count{ 0 }; count < 4; ++count)
doubleArray[count] = 4.4 + 0.1 * count;
for (int count{ 0 }; count < 4; ++count)
std::cout << doubleArray[count] << ' ';
return 0;
}
程序输出:
11 10 9 8 7 6 5 4 3 2 1 0
4.4 4.5 4.6 4.7
上述示例的一个显著特点是:我们无需动态分配 m_array 成员变量!这是因为对于任意给定的 StaticArray 实例,size 必须是 constexpr。例如,当你实例化 StaticArray<int, 12> 时,编译器将 size 替换为 12,于是 m_array 的类型为 int[12],可在静态存储期分配。
这一机制正是标准库类 std::array 的实现基础。当你声明 std::array<int, 5> 时,int 是类型参数,5 是非类型参数!
请注意:若试图用非 constexpr 值实例化模板非类型参数,将导致编译失败:
template <int size>
class Foo
{
};
int main()
{
int x{ 4 }; // x 不是 constexpr
Foo<x> f; // 错误:模板非类型实参必须是 constexpr
return 0;
}
在此情况下,编译器将报出相应错误。