c++ 学习笔记之 模板元编程

文章目录

  • 1、模板元编程(TMP)
    • [1.1 编译期常量(static constexpr)](#1.1 编译期常量(static constexpr))
    • [1.2 模板特化与条件分支](#1.2 模板特化与条件分支)
    • [1.3 类型萃取](#1.3 类型萃取)
    • [1.4 模板递归](#1.4 模板递归)
  • 2、SFINAE
    • [2. 1.基于 decltype 的表达式检测](#2. 1.基于 decltype 的表达式检测)
    • [2. 2.std::enable_if 与模板参数筛选](#2. 2.std::enable_if 与模板参数筛选)
  • 3、现代C++对模板元编程的增强
    • [3.1constexpr 函数(c++11)](#3.1constexpr 函数(c++11))
    • [3.2 if constexpr(C++17)](#3.2 if constexpr(C++17))
    • [3.3 Concepts(c++20)](#3.3 Concepts(c++20))

1、模板元编程(TMP)

模板元编程是 C++ 中极具特色的高级编程范式 ------利用模板编译期实例化的特性,将计算或 逻辑从运行时转移到编译期完成。

看一个例子:

cpp 复制代码
// 主模板:递归计算 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中数据存储的核心方式,能保证值在编译期确定:

cpp 复制代码
// 编译期常量
template <int N>
struct ConstValue {
    static constexpr int value = N * 2; // 编译期计算
};
// 使用:直接取 value,无运行时计算
constexpr int x = ConstValue<5>::value; // x=10

1.2 模板特化与条件分支

通过特化模板与主模板配合,能够在编译器实现条件判断。

cpp 复制代码
//判断是否是指针类型
// 主模板:默认非指针类型
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 类型萃取

cpp 复制代码
// 主模板:默认类型
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> 头⽂件中提供了⼤量类型萃取⼯具:

cpp 复制代码
#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 模板递归

cpp 复制代码
#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 的表达式检测

cpp 复制代码
// 版本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 与模板参数筛选

cpp 复制代码
#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)

允许我们定义在编译期执行的函数,阶乘函数可简化:

cpp 复制代码
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)

编译期条件分支语句。可在编译期根据条件选择不同的代码分支,未选中的分支不会被编译。

cpp 复制代码
#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)

cpp 复制代码
#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++模板元编程学习

  1. 快手C++一面:SFINAE 和模板元编程是怎么实现的?
相关推荐
Larry_Yanan9 小时前
Qt多进程(九)命名管道FIFO
开发语言·c++·qt·学习·ui
聆风吟º9 小时前
【C++藏宝阁】C++入门:命名空间(namespace)详解
开发语言·c++·namespace·命名空间
ybb_ymm9 小时前
尝试新版idea及免费学习使用
java·学习·intellij-idea
九成宫9 小时前
计算机网络期末复习——第3章:运输层 Part One
网络·笔记·计算机网络·软件工程
飞鹰519 小时前
CUDA入门:从Hello World到矩阵运算 - Week 1学习总结
c++·人工智能·性能优化·ai编程·gpu算力
君鼎9 小时前
计算机网络第五章:传输层学习总结
学习·计算机网络
xian_wwq9 小时前
【学习笔记】网络安全L3级模型功能解析
笔记·学习·安全
逑之9 小时前
C语言笔记2:C语言数据类型和变量
c语言·开发语言·笔记
荒诞硬汉9 小时前
递归的学习
java·学习