C++模板编程:从通用代码生成到编译期多态实战

发布时间:2026/8/27 21:28:25

C++模板编程:从通用代码生成到编译期多态实战
1. 从“重复造轮子”到“一劳永逸”的思维转变如果你写过C大概率遇到过这种场景你需要一个函数来比较两个整数的大小于是你写了个max(int a, int b)。过两天项目里又要比较两个浮点数你又得写个max(float a, float b)。代码逻辑一模一样只是参数类型不同。这时候你心里可能会嘀咕“这不就是复制粘贴改个类型吗太蠢了。” 恭喜你你已经摸到了C模板Template设计初衷的门槛。模板本质上是一种“代码生成器”。它允许你编写与类型无关的通用代码让编译器在编译时根据你实际使用的类型自动为你生成一份类型特化的代码。这不仅仅是语法糖而是一种彻底的编程范式转变——从“针对具体类型编写具体函数/类”转变为“针对一类概念Concept编写通用蓝图”。今天我们就来彻底拆解C模板从为什么需要它到怎么用好它再到如何避开它那些著名的“坑”。2. 模板的核心机制编译时的“代码工厂”理解模板首先要抛弃运行时思维建立编译时Compile-time思维。模板的所有“魔法”都发生在代码被编译成机器码之前。2.1 函数模板你的第一个通用工具让我们从最直观的函数模板开始。上面提到的max函数用模板可以这样写template typename T T max(T a, T b) { return (a b) ? a : b; }这短短几行就是一个完整的函数模板。我们来拆解一下template typename T这是一个模板声明。它告诉编译器“嘿我接下来要定义一个模板其中有一个尚未确定的类型我暂时叫它T。”typename关键字可以用class替代在这里两者含义完全相同但typename更直观表示“一个类型名”。T max(T a, T b)这里T就是一个“占位符”。它代表某种类型具体是什么由调用者决定。当你这样调用时int i max(10, 20); // 调用 maxint(int, int) double d max(5.5, 3.14); // 调用 maxdouble(double, double)编译器在编译到这两行时会进行一个叫做“模板实例化”Template Instantiation的过程推导类型对于max(10, 20)编译器看到实参是int于是推导出T应该是int。生成代码编译器拿着int这个类型去“填充”模板蓝图中的T生成一个实实在在的、参数和返回值都是int的max函数。这个过程就像用模具模板和具体的材料类型int压出一个零件函数maxint。最终你的程序里其实存在两个不同的max函数一个处理int一个处理double。它们是编译器自动为你生成的。注意这里有一个关键点模板要求类型T支持模板中使用的操作。对于max它使用了operator因此你传递给max的类型必须支持比较。如果你用自定义的类对象调用max但该类没有重载运算符编译就会失败。这是模板的“隐式接口”——代码对类型提出了要求。2.2 类模板构建通用容器和工具如果说函数模板让算法通用化那么类模板就让数据结构通用化。C标准库中最著名的std::vector、std::list、std::map全都是类模板。template typename T class MyVector { private: T* data; // 指针指向一块内存用于存放T类型的对象 size_t capacity; size_t size; public: MyVector() : data(nullptr), capacity(0), size(0) {} void push_back(const T value) { // ... 分配内存、构造对象等逻辑 // 这里需要对T类型的对象进行拷贝或移动 } T operator[](size_t index) { // 返回T类型对象的引用 return data[index]; } // ... 其他成员函数 };使用这个MyVector类模板MyVectorint intVec; // 实例化一个存储int的MyVector MyVectorstd::string strVec; // 实例化一个存储string的MyVectorMyVectorint和MyVectorstd::string是两个完全不同的类它们由同一个模板蓝图生成但彼此没有继承关系。编译器会为每一种你用到的类型组合生成一份独立的类代码。2.3 非类型模板参数将值也作为蓝图的一部分模板参数不一定非得是类型也可以是整型常量、指针或引用指向具有静态生命周期的对象。这让你能在编译时确定一些值从而实现更高效的代码。一个经典的例子是静态数组的封装template typename T, std::size_t N class StaticArray { private: T data[N]; // 数组大小N在编译时就确定了 public: constexpr std::size_t getSize() const { return N; } T operator[](std::size_t index) { return data[index]; } // ... }; StaticArraydouble, 100 sensorReadings; // 一个编译时大小固定为100的double数组这里N是一个非类型模板参数。它的好处是性能数组大小是编译期常量编译器可以进行更好的优化如循环展开。安全像getSize()这样的函数可以被声明为constexpr在编译期求值。类型化StaticArrayint, 10和StaticArrayint, 20是不同类型避免了无意中的混用。3. 模板的进阶玩法与元编程雏形当你掌握了基础就可以探索模板更强大的能力这些能力构成了C模板元编程Template Metaprogramming, TMP的基础。3.1 模板特化与偏特化提供特殊情况下的最优解通用蓝图很好但有时对于某些特定的类型你有更高效或不同的实现方式。这就是模板特化Specialization的用武之地。全特化为模板的所有参数指定具体的类型或值。// 通用的模板 template typename T class DataSerializer { public: static std::string serialize(const T obj) { // 通用序列化方法可能效率较低 return std::to_string(obj); // 假设T能转成string } }; // 全特化针对const char*类型 template class DataSerializerconst char* { public: static std::string serialize(const char* obj) { // 对于C风格字符串我们有更直接高效的方法 return std::string(obj); } }; // 使用 DataSerializerint::serialize(42); // 使用通用模板 DataSerializerconst char*::serialize(hello); // 使用特化版本偏特化只特化一部分模板参数或者对模板参数加上一些限制如它是指针、引用等。// 通用模板 template typename T class MyPointerWrapper { // 通用实现处理普通指针 }; // 偏特化针对所有类型的指针T* template typename T class MyPointerWrapperT* { // 针对指针类型的特殊实现例如可以自动处理解引用 }; // 偏特化针对两个类型参数的情况 template typename T1, typename T2 class MyPair { /* ... */ }; template typename T class MyPairT, T { // 当两个类型相同时的特化 // 例如可以额外实现一个比较相等的成员函数 };特化是C标准库中性能优化的关键。例如std::vectorbool就是一个著名的全特化它通过位压缩来节省空间但也因此带来了与非特化版本不同的接口行为比如返回的不是bool而是代理对象这是一个需要特别注意的“坑”。3.2 变参模板处理任意数量的参数C11引入了变参模板Variadic Templates允许模板接受任意数量、任意类型的参数。这是实现std::tuple、std::function以及完美转发等现代C特性的基石。// 递归终止函数 void print() { std::cout end\\n; } // 变参模板函数 template typename T, typename... Args // Args是一个模板参数包 void print(T first, Args... rest) { // rest是一个函数参数包 std::cout first ; print(rest...); // 递归展开参数包 } // 使用 print(1, 2.5, hello, a); // 输出1 2.5 hello a end其工作原理是递归实例化。print(1, 2.5, hello, a)会先处理第一个参数1然后将剩下的参数包2.5, hello, a递归调用print直到参数包为空调用无参数的终止函数。在类模板中变参模板可以用来构建元组Tupletemplate typename... Types class Tuple; // 前向声明 // 递归基空元组 template class Tuple {}; // 递归定义一个元素 剩余元素的元组 template typename Head, typename... Tail class TupleHead, Tail... : private TupleTail... { private: Head head; public: // ... 构造函数、get方法等 };这个递归继承的结构使得Tupleint, double, std::string在内存布局上大致等同于一个包含int、double、std::string的结构体。3.3 SFINAE与类型萃取编译时的类型侦探SFINAESubstitution Failure Is Not An Error替换失败并非错误是模板元编程中一个核心规则。它允许编译器在重载决议或特化选择时忽略那些会导致编译错误的候选而不是直接报错。结合std::enable_if我们可以根据类型的特性来启用或禁用某个模板。// 只有T是整数类型时这个函数模板才参与重载决议 template typename T typename std::enable_ifstd::is_integralT::value, void::type processIntegral(T value) { std::cout Processing integral: value std::endl; } // 只有T是浮点类型时这个函数模板才参与重载决议 template typename T typename std::enable_ifstd::is_floating_pointT::value, void::type processIntegral(T value) { std::cout Processing floating point: value std::endl; } processIntegral(10); // OK调用第一个 processIntegral(3.14); // OK调用第二个 // processIntegral(hello); // 编译错误没有匹配的函数std::enable_if条件, 返回类型的工作原理是如果条件为真它内部有一个type成员 typedef 为指定的返回类型如果条件为假它没有type成员。当编译器尝试为processIntegral(hello)匹配模板时两个版本的std::enable_if条件都不满足导致“替换失败”根据SFINAE规则这两个候选被忽略。由于没有其他候选最终编译错误。类型萃取Type Traits是SFINAE的常用伙伴type_traits头文件提供了大量工具如std::is_pointer、std::is_class、std::remove_reference等用于在编译时查询和修改类型信息。4. 模板实战从标准库组件到自定义设计理解了原理我们看看模板在实战中如何大放异彩以及我们自己设计模板时要注意什么。4.1 剖析std::vector一个典型的类模板std::vector的声明大致如下template class T, class Allocator std::allocatorT class vector;它有两个模板参数T元素类型。Allocator内存分配器默认为std::allocatorT。这是一个策略类Policy Class允许用户自定义内存分配行为。这是模板用于实现“策略模式”的经典案例将算法容器管理与策略内存分配解耦。当你写下std::vectorint vec;时你得到的是一个经过高度优化的、类型安全的动态数组。编译器为你生成的vectorint代码其operator[]返回的是intpush_back接受的是const int或int移动语义。所有的类型信息在编译期就已确定没有任何运行时类型检查的开销。4.2 设计一个简单的智能指针模板让我们尝试设计一个简化版的std::unique_ptr来体会模板如何管理资源。template typename T class SimpleUniquePtr { private: T* ptr_; public: // 显式构造函数接管原始指针 explicit SimpleUniquePtr(T* ptr nullptr) : ptr_(ptr) {} // 禁止拷贝独占所有权 SimpleUniquePtr(const SimpleUniquePtr) delete; SimpleUniquePtr operator(const SimpleUniquePtr) delete; // 允许移动转移所有权 SimpleUniquePtr(SimpleUniquePtr other) noexcept : ptr_(other.ptr_) { other.ptr_ nullptr; } SimpleUniquePtr operator(SimpleUniquePtr other) noexcept { if (this ! other) { delete ptr_; ptr_ other.ptr_; other.ptr_ nullptr; } return *this; } // 析构函数释放资源 ~SimpleUniquePtr() { delete ptr_; } // 操作符重载 T operator*() const { return *ptr_; } T* operator-() const { return ptr_; } T* get() const { return ptr_; } // 释放所有权 T* release() { T* old ptr_; ptr_ nullptr; return old; } // 重置指针 void reset(T* ptr nullptr) { delete ptr_; ptr_ ptr; } };这个简单的模板展示了几个关键点资源管理模板将资源T*的生命周期与对象绑定。类型安全operator*返回Toperator-返回T*确保了通过智能指针访问的对象类型正确。可定制析构实际的std::unique_ptr还有一个模板参数用于自定义删除器Deleter这也是通过模板策略实现的可以轻松管理malloc/free或文件句柄等资源。4.3 模板与多态编译期多态 vs 运行期多态面向对象通过继承和虚函数实现运行期多态动态多态。而模板提供的是编译期多态静态多态或参数化多态。特性运行期多态 (虚函数)编译期多态 (模板)绑定时间运行时编译时机制虚函数表 (vtable)模板实例化性能有间接调用开销无额外开销可能被内联灵活性基类指针可指向任何派生类对象类型必须编译期确定但接口更灵活鸭子类型代码膨胀一份虚表代码复用性好为每种类型生成一份代码可能导致二进制体积增大错误检查运行时可能遇到类型错误编译时检查更安全如何选择当需要处理的对象类型集合在运行时可能变化或者需要通过统一的基类接口来操作不同类型时使用运行期多态。当类型在编译期已知且追求极致性能或者需要为不同类型提供高度定制化的行为时使用模板。在现代C中两者常结合使用。例如标准库的迭代器就是基于模板的编译期多态而像std::function这样的类型擦除容器内部则结合了模板和运行期多态。5. 模板的“坑”与最佳实践模板功能强大但也伴随着复杂性。下面是一些常见的陷阱和应对策略。5.1 编译错误信息晦涩难懂这是模板最著名的缺点。一个简单的类型不匹配可能导致编译器输出数十甚至上百行的错误信息其中大部分是模板实例化的层层追踪。应对策略从最后一行看起编译器错误信息通常像栈展开最后一行往往是根源。使用静态断言static_assert进行友好提示在模板代码中提前检查类型约束。template typename T void safeSquare(T x) { static_assert(std::is_arithmeticT::value, safeSquare requires an arithmetic type.); return x * x; } safeSquare(hello); // 编译错误但信息清晰safeSquare requires an arithmetic type.C20 Concepts概念这是解决此问题的终极语言特性。它允许你为模板参数指定命名的约束使接口清晰错误信息友好。template std::integral T // 要求T是整型 T bitwise_not(T a) { return ~a; } bitwise_not(10.5); // 错误信息会直接指出10.5不满足std::integral约束。5.2 代码膨胀Code Bloat每实例化一种类型编译器就会生成一份该类型的代码。如果模板代码体很大且为许多不同类型实例化最终的可执行文件可能会显著增大。应对策略提取非类型相关代码将模板类中与类型无关的辅助函数移出成为独立的非模板函数。使用共同基类如果一组模板类有大量相同代码可以考虑让它们继承自一个非模板的基类将公共代码放在基类中。显式实例化在大型项目中可以在一个.cpp文件中显式实例化你需要的特定类型然后在头文件中使用extern template声明来阻止其他编译单元再次实例化。// my_template.cpp #include my_template.h template class MyTemplateint; // 显式实例化 template class MyTemplatedouble; // my_template.h template typename T class MyTemplate { /* ... */ }; extern template class MyTemplateint; // 外部实例化声明 extern template class MyTemplatedouble;5.3 两阶段查找与依赖名称在模板定义中编译器会进行“两阶段查找”第一阶段模板定义时查找不依赖于模板参数的名称如非成员函数、类型。第二阶段模板实例化时查找依赖于模板参数的名称如T::some_type或t.some_member()。这会导致一个常见问题如果某个名称依赖于模板参数你必须用typename或template关键字来提示编译器它是什么。template typename T void foo() { typename T::value_type x; // value_type 是依赖于T的类型需要typename T::static_func(); // 非类型不需要 T::template nested_templateint obj; // nested_template 是嵌套模板需要template }5.4 模板的分离编译问题通常模板的定义而不仅仅是声明必须放在头文件中。因为编译器在实例化模板时需要看到完整的定义。这违反了传统的“.h声明.cpp定义”的分离编译模式。解决方案头文件包含所有定义最简单直接也是大多数标准库的做法。显式实例化如上文所述将模板定义放在.cpp中并显式实例化特定类型。使用export关键字已弃用C98/03曾引入但编译器支持极少在C11中已不再要求C20已移除。5.5 最佳实践总结从简单开始先写一个具体类型的版本确保逻辑正确再将其“模板化”。明确约束使用static_assert或 C20 Concepts 明确模板对类型的要求让接口更清晰错误更友好。注意通用性模板代码应尽可能通用避免对类型做不必要的假设。使用std::begin/std::end而非.begin()/.end()以支持原生数组。善用标准库很多通用算法和数据结构std::sort,std::find,std::vector,std::map都已模板化无需重复发明。性能与清晰度的权衡模板元编程TMP能在编译期完成计算带来零开销抽象但也会大幅增加编译时间和代码复杂度。除非在性能关键路径否则优先考虑代码可读性和可维护性。测试要充分模板代码需要针对你将使用的各种类型进行测试包括自定义类型、指针、常量类型等以确保其行为符合预期。模板是C从“带类的C”升华为一门强大抽象语言的核心特性之一。它带来的零开销抽象、编译期计算和类型安全是C高性能的基石。虽然学习曲线陡峭但一旦掌握你将拥有构建高度灵活、高效复用库组件的能力。从理解“编译器替我们生成代码”这一基本图景开始逐步深入特化、SFINAE、变参模板等高级主题并时刻警惕那些晦涩的错误和潜在的膨胀问题你就能真正驾驭这把“双刃剑”写出既通用又高效的C代码。

