类型
普通模板
函数模板
- 单参数
template<class T>
T Add(T x, T y) {
return x + y;
}
- 多参数
template<class T, class V>
void Print(T x, V y) {
std::cout << x << y << std::endl;
}
结构体模板
template<class T>
struct Node {
T mData;
Node* mNext;
};
类模板
template <class T>
class Composer {
public:
Composer(T x, T y) : mX(x), mY(y) {}
T GetMax() const {
return mX > mY ? mX : mY;
}
private:
T mX;
T mY;
};
可变参数模板
C++11允许模板中包含任意数量模板参数, 称可变参数模板(Variadic Templates)
可变参数模板函数(Variadic Template Functions)
- 定义
typename后跟 ...
表明Args是可变模板参数, 可接收多种数据类型, 又称模板参数包
args参数, 类型为 Args...
, 可以接收任意个参数
template<typename... Args>
void VairFun(Args... args) {
// ...
}
- 递归方式解包
定义一个辅助递归模板函数, 每次递归调用时从参数包中取出一个参数, 直到参数包为空
#include <iostream>
// 基础递归终止函数
void print() {
std::cout << std::endl; // 换行作为递归终止标志
}
// 递归模板函数
template<typename T, typename... Args>
void print(T firstArg, Args... args) {
std::cout << firstArg << " "; // 打印第一个参数
print(args...); // 递归调用, 解包剩余参数
}
// 可变参数模板函数
template<typename... Args>
void printAll(Args... args) {
print(args...); // 调用递归模板函数开始解包
}
int main() {
printAll(1, 2.5, "Hello");
return 0;
}
- std::initializer_list和逗号运算符解包
#include <iostream>
// 可变参数模板函数, 使用std::initializer_list和逗号运算符解包
template<typename... Args>
void printAll(Args... args) {
(void) std::initializer_list<int>{ (std::cout << args << " ", 0)... };
std::cout << std::endl;
}
int main() {
printAll(1, 2.5, "Hello");
return 0;
}
特征
模板特化
模板特化允许为特定类型提供不同实现, 有全特化和偏特化两种类型
全特化(Explicit Specialization)
全特化是对模板所有参数提供特定实现, 模板特化类型必须完全匹配
#include <iostream>
#include <string>
template<typename T>
struct Printer {
void Print(const T& value) {
std::cout << "General template: " << value << std::endl;
}
};
// 针对 std::string 类型全特化
template<>
struct Printer<std::string> {
void Print(const std::string& value) {
std::cout << "String specialization: " << value << std::endl;
}
};
int main() {
// 一般情况
Printer<int> intPrinter;
intPrinter.Print(42);
// 特化情况
Printer<std::string> strPrinter;
strPrinter.Print("Hello, World!");
return 0;
}
偏特化(Partial Specialization)
偏特化允许特化模板部分参数, 只能用于类模板
#include <iostream>
// 通用模板类定义
template<typename T>
class MyTemplate {
public:
void display() {
std::cout << "Generic template for type: " << typeid(T).name() << std::endl;
}
};
// 对int类型的偏特化
template<>
class MyTemplate<int> {
public:
void display() {
std::cout << "Specialized template for int type" << std::endl;
}
};
int main() {
MyTemplate<int> intObj;
MyTemplate<double> doubleObj;
intObj.display();
doubleObj.display();
return 0;
}
模板变量类型
提取模板变量类型及其特性主要通过类型萃取(Type Traits)和SFINAE(Substitution Failure Is Not An Error)技术实现
利用标准库中头文件<type_traits>
, 可检查、提取和操作模板变量类型信息
std::decay
std::decay 会去掉模板变量引用、const、volatile修饰符, 并将数组和函数类型转换为指针类型, 之后提取模板类型
#include <iostream>
#include <type_traits>
template <typename T>
void PrintDecayedType() {
using DecayedType = typename std::decay<T>::type;
std::cout << typeid(DecayedType).name() << std::endl;
}
int main() {
// int
PrintDecayedType<int&>();
// int const * __ptr64
PrintDecayedType<const int[]>();
}
remove_reference/remove_cv提取
#include <iostream>
#include <type_traits>
template <typename T>
void PrintStrippedType() {
using NoRefType = typename std::remove_reference<T>::type;
using NoCVType = typename std::remove_cv<NoRefType>::type;
std::cout << typeid(NoCVType).name() << std::endl;
}
int main() {
// int
PrintStrippedType<const int&>();
}
std::type_traits
#include <iostream>
#include <type_traits>
template <typename T>
void PrintTypeTraits() {
std::cout << "is_pointer: " << std::is_pointer<T>::value << std::endl;
std::cout << "is_reference: " << std::is_reference<T>::value << std::endl;
std::cout << "is_integral: " << std::is_integral<T>::value << std::endl;
std::cout << "is_float: " << std::is_floating_point<T>::value << std::endl;
}
int main() {
// is_pointer: 1
PrintTypeTraits<int*>();
// is_reference: 1
PrintTypeTraits<int&>();
// is_float: 1
PrintTypeTraits<float>();
}
decltype
decltype 操作符用于在编译时推导表达式的类型
template <typename T, typename U>
auto Add(T a, U b) -> decltype(a + b) {
return a + b;
}
int main() {
// 推导出result类型为double
auto result = Add(1, 2.0);
// double
std::cout << typeid(result).name() << std::endl;
}
编译
C++编译器在编译模板时并不知道模板会被用来实例化何种类型, 所有无法生成具体代码, 只有在使用模板时(实例化)编译器才知道模板参数类型, 才能生成具体实现代码
因此若在分离编译模型下(将模板声明放在.h, 定义放在.cpp), 编译器在处理.h文件时无法获悉模板定义, 无法实例化模板
模板实例化需要在同一个编译单元中同时看到模板声明和定义, 才能生成特定类型实例化代码, 具体实现如下,
声明定义均放在头文件
// TestTemplate.hpp
#ifndef TEST_TEMPLATE_HPP
#define TEST_TEMPLATE_HPP
#include <iostream>
template <typename T>
class MyTemplate {
public:
void Print(const T& value);
};
template <typename T>
void MyTemplate<T>::Print(const T& value) {
std::cout << value << std::endl;
}
#endif
// Main.cpp
#include "TestTemplate.hpp"
int main() {
MyTemplate<int> myInt;
myInt.Print(1);
MyTemplate<std::string> myStr;
myStr.Print("Hello");
return 0;
}
定义放在内联实现文件
// TestTemplate.hpp
#ifndef TEST_TEMPLATE_HPP
#define TEST_TEMPLATE_HPP
#include <iostream>
template <typename T>
class MyTemplate {
public:
void Print(const T& value);
};
// 包含模板定义
#include "TestTemplate.tpp"
#endif
// TestTemplate.tpp
template <typename T>
void MyTemplate<T>::Print(const T& value) {
std::cout << value << std::endl;
}
// Main.cpp
#include "TestTemplate.hpp"
int main() {
MyTemplate<int> myInt;
myInt.Print(1);
MyTemplate<std::string> myStr;
myStr.Print("Hello");
return 0;
}
这样可在.hpp中保持模板声明简洁性, 同时将模板实现放在.cpp中便于管理
显式实例化
若确定模板只会用于特定类型, 可使用显式实例化, 将模板定义放在.cpp中实例化所需类型
// TestTemplate.hpp
#ifndef TEST_TEMPLATE_HPP
#define TEST_TEMPLATE_HPP
#include <iostream>
template <typename T>
class MyTemplate {
public:
void print(const T& value);
};
#endif
// TestTemplate.cpp
#include "TestTemplate.hpp"
template <typename T>
void MyTemplate<T>::print(const T& value) {
std::cout << value << std::endl;
}
// 显式实例化
template class MyTemplate<int>;
template class MyTemplate<double>;
现在.cpp只会生成特定类型模板实例, 而对于没有实例化类型编译器会报错