C++ 中模板元编程与 SFINAE 机制
什么是模板元编程?
用模板来进行编译器计算,运行期直接把结果替换到代码内,如编译器进行斐波那契数列计算,编译器判断是否指针类型;例子:
template <int N>
struct Fib {static constexpr int value = Fib<N-1>::value + Fib<N-2>::value;
};// 特化编程,设置边界
template <>
struct Fib<0> {static constexpr int value = 1;
}template <>
struct Fib<1> {static constexpr int value = 0;
};int main() {cout << Fib<10>::value << endl;return 0;
}
模板类型有几种?
模板类型分两种,一种是参数类型,一种是非参数类型
参数类型:用参数类型作为模板参数,泛化不同类型。
template
template
非参数类型:
用常量值作为模板参数,泛化不同常量,计算不同常量的结果;
template
template
模板元编程为什么必须使用静态成员变量?
模板元编程是编译期进行计算,非静态成员变量不能在编译器进行计算,需要运行期才能计算,因为访问非静态成员变量需要类/结构体对象,对象是运行期的产物;
注意:模板非类型参数作为模板参数,是运行非静态成员变量的,
非类型参数有哪些限制:
1)支持的类型限制:支持int, long, char, bool,enum,指针,引用,成员指针
不支持:double,float
2)必须是编译器常量
非类型参数的值必须在编译时确定,不能是运行时变量(比如 int n=5; Fib 会编译报错,因为 n 是运行时变量);
必须用常量表达式传入(如 Fib<5>、Fib<3+2>、constexpr int n=5; Fib)。
模板元编程有哪些用途?
TMP 的典型用途
编译期数值计算(如常量、数组大小、哈希值计算);
类型萃取(Type Traits,如判断类型是否为指针 / 引用 / 数组、提取模板参数等);
条件编译(根据类型 / 数值选择不同的模板实现);
生成复杂代码(如自动生成类成员、序列化逻辑)。
模板特化
模板的特殊版本,用于处理递归的终止条件(避免无限递归);
生成一个完全确定的模板实例。
template <>
struct Fib<1> {static constexpr int value = 0;
};
这就属于模板特化
template <> 是空模板参数列表,为 “模板参数 N=1” 这个特定情况,提供专门的结构体实现。
给类型参数使用还是非类型参数?
核心是「为模板的所有参数指定具体值 / 类型」,和模板参数本身是 “类型” 还是 “非类型” 无关。
非类型和类型都可以有全特化模板;
1. 类型模板参数(typename T)的全特化
// 通用类型模板(模板参数是类型 T)
template <typename T>
struct PrintType {static void print() {cout << "通用类型:" << typeid(T).name() << endl;}
};// 全特化:为 T=int 指定具体实现
template <>
struct PrintType<int> {static void print() {cout << "特化类型:int(整数类型)" << endl;}
};// 全特化:为 T=string 指定具体实现
template <>
struct PrintType<string> {static void print() {cout << "特化类型:string(字符串类型)" << endl;}
};// 全特化:为 T=double 指定具体实现
template <>
struct PrintType<double> {static void print() {cout << "特化类型:double(浮点数类型)" << endl;}
};int main() {PrintType<char>::print(); // 调用通用模板:输出“通用类型:char”PrintType<int>::print(); // 调用 int 全特化:输出“特化类型:int(整数类型)”PrintType<string>::print(); // 调用 string 全特化:输出“特化类型:string(字符串类型)”PrintType<double>::print(); // 调用 double 全特化:输出“特化类型:double(浮点数类型)”return 0;
}
2. 非类型模板参数(int N)的全特化
template <int N> // 模板参数是非类型 int N
struct Fib {static constexpr int value = Fib<N-1>::value + Fib<N-2>::value; // 通用递归逻辑
};// 全特化:为 N=0 指定具体实现(终止条件)
template <>
struct Fib<0> {static constexpr int value = 0;
};// 全特化:为 N=1 指定具体实现(终止条件)
template <>
struct Fib<1> {static constexpr int value = 1;
};
3. 混合模板参数(类型 + 非类型)的全特化
// 混合模板参数:类型 T + 非类型 N
template <typename T, int N>
struct MixTemplate {static void show() {cout << "通用版本:类型=" << typeid(T).name() << ", 非类型=" << N << endl;}
};// 全特化:同时具体化 T=int、N=5(所有参数都确定)
template <>
struct MixTemplate<int, 5> {static void show() {cout << "全特化版本:类型=int, 非类型=5" << endl;}
};// 全特化:同时具体化 T=double、N=3(所有参数都确定)
template <>
struct MixTemplate<double, 3> {static void show() {cout << "全特化版本:类型=double, 非类型=3" << endl;}
};int main() {MixTemplate<char, 2>::show(); // 通用版本:类型=char, 非类型=2MixTemplate<int, 5>::show(); // 全特化版本:类型=int, 非类型=5MixTemplate<double, 3>::show(); // 全特化版本:类型=double, 非类型=3return 0;
三、易混淆点:全特化 vs 偏特化
// 通用模板:T(类型)+ N(非类型)
template <typename T, int N>
struct MixTemplate { ... };// 偏特化:只具体化 N=5,T 仍保留泛化(不是全特化)
template <typename T>
struct MixTemplate<T, 5> { ... };// 全特化:具体化 T=int + N=5(所有参数都确定)
template <>
struct MixTemplate<int, 5> { ... };
全特化的关键是 “全”—— 所有模板参数都要 “落地” 到具体的类型 / 值,和参数本身是 “类型” 还是 “非类型” 无关。
