C++模板

 

函数模板

类型

普通模板

函数模板

  • 单参数
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只会生成特定类型模板实例, 而对于没有实例化类型编译器会报错