C++模板

 

参考

定义

C++模板是一种强大特性, 允许程序员编写与类型无关代码

通过模板, 可以编写一次代码, 用于多种数据类型, 从而提高代码重用性和灵活性

template<typename T>

T是一个类型参数(type parameter), 是一个占位符, 用于在模板实例化时指定具体类型

种类

普通模板

函数模板

单参数
template<typename T>
  • 示例, 定义单参数模板
#include <iostream>

template<typename T>
T Add(T x, T y) {
    return x + y;
}

int main() {
    std::cout << Add<int>(0xA, 0xB) << std::endl;
    std::cout << Add<double>(3.1415, 2.7141) << std::endl;
    std::cout << Add<std::string>("Hello", "World") << std::endl;
    return 0;
}
多参数
template<typename T, typename V>
  • 示例, 定义多参数模板
#include <iostream>

template<typename T, typename V>
void Print(T x, V y) {
    std::cout << x << std::endl << y << std::endl;
}

int main() {
    Print(1, 0.1412);
    Print("abcdef", 'A');
    return 0;
}

类模板

  • 示例, 定义类模板
#include <iostream>

template <typename 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;
};

int main() {
    Composer<int> com1(0xFF, 0xAB);
    std::cout << com1.GetMax() << std::endl;
    return 0;
}
结构体模板
template<typename T>
struct Node {
    T     mData;
    Node* mNext;
};

可变参数模板

C++11允许模板中包含任意数量模板参数, 称可变参数模板(variadic templates)

variadic template functions(可变参数模板函数)

定义

typename后跟 ...Args 表明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 ExplicitDemo {
    void Print(const T& value) {
        std::cout << "general template: " << value << std::endl;
    }
};

// 针对 std::string 类型全特化
template<>
struct ExplicitDemo<std::string> {
    void Print(const std::string& value) {
        std::cout << "string specialization: " << value << std::endl;
    }
};

int main() {
    ExplicitDemo<int> intDemo;
    intDemo.Print(42);

    // 特化情况
    ExplicitDemo<std::string> strDemo;
    strDemo.Print("Hello, World!");
    return 0;
}

partial specialization(偏特化)

偏特化允许特化模板部分参数, 只能用于类模板

  • 示例, 模板偏特化
#include <iostream>

// 通用模板类定义
template<typename T>
class PartialDemo {
public:
    void Display() {
        std::cout << "generic template for type: " << typeid(T).name() << std::endl;
    }
};

// 对int类型偏特化
template<>
class PartialDemo<int> {
public:
    void Display() {
        std::cout << "specialized template for int type" << std::endl;
    }
};

int main() {
    PartialDemo<int> intDemo;
    intDemo.Display();
    PartialDemo<double> doubleDemo;
    doubleDemo.Display();
    return 0;
}

变量类型

提取模板变量类型及其特性主要通过类型萃取(type traits)和SFINAE(substitution failure is not an error)技术实现

提取

利用标准库中头文件<type_traits>, 可检查、提取和操作模板变量类型信息

std::decay

std::decay是一个类型特征(type trait), std::decay 会去掉模板变量引用、cv-限定符(constvolatile), 并将数组和函数类型转换为指针类型, 之后提取模板类型

std::decay本身并不直接”提取”模板变量类型, 它只是转换一个给定类型到其衰减后形式

  • 示例, 提取模板变量类型
#include <iostream>
#include <type_traits>

template <typename T>
void PrintDecayedType() {
    using DecayedT = typename std::decay<T>::type
    std::cout << typeid(DecayedT).name() << std::endl;
}

int main() {
    // int(移除引用)
    PrintDecayedType<int&>();   
    // int const * __ptr64
    PrintDecayedType<const int[]>();
}
remove_reference/remove_cv

std::remove_referencestd::remove_cv是两种类型特征(type traits), 分别用于从给定类型中移除引用和cv-限定符(const和volatile)

#include <iostream>
#include <type_traits>

template <typename T>
void PrintStrippedType() {
    // 先移除引用, 再移除cv限定符
    using CleanedT = typename std::remove_cv<typename std::remove_reference<T>::type>::type;
    std::cout << typeid(CleanedT).name() << std::endl;
}

int main() {
    // int
    PrintStrippedType<const int&>();
}
std::type_traits