相关新闻

不删数据!C盘爆满清理与无损扩容实战指南

不删数据!C盘爆满清理与无损扩容实战指南

2026/8/27 21:28:25

C 盘爆满这个事,几乎每个用 Windows 的人都会遇到。尤其是系统盘从 C 盘变红,再到完全没空间,开机慢、软件卡、更新失败全赶一起。网上不少教程上来就让删文件,但很多人的桌面、文档、下载里全是重要的项目资料,根本不…

黄河水沙监测数据建模与物理可解释分析实战

黄河水沙监测数据建模与物理可解释分析实战

2026/8/27 21:28:25

1. 这不是一道“赛题”,而是一份黄河水沙的体检报告单 2023年高教社杯全国大学生数学建模竞赛E题——“黄河水沙监测数据分析模型和代码”,表面看是高校竞赛里一道带编号的题目,但在我连续三年参与黄河中游水文站数据核查、协助地方水保部门做…

上下文工程实战:用book-to-skill把一本书变成按需加载的技能

上下文工程实战:用book-to-skill把一本书变成按需加载的技能

2026/8/27 21:28:25

实际使用 AI 编程助手时,最常遇到的问题不是单次提问没答对,而是任务进行到一半,上下文窗口爆了。你刚把一本书、一份几千行的接口文档或者一套团队规范全部塞进对话里,后续请求就开始变慢、变不稳定,甚至出现“已进行…

