C++模板参数主要分为类型模板参数和非类型模板参数两大类,类型模板参数用于传递数据类型,非类型模板参数则用于传递编译期可确定的常量值,二者共同支撑了C++泛型编程的灵活性。

C++模板参数的分类
1. 类型模板参数
类型模板参数是最常见的模板参数形式,用于在模板实例化时指定具体的数据类型,通常用typename或者class关键字声明。例如下面的简单模板类,T就是类型模板参数:
// 类型模板参数示例
template <typename T>
class MyContainer {
private:
T data;
public:
MyContainer(T val) : data(val) {}
T get() const { return data; }
};
2. 非类型模板参数
非类型模板参数允许我们传递编译期就能确定的常量值作为模板参数,这些常量值可以是整数、枚举、指针、引用等类型,不能是运行时才能确定的变量。声明非类型模板参数时不需要typename关键字,直接指定参数类型和参数名即可。
非类型模板参数的支持类型
并非所有数据类型都可以作为非类型模板参数,C++标准规定支持的非类型模板参数类型包括:
- 整型(包括char、short、int、long等所有整数类型)
- 枚举类型
- 指向对象或者函数的指针(需要是编译期可确定的地址)
- 指向对象或者函数的引用(需要是编译期可确定的引用)
- std::nullptr_t类型(C++11及以后)
注意浮点数、字符串字面量(部分编译器扩展支持,但标准不保证)、自定义类类型都不能作为非类型模板参数。
非类型模板参数的应用场景
1. 固定大小的容器实现
很多场景下我们需要使用固定大小的容器,比如固定长度的数组,使用非类型模板参数可以在编译期确定容器大小,避免运行时动态内存分配的开销:
// 固定大小数组模板,N为非类型模板参数
template <typename T, int N>
class FixedArray {
private:
T arr[N];
public:
T& operator[](int index) {
return arr[index];
}
int size() const {
return N;
}
};
// 使用示例
int main() {
FixedArray<int, 5> arr;
for (int i = 0; i < arr.size(); ++i) {
arr[i] = i * 10;
}
return 0;
}
2. 编译期计算
非类型模板参数可以配合模板递归实现编译期计算,比如计算阶乘:
// 编译期阶乘计算模板
template <int N>
struct Factorial {
// 递归计算,N的阶乘等于N乘以N-1的阶乘
static const int value = N * Factorial<N-1>::value;
};
// 模板特化,0的阶乘为1,终止递归
template <>
struct Factorial<0> {
static const int value = 1;
};
int main() {
// 编译期就计算出5的阶乘结果,不需要运行时计算
int result = Factorial<5>::value; // result值为120
return 0;
}
3. 编译期配置参数传递
当我们需要给模板传递一些编译期确定的配置参数时,非类型模板参数非常实用,比如指定日志系统的最大日志级别:
// 日志级别枚举
enum LogLevel {
DEBUG,
INFO,
WARN,
ERROR
};
// 日志模板,MaxLevel为非类型模板参数,指定最大输出日志级别
template <LogLevel MaxLevel>
class Logger {
public:
void log(LogLevel level, const char* msg) {
if (level <= MaxLevel) {
// 实际日志输出逻辑
}
}
};
int main() {
// 实例化只输出INFO及以上级别的日志器
Logger<INFO> logger;
logger.log(DEBUG, "debug message"); // 不会输出
logger.log(INFO, "info message"); // 会输出
return 0;
}
非类型模板参数的使用限制
使用非类型模板参数时需要注意以下限制:
- 非类型模板参数的值必须是编译期常量,不能是运行时变量,比如下面的写法是错误的:
int main() {
int n = 5;
// 错误,n是运行时变量,不能作为非类型模板参数
FixedArray<int, n> arr;
return 0;
}
- 如果非类型模板参数是指针或引用类型,传递的参数必须是具有外部链接的对象的地址,或者是nullptr,不能是局部变量的地址。
- 非类型模板参数不支持浮点数、自定义类类型等标准未规定的类型,如果需要传递这类值,可以考虑封装成类型模板参数。
非类型模板参数的核心价值在于将运行时的常量判断提前到编译期完成,既可以减少运行时开销,也能在编译阶段发现更多参数错误,是C++模板高级用法中非常重要的组成部分。
总结
C++模板参数分为类型模板参数和非类型模板参数,前者用于传递数据类型,后者用于传递编译期常量。非类型模板参数在固定大小容器、编译期计算、编译期配置等场景下有着广泛的应用,使用时需要注意其支持的类型和编译期常量的限制。合理搭配两种模板参数,可以大幅提升C++代码的复用性和执行效率。