C++ 模板(Template)详解
模板是 C 泛型编程Generic Programming的核心机制它允许你编写与具体类型无关的通用代码在编译时由编译器根据实际类型自动生成对应的代码。一、函数模板Function Template基本语法template typename T T max_val(T a, T b) { return (a b) ? a : b; }template typename T声明一个模板参数Ttypename和class在此处等价。编译器在调用时根据实参类型自动推导T模板参数推导。调用方式max_val(3, 5); // T 推导为 int max_val(3.14, 2.71); // T 推导为 double max_valint(3, 5); // 显式指定 T int多模板参数template typename T, typename U auto add(T a, U b) - decltype(a b) { return a b; }C14 起可以省略尾置返回类型直接使用auto推导template typename T, typename U auto add(T a, U b) { return a b; }二、类模板Class Template基本语法template typename T class Stack { private: std::vectorT elems; public: void push(const T val) { elems.push_back(val); } void pop() { elems.pop_back(); } T top() const { return elems.back(); } bool empty() const { return elems.empty(); } };使用方式Stackint intStack; intStack.push(42); Stackstd::string strStack; strStack.push(hello);⚠️ 类模板不会自动推导参数类型C17 之前必须显式指定。C17 引入了类模板参数推导CTADStack intStack{42}; // C17: 自动推导 T int类外定义成员函数template typename T void StackT::push(const T val) { elems.push_back(val); }三、非类型模板参数Non-Type Template Parameters模板参数不仅可以是类型还可以是常量表达式template typename T, int Size class FixedArray { T data[Size]; public: T operator[](int idx) { return data[idx]; } constexpr int size() const { return Size; } }; FixedArrayint, 100 arr; // Tint, Size100支持的非类型参数类型整型int,long,unsigned等枚举类型enum指针 / 成员指针std::nullptr_tC11自动推导类型autoC17浮点类型C20四、模板特化Template Specialization全特化Full Specialization为特定类型提供专门的实现// 通用版本 template typename T class Printer { public: void print(const T val) { std::cout val std::endl; } }; // 针对 bool 的全特化 template class Printerbool { public: void print(const bool val) { std::cout (val ? true : false) std::endl; } };偏特化Partial Specialization只对部分模板参数做特化template typename T, typename U class Pair { /* ... */ }; // 偏特化第一个参数固定为 int template typename U class Pairint, U { int first; U second; // ... }; // 偏特化两个参数相同 template typename T class PairT, T { T first, second; // ... };注意函数模板不支持偏特化但可以通过重载实现类似效果。五、默认模板参数template typename T int, int Size 10 class Buffer { T data[Size]; }; Buffer buf1; // Tint, Size10 Bufferdouble buf2; // Tdouble, Size10 Bufferdouble, 20 buf3; // Tdouble, Size20六、可变参数模板Variadic TemplatesC11 引入允许模板接受任意数量的模板参数参数包展开// 递归终止条件 void print() { std::cout std::endl; } // 递归展开 template typename T, typename... Args void print(const T first, const Args... rest) { std::cout first; if constexpr (sizeof...(rest) 0) std::cout , ; print(rest...); } print(1, 3.14, hello, c); // 输出: 1, 3.14, hello, c折叠表达式C17大幅简化可变参数模板的写法template typename... Args auto sum(Args... args) { return (args ...); // 右折叠 } template typename... Args void printAll(Args... args) { ((std::cout args ), ...); // 逗号折叠 } sum(1, 2, 3, 4); // 10 printAll(1, , 2.0); // 1 2四种折叠形式形式语法展开方式一元右折叠(pack op ...)p1 op (p2 op (... op pN))一元左折叠(... op pack)((p1 op p2) op ...) op pN二元右折叠(pack op ... op init)p1 op (... op (pN op init))二元左折叠(init op ... op pack)((init op p1) op ...) op pN七、模板元编程Template Metaprogramming利用模板在编译时进行计算生成高效的运行时代码。编译期阶乘template unsigned N struct Factorial { static constexpr unsigned long long value N * FactorialN - 1::value; }; template struct Factorial0 { static constexpr unsigned long long value 1; }; // 使用 constexpr auto fact5 Factorial5::value; // 编译期计算 120C17if constexpr让模板中的编译期分支更加直观template typename T std::string serialize(const T val) { if constexpr (std::is_arithmetic_vT) { return std::to_string(val); } else if constexpr (std::is_same_vT, std::string) { return \ val \; } else { return val.toString(); // 要求 T 有 toString() 方法 } }八、SFINAE 与std::enable_ifSFINAESubstitution Failure Is Not An Error模板参数替换失败不是错误编译器会静默丢弃该候选。// 仅对整数类型启用 template typename T, std::enable_if_tstd::is_integral_vT, int 0 T process(T val) { return val * 2; } // 仅对浮点类型启用 template typename T, std::enable_if_tstd::is_floating_point_vT, int 0 T process(T val) { return val * 2.5; }C20 Concepts概念concepts是 SFINAE 的现代替代方案语法更清晰template std::integral T T process(T val) { return val * 2; } template std::floating_point T T process(T val) { return val * 2.5; } // 自定义 concept template typename T concept Printable requires(T t) { { std::cout t } - std::same_asstd::ostream; }; template Printable T void display(const T val) { std::cout val std::endl; }九、模板与继承基类是模板template typename T class Base { public: void foo() { std::cout Base::foo() std::endl; } protected: T value; }; template typename T class Derived : public BaseT { public: void bar() { // 必须用 this- 或 using 声明来访问基类成员 this-foo(); this-value T{}; } };⚠️ 在模板派生类中访问基类的成员时必须使用this-或using BaseT::member;因为基类是依赖基类dependent base编译器在两阶段查找时不会自动查找。十、模板的实例化隐式实例化编译器在需要时自动实例化Stackint s; // 编译器在此实例化 Stackint显式实例化强制编译器在某个翻译单元中生成代码避免重复实例化// 显式实例化定义 template class Stackint; // 显式实例化声明告诉编译器别处已有定义 extern template class Stackint;两阶段名称查找第一阶段模板定义时检查非依赖名称不依赖模板参数的名称。第二阶段模板实例化时检查依赖名称依赖模板参数的名称。template typename T void func() { int x 10; // 非依赖名称第一阶段检查 T val T{}; // 依赖名称第二阶段检查 unknown_func(); // ❌ 第一阶段就会报错 }十一、模板别名Alias TemplateC11 引入用using为模板创建别名template typename T using Vec std::vectorT; Vecint v {1, 2, 3}; // 等价于 std::vectorint template typename T using IntPtr std::shared_ptrT; IntPtrstd::string sp std::make_sharedstd::string(hello);十二、常用标准库模板工具工具头文件用途std::enable_iftype_traitsSFINAE 条件启用std::is_sametype_traits判断类型是否相同std::decaytype_traits去除引用和 cv 限定std::invokefunctional统一调用函数/成员函数std::tupletuple编译期类型安全的多元组std::variantvariant类型安全的联合体C17std::optionaloptional可能为空的值C17十三、最佳实践与注意事项优先使用typename在模板中访问嵌套依赖类型时必须加typenametemplate typename T void func() { typename T::iterator it; // 必须加 typename }template关键字消歧义调用依赖名称的模板成员时需要obj.template getint();避免过深的模板递归编译器有递归深度限制通常 900~1024 层。错误信息冗长模板编译错误往往非常长善用static_assert提供友好提示template typename T void process(T val) { static_assert(std::is_arithmetic_vT, process() 仅支持算术类型); }概念Concepts优于 SFINAEC20 起优先使用concepts约束模板参数代码更可读。将模板实现放在头文件中模板的实例化发生在调用处因此定义必须在包含它的翻译单元中可见。