【单片机毕业设计】基于 STM32 的红外感应智能出水硬件平台设计 基于 STM32 的手动自动双模式智能饮水机控制系统(012105)

【单片机毕业设计】基于 STM32 的红外感应智能出水硬件平台设计 基于 STM32 的手动自动双模式智能饮水机控制系统(012105)

2026/8/27 22:38:28

博主介绍:✌️码农一枚 ,专注于大学生项目实战开发、讲解和毕业🚢文撰写修改等。全栈领域优质创作者,博客之星、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于嵌入式单片机,Java、小程序技术领域和毕业项目实战 ✌️…

【单片机毕业设计】基于 STM32 的温湿度光照监测智能储物柜体设计 基于 STM32 的语音识别智能除湿消毒柜体控制系统设计(012005)

【单片机毕业设计】基于 STM32 的温湿度光照监测智能储物柜体设计 基于 STM32 的语音识别智能除湿消毒柜体控制系统设计(012005)

2026/8/27 22:38:28

博主介绍:✌️码农一枚 ,专注于大学生项目实战开发、讲解和毕业🚢文撰写修改等。全栈领域优质创作者,博客之星、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于嵌入式单片机,Java、小程序技术领域和毕业项目实战 ✌️…

Java增量更新实战:时间戳陷阱与高可靠同步方案

