当前位置: 首页 > news >正文

C++模板元编程学习

        本期我们来学习现代C++中一个十分重要的东西:模板元编程。学习模板元编程是学习现代C++不可或缺的一部分。本期就让我们来学习一下

        作者的个人gitee如下:楼田莉子/CPP代码学习喜欢请点个赞谢谢

目录

模板元编程介绍

模板元编程的核心概念

模板元编程的基本语法

        基本模板结构

        编译期值计算

        编译期类型计算

        类型萃取

        SFINAE

现代C++对模板元编程的优化和强化

        constexpr函数(C++11起)

        变量模板(C++14起)

        if constexpr (C++17)

        折叠表达式(C++17)

        概念(C++20)

        模块(C++20)

模板元编程优缺点分析

        优点:

        缺点:


模板元编程介绍

        现代C++的⼀个进化⽅向就是在编译时做更多的⼯作,模板元编程(Template Metaprogramming, TMP)是C++中⼀种利⽤模板机制在编译期进⾏计算和代码⽣成的⾼级技术。它通过模板特化、递归实例化和类型操作,在编译时完成传统运⾏时才能处理的任务,从⽽实现零运⾏时开销的优化。下⾯我将从核⼼概念、关键技术、现代发展等⽅⾯全⾯讲解C++模板元编程。

        模板元编程最早由Erwin Unruh在1994年发现,他展⽰了如何让编译器在错误信息中输出素数序列,随后被Todd Veldhuizen和David Vandevoorde等⼈系统化,Todd Veldhuizen证明了C++模板具有图灵完备性,理论上能执⾏任何计算任务,它遵循函数式编程范式,模板参数作为不可变数据参与编译期计算

模板元编程的核心概念

        模板元编程的本质是将计算从运⾏时转移到编译期,利⽤编译器作为"计算引擎"⽣成⾼效代码。其核⼼思想包括:

1. 编译期计算 :所有运算在编译阶段完成,结果直接嵌⼊最终程序

2. 类型操作 :通过模板参数推导和类型萃取(Type Traits)操作类型

3. 递归模板实例化 :通过递归展开实现循环和条件逻辑

4. 零运⾏时开销 :结果在编译期确定,不增加程序运⾏负担

模板元编程的基本语法

        基本模板结构

        模板元编程主要使⽤类模板(⽽⾮函数模板),因为类模板可以包含类型成员和静态成员,再利⽤模板特化和递归实现。

template <typename T>
struct MyTemplate 
{using type = T; // 类型成员static const int value = 42; // 静态成员
}

        编译期值计算

        最简单的模板元编程是编译期计算阶乘

#include <iostream>
#include <type_traits>
using namespace std;
//编译期数值计算
template <unsigned int N>
struct Factorial 
{static const unsigned int value = N * Factorial<N - 1>::value;
};
// 终⽌条件特化
template <>
struct Factorial<0> 
{static const unsigned int value = 1;
};
int main()
{constexpr unsigned int fact5 = Factorial<5>::value; // 编译时计算出120cout << "5的阶乘是: " << fact5 << endl;return 0;
}

        编译期计算值和运行时计算值的性能对比:

