模板特化(Template Specialization)

模板特化允许开发者为特定类型或类型组合提供专门的实现。当通用模板无法满足特定需求时,特化模板可以调整行为以处理特定的情况。C++ 支持全特化(Full Specialization)__偏特化(Partial Specialization),但需要注意的是,函数模板不支持偏特化,只能进行全特化

类模板的特例化

类模板全特化

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
template<typename T>  
class Printer {
public:
void print(const T& obj) {
std::cout<<"General Printer "<<obj<<std::endl;
}
};

template<>
class Printer<std::string> {
public:
void print(const std::string& obj) {
std::cout<<"String Printer "<<obj<<std::endl;
}
};

类模板偏特化

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
template<class T, class U>  
class Pair {
public:
T first;
U second;
void print_pair() {
std::cout<<first<<" "<<second<<std::endl;
}
};
template<typename T, typename U>
class Pair<T, U*> {
public:
T first;
U* second;
void print() {
std::cout<<first<<" "<<*second<<std::endl;
}
};

调用实例

1
2
3
double temp = 1.1;  
Pair<int, double*> pair(20, &temp);
pair.print();

编译器会优先匹配特例化的模板,再匹配普通模板,如果这里没有实现这个特例化,传指针会打印这个指针指向的地址。

函数模板的特例化

与类模板不同,函数模板不支持偏特化,只能进行全特化。当对函数模板进行全特化时,需要显式指定类型。
代码实例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
template<typename T>  
void printValue(const T& obj) {
std::cout<<"General Printer "<<obj<<std::endl;
}

template<>
inline void printValue<std::string>(const std::string& value) {
std::cout<<"String Printer "<<value<<std::endl;
}

template<>
inline void printValue<int*>(int* const& value) {
std::cout<<"int* Printer "<<*value<<std::endl;
}

需要注意的问题

一、 这里不加inline会报错重定义
在 C++ 中,模板(包括特化模板)的实现代码如果直接写在头文件(.h)中,且该头文件被多个 .cpp 文件包含,会导致编译器在每个 .cpp 中都生成一份函数定义,最终链接时出现 “重复定义” 冲突。

解决方案

  1. 将特化函数标记为 inline

    在函数前加 inline,强制编译器合并重复定义:

    1
    2
    3
    4
    template<>
    inline void printValue<std::string>(const std::string& value) {
    std::cout << "String Printer " << value << std::endl;
    }
  2. 将特化实现放在 .cpp 文件中

    头文件中仅声明特化,实现写在对应的 .cpp(如 template.cpp)中:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    // 头文件 template.h
    template<> void printValue<std::string>(const std::string& value);

    // 实现文件 template.cpp
    #include "template.h"
    template<>
    void printValue<std::string>(const std::string& value) {
    std::cout << "String Printer " << value << std::endl;
    }

二、为什么写int* const&
模板原声明是 void printValue(const T& obj),当 T=int* 时:

const T& 等价于 **int* const&**(“指向 int 的指针的常量引用”),而非 const int*&(“指向常量 int 的指针的引用”)。

如果你的实际需求是接收 “指向常量 int 的指针”,则应将特化的 T 定义为 const int*,此时 const T& 等价于 const int* const&,特化代码如下:

1
2
3
4
5
// 特化 T=const int*
template<>
inline void printValue<const int*>(const int* const& value) {
std::cout << "const int* Printer " << *value << std::endl;
}

变参模板(Variadic Templates)

变参模板允许模板接受可变数量的参数,提供极高的灵活性,是实现诸如 std::tuplestd::variant 等模板库组件的基础。

定义与语法

变参模板使用 参数包(Parameter Pack),通过 ...语法来表示。
语法:

1
2
3
4
5
6
7
8
9
template <typename... Args>

class MyClass { /* ... */ };



template <typename T, typename... Args>

void myFunction(T first, Args... args) { /* ... */ }

变参模板实现方法

一、递归实现

1
2
3
4
5
6
7
8
9
inline void printAll() {  
std::cout<<std::endl;
}

template<typename T,typename... Args>
inline void printAll(const T& first, Args... args) {
std::cout<<first<<" ";
printAll(args...);
}
  1. 无参版本:作为递归的终止条件,仅输出一个换行;
  2. 变参模板版本:接收 “第一个参数 + 剩余参数包”,先打印第一个参数,再递归调用自身处理剩余参数,直到参数包为空,触发无参版本

二、折叠表达式(C++17以后)

1
2
3
4
5
template<typename... Args>  
void coutAll(const Args&... args) {
((std::cout<<args<<" "), ...);
std::cout<<std::endl;
}

折叠表达式 ((std::cout<<args<<" "), ...)

  • 语法解析

    • ...折叠运算符,作用是 “展开参数包args”;
    • 外层()是折叠表达式的语法要求,内层()是逗号表达式的要求;
    • 逗号运算符,的特性:按顺序执行左侧表达式,最终返回右侧表达式结果(这里只利用 “顺序执行” 的特性,忽略返回值)。
  • 执行过程(以coutAll(10, 3.14, "hello")为例):

    表达式会被编译器展开为:

    1
    (std::cout<<10<<" "), (std::cout<<3.14<<" "), (std::cout<<"hello"<<" ");

    即依次执行每个参数的打印操作,实现参数包的遍历。

折叠表达式示例

1
2
3
4
template<typename... Args>  
auto sumAll(Args... args)->decltype((args + ...)) {
return (args + ...);
}

decltype可以理解为 “类型探测器”:给它一个表达式,它能精准返回这个表达式的类型,哪怕是复杂的、无法手动写出来的类型(比如模板推导的类型、函数返回值类型),decltype的核心是编译期推导表达式的原始类型,保留const、引用、指针等所有属性。

当函数返回值类型依赖于参数类型,且无法用auto直接推导时,decltype能精准匹配。