Java增量更新实战:时间戳陷阱与高可靠同步方案

2026/8/27 22:38:28

1. 增量更新不是“定时全量刷一遍”,而是让系统学会“只动变化的那几行”你有没有遇到过这样的场景:一个电商后台每天要同步百万级商品数据,但实际每天只有不到0.3%的商品价格或库存发生变动;或者一个用户行为日志系统&#xff0c…

MATLAB与LINGO协同求解钢材切割下料:从优化模型到车间实战

MATLAB与LINGO协同求解钢材切割下料:从优化模型到车间实战

2026/8/27 22:38:28

1. 项目概述与问题再审视 上次我们聊了聊钢材切割下料问题的基本模型和用MATLAB、LINGO求解的思路,算是把骨架搭起来了。但后台和评论区收到不少反馈,很多朋友在实际套用自己的数据时遇到了各种“坑”:比如模型跑起来慢得离谱,结果…

基于Matlab的香烟过滤嘴扩散-吸附动力学建模与仿真分析

基于Matlab的香烟过滤嘴扩散-吸附动力学建模与仿真分析

2026/8/27 22:38:28

1. 项目概述与问题引入 香烟过滤嘴问题,乍一听可能觉得离我们这些搞数学建模、写代码的人有点远。但如果你仔细琢磨一下,这其实是一个绝佳的、融合了物理、化学和工程思维的经典扩散-吸附动力学问题。我最初接触这个题目,是在指导一次校内数学…