std::type_traits 是 C++11 所引入标准库, 提供一组模板类和函数, 用于在编译时查询和操作类型特性, 包括但不限于类型是否为整型、是否为浮点型、是否有默认构造函数、是否可拷贝等

#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>();
    return 0;
}
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文件时无法获悉模板定义, 无法实例化模板

模板实例化需要在同一个编译单元中同时看到模板声明和定义, 才能生成特定类型实例化代码

声明定义均放头文件

// test_template.hpp
#ifndef __TEST_TEMPLATE_HPP__
#define __TEST_TEMPLATE_HPP__
#include <iostream>

template <typename T>
class TemplateDemo {
public:
    void Print(const T& value);
};

template <typename T>
void TemplateDemo<T>::Print(const T& value) {
    std::cout << value << std::endl;
}
#endif
// main.cpp
#include "test_template.hpp"
int main() {
    TemplateDemo<int> intDemo;
    intDemo.Print(1);

    TemplateDemo<std::string> strDemo;
    strDemo.Print("Hello");
    return 0;
}

定义放内联实现文件

// test_template.hpp
#ifndef __TEST_TEMPLATE_HPP__
#define __TEST_TEMPLATE_HPP__

#include <iostream>

template <typename T>
class TemplateDemo {
public:
    void Print(const T& value);
};

// 包含模板定义
#include "test_template.tpp"

#endif
// test_template.tpp
template <typename T>
void TemplateDemo<T>::Print(const T& value) {
    std::cout << value << std::endl;
}
// main.cpp
#include "test_template.hpp"
int main() {
    TemplateDemo<int> intDemo;
    intDemo.Print(1);

    TemplateDemo<std::string> strDemo;
    strDemo.Print("Hello");
    return 0;
}

这样可在.hpp中保持模板声明简洁性, 同时将模板实现放在.cpp中便于管理

显式实例化

若确定模板只会用于特定类型, 可使用显式实例化, 将模板定义放在.cpp中实例化所需类型

// test_template.hpp
#ifndef __TEST_TEMPLATE_HPP__
#define __TEST_TEMPLATE_HPP__
#include <iostream>

template <typename T>
class TemplateDemo {
public:
    void print(const T& value);
};
#endif
// test_template.cpp
#include "test_template.hpp"

template <typename T>
void TemplateDemo<T>::print(const T& value) {
    std::cout << value << std::endl;
}

// 显式实例化
template class TemplateDemo<int>;
template class TemplateDemo<double>;

现在.cpp只会生成特定类型模板实例, 对于没有实例化类型编译器会报错

traits

traits是一种用于泛型编程(generic programming)技术, 允许程序根据类型特征进行编译期决策

traits可以在编译时查询和操作类型属性, 而无需在运行时进行类型检查, 通常用于模板编程中以增强代码灵活性和可重用性

例如可以创建一个traits来确定类型是否为某种类型(如整数、浮点数、指针等), 是否满足某种条件(如可复制、可移动), 或者是否具备某种操作(如算术操作、输入输出操作)等

功能

traits通常以模板类形式实现, 并使用模板特化(template specialization)来定义特定类型特性

is_same

std::is_same 是C++标准库中所提供, 用于判断两个模板类型是否相同

#include <iostream>
#include <type_traits>

template <typename T>
void CheckType() {
    if (std::is_same<T, int>::value) {
        std::cout << "type is int" << std::endl;
        return;
    }
    std::cout << "type is not int" << std::endl;
}

int main() {
    CheckType<int>();
    CheckType<double>();
    return 0;
}

自定义traits类

可以自定义traits类来检查某个类型, 通常使用模板特化来对特定类型进行处理

#include <iostream>
#include <type_traits>

// 默认情况下, 非整数类型
template <typename T>
struct IsInteger {
    static const bool value = false;
};

// 针对整数类型进行特化
template <>
struct IsInteger<int> {
    static const bool value = true;
};

template <>
struct IsInteger<long> {
    static const bool value = true;
};

template <typename T>
void CheckIntegerType() {
    if (IsInteger<T>::value) {
        std::cout << "type is integer" << std::endl;
        return;
    }
    std::cout << "type is not integer" << std::endl;
}

int main() {
    CheckIntegerType<int>();
    CheckIntegerType<double>();
    return 0;
}

上面代码中定义一个 IsInteger traits 类

通过模板特化将 IsInteger<int>IsInteger<long> 设置为 true, 而其他类型默认为 false

类型别名