#include <iostream>
#include <chrono>
#include <type_traits>
using namespace std;
using namespace std::chrono;// 编译期数值计算
template <unsigned int N>
struct Factorial
{static const unsigned int value = N * Factorial<N - 1>::value;
};// 终止条件特化
template <>
struct Factorial<0>
{static const unsigned int value = 1;
};// 运行时递归计算的阶乘函数
unsigned int factorial_recursive(unsigned int n) {if (n == 0) return 1;return n * factorial_recursive(n - 1);
}// 运行时迭代计算的阶乘函数(通常更快)
unsigned int factorial_iterative(unsigned int n) {unsigned int result = 1;for (unsigned int i = 1; i <= n; ++i) {result *= i;}return result;
}int main() {constexpr unsigned int n = 15; // 测试的阶乘数// 编译期计算auto start_compile = high_resolution_clock::now();constexpr unsigned int fact_compile = Factorial<n>::value;auto end_compile = high_resolution_clock::now();auto duration_compile = duration_cast<nanoseconds>(end_compile - start_compile);cout << "编译期计算:" << endl;cout << n << "的阶乘是: " << fact_compile << endl;cout << "编译期计算时间: " << duration_compile.count() << " 纳秒" << endl;cout << endl;// 运行时递归计算auto start_recursive = high_resolution_clock::now();unsigned int fact_recursive = factorial_recursive(n);auto end_recursive = high_resolution_clock::now();auto duration_recursive = duration_cast<nanoseconds>(end_recursive - start_recursive);cout << "运行时递归计算:" << endl;cout << n << "的阶乘是: " << fact_recursive << endl;cout << "运行时递归计算时间: " << duration_recursive.count() << " 纳秒" << endl;cout << endl;// 运行时迭代计算auto start_iterative = high_resolution_clock::now();unsigned int fact_iterative = factorial_iterative(n);auto end_iterative = high_resolution_clock::now();auto duration_iterative = duration_cast<nanoseconds>(end_iterative - start_iterative);cout << "运行时迭代计算:" << endl;cout << n << "的阶乘是: " << fact_iterative << endl;cout << "运行时迭代计算时间: " << duration_iterative.count() << " 纳秒" << endl;cout << endl;// 多次运行以获得更准确的时间测量(迭代版本)const int iterations = 1000000;auto start_iterative_multi = high_resolution_clock::now();for (int i = 0; i < iterations; ++i) {factorial_iterative(n);}auto end_iterative_multi = high_resolution_clock::now();auto duration_iterative_multi = duration_cast<microseconds>(end_iterative_multi - start_iterative_multi);cout << "多次运行测试 (" << iterations << " 次迭代):" << endl;cout << "总时间: " << duration_iterative_multi.count() << " 微秒" << endl;cout << "平均每次计算时间: " << static_cast<double>(duration_iterative_multi.count()) / iterations << " 微秒" << endl;cout << endl;// 验证结果是否正确cout << "结果验证:" << endl;cout << "编译期 == 递归: " << (fact_compile == fact_recursive ? "正确" : "错误") << endl;cout << "编译期 == 迭代: " << (fact_compile == fact_iterative ? "正确" : "错误") << endl;cout << "递归 == 迭代: " << (fact_recursive == fact_iterative ? "正确" : "错误") << endl;return 0;
}

        结果为:

        编译期类型计算

        编译时获取或修改类型信息的操作

//boogiepop.h
#pragma once
namespace boogiepop
{// 主模板template<typename T>struct is_pointer {static constexpr bool value = false;};// 针对指针类型的偏特化template<typename T>struct is_pointer<T*> {static constexpr bool value = true;};// 主模板,默认情况类型不同template<typename T, typename U>struct is_same {static constexpr bool value = false;};// 特化版本,当两个类型相同时template<typename T>struct is_same<T, T> {static constexpr bool value = true;};// 移除 const// 主模板,默认情况下不改变类型template <typename T>struct remove_const {using type = T;};// 针对 const T 的特化版本,移除 consttemplate <typename T>struct remove_const<const T> {using type = T;};// 移除 指针template <typename T>struct remove_pointer {using type = T;};template <typename T>struct remove_pointer<T*> {using type = T;};template <typename T>struct remove_pointer<T* const> {using type = T;};void func(){static_assert(is_pointer<int*>::value, "int* is a pointer");// static_assert(bit::is_pointer<int>::value, "int is not a pointer");static_assert(is_same<int, int>::value, "int and int should be thesame");// static_assert(is_same<int, float>::value, "int and float should bedifferent");static_assert(is_same<remove_pointer<int*>::type, int>::value, "intand int should be the same");static_assert(is_same<remove_const<const int>::type, int>::value, "intand int should be the same");}
}
//test.cpp
#include <iostream>
#include <type_traits>
#include"boogiepop.h"
using namespace std;
//编译期间类型计算
int main()
{boogiepop::func();return 0;
}

        类型萃取

        类型萃取是C++模板元编程中的核⼼技术,它允许在编译时检查和修改类型特性。C++11版本开始标准库在 <type_traits> 头⽂件中提供了⼤量类型萃取⼯具。类型萃取是通过模板特化技术实

现的编译期类型操作,主要⽤途包括:检查类型特性、修改/转换类型、根据类型特性进⾏编译期

分⽀。

        官方文档:标准库标题<type_traits> (C++11) - cppreference.com