定日镜场优化设计实战:Python实现太阳能聚光系统建模与遗传算法布局优化

定日镜场优化设计实战:Python实现太阳能聚光系统建模与遗传算法布局优化

2026/8/27 22:28:28

1. 项目概述:这不是一道数学题,而是一场光与热的工程实战2023年全国大学生数学建模竞赛A题——“定日镜场的优化设计”,表面看是道典型的数模赛题,但真正做过的人才知道,它根本不是在考你能不能解出一个漂亮公式&#…

[光学原理与应用-521]:对光的错误理解与纠偏

[光学原理与应用-521]:对光的错误理解与纠偏

2026/8/27 11:10:02

首先光是一种能量的载体和形态,宏观上观察到的光是由无数个微观的光量子组成的,每个光子在产生的瞬间,其在真空的空间中以确定不变的速度沿着一个初始的方向一直向前,在微观层面,每个光量子的运动轨迹是以波函数所展现…

SIP通话转接原理与REFER方法实战解析

SIP通话转接原理与REFER方法实战解析

2026/8/27 7:25:23

1. 通话转接不是“挂断再拨号”,而是SIP会话的动态重定向你有没有遇到过这样的场景:客服坐席A正在和客户通电话,突然需要把这通对话无缝转给专家坐席B,客户完全感知不到中间的断连——既没听到忙音,也没被要求重新拨号…