value_type

value_type定义容器中存储元素类型

#include <iostream>
#include <vector>

int main() {
    using value_type = std::vector<int>::value_type

    std::vector<int> vec = {1, 2, 3, 4, 5};
    value_type x = 10;
    vec.push_back(x);
    std::cout << vec.back() << std::endl;

    return 0;
}

difference_type

difference_type表示两个迭代器之间距离类型, 通常是一个有符号整数类型

#include <vector>
#include <iostream>

int main() {
    std::vector<int> vec = {1, 2, 3, 4, 5};
    auto it1 = vec.begin();
    auto it2 = vec.end();
    std::vector<int>::difference_type diff = it2 - it1; // 计算迭代器之间距离
    std::cout << diff << std::endl;
    return 0;
}

pointer

pointer提供指向容器中元素指针类型

reference

reference定义容器中元素引用类型

#include <vector>
#include <iostream>

int main() {
    std::vector<int> vec = {1, 2, 3, 4, 5};
    // 获取vec中第一个元素引用
    std::vector<int>::reference ref = vec[0];
    // 修改引用值, 即修改vec中第一个元素值
    ref = 10;
    // 输出10, 表示vec中第一个元素被修改
    std::cout << vec[0] << std::endl; 
    return 0;
}

std::iterator_traits

std::iterator_traits是 C++ 标准库中一个模板结构体, 提供方法来查询迭代器特性, 如迭代器类别、值类型、差异类型、指针类型和引用类型等

std::iterator_traits 主要定义几个类型别名value_typedifference_typepointerreference

获取指向类型

std::iterator_traits<T>::value_type 用于获取迭代器 T 所指向值类型

  • 示例, 获取迭代器类型
#include <iterator>
#include <type_traits>

template <typename T>
struct MyIterator {
    using iterator_category = std::forward_iterator_tag; // 迭代器类别
    using value_type = T;                                // 值类型
    using difference_type = std::ptrdiff_t;              // 差异类型
    using pointer = T*;                                  // 指针类型(如果需要)
    using reference = T&;                                // 引用类型

    // ... 其他成员和函数 ...
};

// 通常不需要特化 std::iterator_traits, 除非你有特殊需求
// 在这个例子中, MyIterator 已经定义所有必要类型成员, 所以 std::iterator_traits<MyIterator<T>> 会自动工作

int main() {
    // 使用 std::iterator_traits 来查询 MyIterator特性
    static_assert(std::is_same<std::iterator_traits<MyIterator<int>>::iterator_category, std::forward_iterator_tag>::value, "");
    static_assert(std::is_same<std::iterator_traits<MyIterator<int>>::value_type, int>::value, "");
    // ... 其他静态断言 ...

    return 0;
}

例如, 若 T 是 std::vector::iterator, 那么 iterator_traits::value_type 将是 int

  • 示例, iterator_traits使用
template<typename T>
typename iterator_traits<T>::value_type TestFunc(T iter) {
    return *iter;
}

(1) 模板声明

template<typename T> 表示 TestFunc 是模板函数, 可以接受任意类型 T 参数

(2) 函数返回类型

typename iterator_traits<T>::value_type

函数返回类型, 使用 iterator_traits 提取 T 类型所对应迭代器值类型(即 value_type)

typename 关键字用于指明iterator_traits<T>::value_type 是一个类型(type), 而不是一个静态成员或其他内容

(3) 函数参数

该函数接受一个类型为 T 参数 iter, 通常是一个迭代器(例如指向容器指针或迭代器)

(4) 函数体

函数返回 *iter, 即传入迭代器所指向值, 意味着 T 必须是一个可以解引用迭代器类型

(5) 用途

该函数接受一个迭代器 ite, 并返回迭代器解引用后值

返回类型是 T 所对应迭代器 value_type, 确保返回值类型与 ite 所指向对象类型相匹配

因此, 该函数可以处理任意迭代器, 例如 std::vector<int>::iteratorstd::list<std::string>::iterator 等, 而不需要知道具体容器类型

#include <iostream>
#include <vector>
#include <iterator>

template<typename T>
typename std::iterator_traits<T>::value_type func(T iter) {
    return *iter;
}

int main() {
    std::vector<int> vec = {1, 2, 3, 4, 5};
    auto it = vec.begin();
    int val = func(it);
    std::cout << "The value is: " << val << std::endl;

    return 0;
}