#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;

        类型萃取库的⼀些使⽤样例展⽰

#include<iostream>
//using namespace std;
template<typename T>
void process(T value) 
{if constexpr (std::is_pointer_v<T>) {// 指针类型的处理std::cout << "Processing pointer: " << *value << std::endl;} else if constexpr (std::is_integral_v<T>) {// 整数类型的处理std::cout << "Processing integer: " << value * 2 << std::endl;} else if constexpr (std::is_floating_point_v<T>) {// 浮点类型的处理std::cout << "Processing float: " << value / 2.0 << std::endl;} else{// 默认处理std::cout << "Processing unknown type" << std::endl;}
}
#if defined(_WIN32)#include<winsock2.h>using socket_t = SOCKET;
#elseusing socket_t = int;
#endif
template<typename T>
void close_handle(T handle) {if constexpr (std::is_same_v<T, SOCKET>) {closesocket(handle);} else{close(handle);}
} 
int main()
{// 使⽤int i = 42;process(i); // Processing integer: 84process(&i); // Processing pointer: 42process(3.14); // Processing float: 1.57process("hello"); // Processing unknown typereturn 0;
}

        结果为:

        SFINAE

        SFINAE是Substitution Failure Is Not An Error⾸字⺟缩写,意思是"替换失败不是错误",在模板参数推导或替换时,如果某个候选模板导致编译错误(如类型不匹配、⽆效表达式等),编译器不会直接报错,⽽是跳过该候选,尝试其他可⾏的版本。如果最后都没匹配到合适的版本,再进⾏报错。

        SFINAE的语法相对复杂难理解,在C++20以后,考虑使⽤概念(Concepts)替代绝⼤部分的

SFINAE,所以SFINAE我们了解⼀下,后续我们重点学习C++20的概念。

        SFINAE经典应⽤场景函数重载

// 版本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;
}

        官方文档:std::enable_if - cppreference.com

std::enable_if

是⼀个类型萃取,它的功能和实现具体看上⾯⽂档,它是 SFINAE 的典型应⽤,⽤于在编译时启⽤/禁⽤函数模板

// 对于整数类型启⽤此重载
template<typename T>
typename std::enable_if_t<std::is_integral_v<T>, T>
add_one(T t) {return t + 1;
} 
// 对于浮点类型启⽤此重载
template<typename T>
typename std::enable_if_t<std::is_floating_point_v<T>, T>
add_one(T t) {return t + 2.0;
} 
// 模板参数的检查
template<typename T,typename = std::enable_if_t<std::is_integral_v<T>>>
void process_integer(T value) {// 只接受整数类型
} 
int main() {std::cout << add_one(5) << "\n"; // 调⽤整数版本,输出6std::cout << add_one(3.14) << "\n"; // 调⽤浮点版本,输出4.14// add_one("hello"); // 编译错误,没有匹配的重载process_integer(1);// process_integer(1.1); // 编译错误,没有匹配的重载return 0;
}

现代C++对模板元编程的优化和强化

        constexpr函数(C++11起)

        C++11/14/17/20逐步增强了constexpr能⼒,许多模板元编程任务可以⽤constexpr函数替代,允许函数和变量在编译期求值,替代部分传统模板元编程的递归实例化,constexpr简化了很多相对复杂的模板元编程实现。

constexpr int factorial(int n) {return n <= 1 ? 1 : n * factorial(n - 1);
} 
constexpr int x = factorial(5); // 120

        变量模板(C++14起)

        变量模板直接定义编译期常量值,有了变量模板类型萃取的⼀些取值特性就可以简化⼀些,如is_integral_v等

#include <iostream>
#include <type_traits>
template<typename T>
constexpr T pi = T(3.1415926535897932385);
template< class T >
constexpr bool is_integral_v = std::is_integral<T>::value;
int main() {float f = pi<float>; // 单精度πdouble x = pi<double>; // 双精度π// 使⽤不同精度的πstd::cout.precision(6);std::cout << "float π: " << f << std::endl;std::cout.precision(10);std::cout << "double π: " << x << std::endl;return 0;
}

        结果为:

        if constexpr (C++17)

        在编译期根据条件选择代码路径,避免⽣成⽆效代码分⽀,简化SFINAE和模板特化的复杂逻辑,提升可读性。