Kolla-ansible单节点OpenStack部署实战:从环境准备到排坑指南

Kolla-ansible单节点OpenStack部署实战:从环境准备到排坑指南

2026/8/26 17:50:58

1. 为什么选择Kolla-ansible来部署单节点OpenStack?如果你正在寻找一种能把OpenStack从“概念”快速变成“可用的实验环境”的方法,那么Kolla-ansible几乎是当前最主流、最省心的选择。我见过太多人卡在手动编译依赖、配置服务、处理版本冲突的泥潭里&am…

Go语言构建企业级AI服务网关:统一管理英伟达等AI接口调用

Go语言构建企业级AI服务网关:统一管理英伟达等AI接口调用

2026/8/27 0:07:12

1. 项目概述:从零构建一个企业级的AI服务网关 最近在帮一个做内容审核的团队做技术架构升级,他们原来的业务里,每天有几十万张图片和短视频需要过审,最初是接了几个开源的AI模型自己部署,但效果和性能一直不太稳定。后…

LeetCode Hot100(51-60)算法精解与面试技巧

LeetCode Hot100(51-60)算法精解与面试技巧

2026/8/27 0:07:12

1. 题目背景与核心价值"hot100(51-60)"这个标题看起来像是某个编程题库或算法练习集中的一组题目编号。在技术社区中,类似命名通常指向LeetCode、牛客网等平台的热门题目集合。作为刷过300题的算法老手,我理解这类题目的核心价值在于&#xff…

CRC校验实战:从模2除法到HJ212协议排错

CRC校验实战:从模2除法到HJ212协议排错

2026/8/27 0:07:12

1. 为什么一个“校验码”能扛住工业现场90%的数据 corruption? 你有没有遇到过这样的场景:嵌入式设备通过RS-485上传温湿度数据,上位机偶尔收到一帧乱码——温度显示成-273℃,湿度跳到999%,但串口波形看起来完全正常&a…

摆脱论文困扰!盘点2026年全网爆红的的AI论文写作工具

摆脱论文困扰!盘点2026年全网爆红的的AI论文写作工具

2026/8/22 2:02:26

一天写完毕业论文在2026年已不再是天方夜谭。2026年最炸裂、实测能大幅提速的AI论文写作工具,覆盖选题构思、文献整理、内容生成、格式排版等核心场景,真正帮你高效搞定论文难题。 一、全流程王者:一站式搞定论文全链路(一天定稿首…

导师推荐!2026最新AI论文工具测评与实用推荐

导师推荐!2026最新AI论文工具测评与实用推荐

2026/8/26 18:07:30

2026年真正好用的AI论文工具,核心看生成的论文质量、低AI味、格式正确、学术适配四大指标。综合实测,千笔AI、ThouPen、豆包、DeepSeek、Grammarly 是当前最值得推荐的梯队,覆盖从免费到付费、从中文到英文、从文科到理工的全场景需求。 一、…

告别游戏崩溃:XCOM 2模组管理器的智能革命

告别游戏崩溃:XCOM 2模组管理器的智能革命

2026/8/26 17:57:52

告别游戏崩溃:XCOM 2模组管理器的智能革命 【免费下载链接】xcom2-launcher The Alternative Mod Launcher (AML) is a replacement for the default game launchers from XCOM 2 and XCOM Chimera Squad. 项目地址: https://gitcode.com/gh_mirrors/xc/xcom2-lau…