c++ 学习笔记之 模板元编程
本文介绍了C++模板元编程(TMP)和SFINAE技术。模板元编程通过在编译期完成计算和逻辑推导,利用模板特化、递归和类型萃取等特性实现编译期编程。示例展示了编译期常量计算、条件分支、类型萃取和递归模板等核心概念。SFINAE(替换失败非错误)技术则允许编译器在模板参数替换失败时尝试其他特化版本,C++20后可用Concepts替代大部分SFINAE应用。文章还介绍了标准库<type_tra
文章目录
1、模板元编程(TMP)
模板元编程是 C++ 中极具特色的高级编程范式 ——利用模板编译期实例化的特性,将计算或 逻辑从运行时转移到编译期完成。
看一个例子:
// 主模板:递归计算 N! = N * (N-1)!
template <unsigned int N>
struct Factorial {
static constexpr unsigned int value = N * Factorial<N - 1>::value;
};
// 全特化:终止条件 0! = 1
template <>
struct Factorial<0> {
static constexpr unsigned int value = 1;
};
// 编译期计算 5! = 120
constexpr unsigned int fact5 = Factorial<5>::value;
上述代码对模板元编程有很好的体现。模板元编程是一种编译期编程范式:通过 C++ 模板的递归实例化、特化、可变参数等特性,在编译器处理模板时完成计算或逻辑推导,最终生成可直接运行的机器码(无运行时模板开销)。
1.1 编译期常量(static constexpr)
static constexpr是TMP中数据存储的核心方式,能保证值在编译期确定:
// 编译期常量
template <int N>
struct ConstValue {
static constexpr int value = N * 2; // 编译期计算
};
// 使用:直接取 value,无运行时计算
constexpr int x = ConstValue<5>::value; // x=10
1.2 模板特化与条件分支
通过特化模板与主模板配合,能够在编译器实现条件判断。
//判断是否是指针类型
// 主模板:默认非指针类型
template <typename T>
struct IsPointer {
static constexpr bool value = false;
};
// 偏特化:匹配指针类型
template <typename T>
struct IsPointer<T*> {
static constexpr bool value = true;
};
// 使用
//IsPointer<int*>::value == true
//IsPointer<int>::value == false
----------------------------------------
//判断奇偶
template<int N>
struct IsEven {
static constexpr bool value =(N%2==0);
}
//偏特化
template<>
struct IsEven<0> {
static constexpr bool value =true;
}
template<>
struct IsEven<1> {
static constexpr bool value =false;
}
//使用:
//IsEven<4>::value=true;
//IsEven<3>::value=false;
1.3 类型萃取
// 主模板:默认类型
template <typename T>
struct RemoveConst {
using type = T;
};
// 偏特化:匹配const T
template <typename T>
struct RemoveConst<const T> {
using type = T;
};
// 使用
using NonConstInt = RemoveConst<const int>::type;
static_assert(std::is_same_v<NonConstInt, int>, "RemoveConst failed");
对于类型萃取,C++11版本开始标准库在 <type_traits> 头⽂件中提供了⼤量类型萃取⼯具:
#include <type_traits>
// 1、基础类型检查
std::is_void<void>::value; // true
std::is_integral<int>::value; // true
std::is_floating_point<float>::value; // true
std::is_pointer<int*>::value; // true
std::is_reference<int&>::value; // true
std::is_const<const int>::value; // true
// 2、复合类型检查
std::is_function<void()>::value; // true
std::is_member_object_pointer<int (Foo::*)>::value; // true
std::is_compound<std::string>::value; // true (⾮基础类型)
// 3、类型关系检查
std::is_same<int, int32_t>::value; // 取决于平台
std::is_base_of<Base, Derived>::value;
std::is_convertible<From, To>::value;
// 4、类型修改
std::add_const<int>::type; // const int
std::add_pointer<int>::type; // int*
std::add_lvalue_reference<int>::type; // int&
std::remove_const<const int>::type; // int
std::remove_pointer<int*>::type; // int
std::remove_reference<int&>::type; // int
// 4、条件类型选择
std::conditional<true, int, float>::type; // int
std::conditional<false, int, float>::type; // float
// 5、类型推导
// 函数的返回结果类型
std::result_of<F(Args...)>::type; // C++17以后被废弃
std::invoke_result<F, Args...>::type; // C++17以后使⽤这个
template<class F, class... Args>
using invoke_result_t = typename invoke_result<F, Args...>::type;
//c++ 17 又为类型萃取添加了 _v 和 _t 后缀的便利变量模板和类型别名
// C++11⽅式
std::is_integral<int>::value;
std::remove_const<const int>::type;
// C++14、C++17 更简洁的⽅式
std::is_integral_v<int>;
std::remove_const_t<const int>;
// C++17 引⼊的辅助变量模板
template<typename T>
inline constexpr bool is_integral_v = is_integral<T>::value;
// C++14 引⼊的辅助别名模板
template<typename T>
using remove_const_t = typename remove_const<T>::type;
1.4 模板递归
#include <array>
#include <iostream>
// 辅助模板:构建倒序索引序列
template <typename T, size_t N, size_t... I>
struct ReverseArrayHelper {
// 修正:参数包仅追加 N-1,不插入冗余的 0
using type = typename ReverseArrayHelper<T, N - 1, N - 1, I...>::type;
};
// 特化模板:递归终止(N=0 时,用参数包 I 定义 std::array 类型)
template <typename T, size_t... I>
struct ReverseArrayHelper<T, 0, I...> {
// 正确:类型别名仅定义 std::array 类型,初始化在使用时完成
using type = std::array<T, sizeof...(I)>;
};
// 模板别名:简化使用
template <typename T, size_t N>
using ReverseArray = typename ReverseArrayHelper<T, N>::type;
// 编译期初始化倒序数组的辅助函数
template <typename T, size_t... I>
constexpr std::array<T, sizeof...(I)> createReverseArray(std::integer_sequence<size_t, I...>) {
// 元素为 N-1-I(如 I=0→4, I=1→3...,对应 N=5)
return {{static_cast<T>(sizeof...(I)-1 - I)...}};
}
template <typename T, size_t N>
constexpr ReverseArray<T, N> makeReverseArray() {
return createReverseArray<T>(std::make_integer_sequence<size_t, N>{});
}
int main() {
using ReversedArrayType = ReverseArray<int, 5>;
constexpr ReversedArrayType arr = makeReverseArray<int,5>();
for (auto v : arr) {
std::cout << v << " "; // 输出:4 3 2 1 0
}
std::cout << std::endl;
return 0;
}
2、SFINAE
SFINAE即Substitution Failure Is Not An Error,即替换失败不是错误。当我们实例化模板时,若遇到模板参数的替换导致了编译错误,编译器不会报错,而会忽略这个错误,去尝试其他可能的特化版本。
c++20之后,(Concepts)替代绝⼤部分的SFINAE。
2. 1.基于 decltype 的表达式检测
// 版本1:仅适⽤于可递增的类型(如 int)
template<typename T>
//检测类型支不支持++x,如果支持调用第一个,如果不支持调用第二个
auto foo(T x) -> decltype(++x, void()) {
std::cout << "foo(T): " << x << " (can be incremented)\n";
}
// C++17 使⽤void_t优化上⾯的写法
template<typename T>
auto foo(T x) -> std::void_t<decltype(++x)> {
std::cout << "foo(T): " << x << " (can be incremented)\n";
}
// 版本2:回退版本
void foo(...) {
std::cout << "foo(...): fallback (cannot increment)\n";
}
int main() {
foo(42); // 调⽤版本1(int ⽀持 ++x)
foo(std::string("1111")); // 调⽤版本2(string 不⽀持 ++x)
return 0;
}
2. 2.std::enable_if 与模板参数筛选
#include <type_traits>
template <typename T>
typename std::enable_if<std::is_integral<T>::value>::type print_type(T) {
std::cout << "Integral type" << std::endl;
}
//也可以使用这种写法
template <typename T>
typename std::enable_if_t<std::is_integral_v<T>,T>
print_type(T) {
std::cout << "Integral type" << std::endl;
}
template <typename T>
typename std::enable_if<!std::is_integral<T>::value>::type print_type(T) {
std::cout << "Non-integral type" << std::endl;
}
int main() {
print_type(42); // Integral type
print_type(3.14); // Non-integral type
}
3、现代C++对模板元编程的增强
3.1constexpr 函数(c++11)
允许我们定义在编译期执行的函数,阶乘函数可简化:
constexpr unsigned int factorial(unsigned int n) {
return n <= 1? 1 : n * factorial(n - 1);
}
constexpr unsigned int fact5 = factorial(5);
3.2 if constexpr(C++17)
编译期条件分支语句。可在编译期根据条件选择不同的代码分支,未选中的分支不会被编译。
#include <iostream>
#include <type_traits>
using namespace std;
template <typename T>
auto process(T value) {
if constexpr (std::is_integral_v<T>) {
return value * 2;
}
else if constexpr (std::is_floating_point_v<T>) {
return value / 2;
}
else {
return value;
}
}
int main() {
int a = 5;
float b = 3.5;
double c = 2.7;
cout << process(a) << endl; // Output: 10
cout << process(b) << endl; // Output: 1.75
cout << process(c) << endl; // Output: 2.7
return 0;
}
3.3 Concepts(c++20)
#include <concepts>
template <typename T>
concept Arithmetic = requires(T a, T b) {
{ a + b } -> std::same_as<T>;
};
// 使用Concept约束模板
template <Arithmetic T>
T add(T a, T b) {
return a + b;
}
Arithmetic概念定义了类型T必须支持加法操作,并且加法的结果类型必须与T相同
部分内容参考:
1.C++模板元编程学习
2. 快手C++一面:SFINAE 和模板元编程是怎么实现的?
更多推荐


所有评论(0)