#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: 10cout << process(b) << endl; // Output: 1.75cout << process(c) << endl; // Output: 2.7return 0;
}

        折叠表达式(C++17)

        简化可变参数模板的参数包展开操作,具体细节我会在C++17章节中讲解

template<typename... Args>
void print(Args&&... args) {(std::cout << ... << args) << '\n';// 等价于 (((std::cout << arg1) << arg2) << ...) << argN;
}

        概念(C++20)

        概念(concept)是C++20引⼊的模板参数约束机制,取代SFINAE的复杂约束语法。具体细节C++20章节中细讲

// 定义⼀个要求T是整形的概念
template< class T >
concept Integral = std::is_integral_v<T>;
// 1、模板参数后直接使⽤
template<Integral T>
void f1(T x)
{std::cout << "有 concepts 约束" << std::endl;
}

        模块(C++20)

        C++20引⼊的模块(Modules)是C++语⾔的⼀项重⼤⾰新,旨在解决传统头⽂件包含机制(#include)的诸多问题。其中⼀个问题就是,每次包含头⽂件时,编译器都需要重新解析其内容,导致编译时间⼤幅增加。模块引⼊以后可以⼤⼤缩短编译时间,具体特性,我将在后⾯C++20部分内容再细讲。

        C++20 Modules 代码在 Alibaba Hologres 主线上已稳定运⾏⼀年半以上,并减少了 42% 的编译时间。

        相关的介绍可以参考这篇文章:告别头文件,编译效率提升 42%!C++ Modules 实战解析 | 干货推荐_c++_OpenAnolis小助手_InfoQ写作社区

        模板元编程的⼀个重⼤问题就是让项⽬的编译时间变⻓,所以模块的引⼊可以很好的缓解这个问题。

模板元编程优缺点分析

        优点:

1. 零运⾏时开销:所有计算在编译期完成

2. 类型安全:编译期类型检查

3. ⾼度抽象:可构建灵活通⽤的库

        缺点:

1. 编译时间⻓:复杂的模板实例化会增加编译时间

2. 学习成本增加:很多模板元编程的写法晦涩难懂,⼤⼤增加学习成本

3. 错误信息晦涩:模板错误通常难以理解

4. 调试困难:难以调试编译期计算

        本期关于C++模板元编程的内容到这里就结束了,喜欢请点个赞谢谢

封面图自取:

http://www.dtcms.com/a/557523.html

相关文章:

  • 虚机镜像创建方法系统化分析:操作路径、技术原理与场景适配
  • 微端网站开发阿里云服务器学生优惠
  • 23 种经典设计模式的名称、意图及适用场景概述
  • Snapan项目--预览文件梳理
  • Readest0.9.90 | 一款好用的开源阅读器,提供划词翻译、高亮笔记、语音朗读等功能,内置大量朗读引擎和云同步
  • [Dify 实战] Dify 与 LangChain 的区别与组合方式:从工作流到编排框架的深度解析
  • internet网站建设试卷做兼职的网站打字员
  • 济南网站建设多少钱官方传奇手游下载
  • H.264 编码原理与 RTP/RTSP 传输流程详解
  • 包装设计的网站wordpress禁用导航栏代码
  • 非洲秃鹫优化算法(AVOA)的详细原理和数学公式
  • 怎样建立网站卖东西百度打开百度搜索
  • 淮安网站优化营销案例分享
  • 高效简便的网站开发网站服务器迁移
  • html5与android之间相互调用
  • 用一份 YAML 编排实时数据集成Flink CDC 工程实践
  • 全志SPI-NG框架使用说明
  • 域名及网站建设实训wordpress 不能自定义主题
  • 新河网站网站后台默认用户名
  • 第十二章:终极叩问:我是谁,我往何方?(1)
  • JAVA高频面试题
  • 如何制作一个自己的网站?安全教育平台登录入口网址
  • 软考 系统架构设计师系列知识点之杂项集萃(184)
  • Redis性能提升秘籍:大Key与热点Key优化实战
  • 大专物流管理专业职业发展指南
  • 徐州网站制作机构做猎头需要用到的网站
  • 石家庄做网站制作公司做公司点评的网站
  • Git指令集
  • 基于边缘信息提取的遥感图像开放集飞机检测方法
  • 前端基础知识---Promise