C++模板学习笔记(二)

记录一下学习C++模板时的知识点

模板参数省略

调用模板函数时可以省略模板参数,因为很容易从实参中推导出类型

1
2
3
4
5
6
7
template<typename T>
void print(T val) {
std::cout << val << std::endl;
}

print(42); // T 被推导为 int
print("hello"); // T 被推导为 const char*

类模板参数通常不可以省略,但是在C++17及以上时具有类模板参数推导的机制,可以省略。

1
2
3
4
5
6
7
template<typename T>
class Wrapper {
public:
Wrapper(T val) { ... }
};

Wrapper w(42); // ✅ 从构造函数推导出 T = int(C++17)

对于C++11等不支持类模板参数推导的情况,可以使用模板函数对模板类的构造进行封装,也能实现类似的效果

1
2
3
4
5
6
7
8
9
10
11
12
template<typename T>
class Wrapper {
public:
Wrapper(T val) { ... }
};

template<typename T>
Wrapper<T> make_wrapper(T val) {
return Wrapper<T>(val);
}

auto w = make_wrapper(42); // ✅ 自动推导 Wrapper<int>

模板实例化的规则

先看下这段代码问题在哪里

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#include "bits/stdc++.h"

using namespace std;

template<typename T, typename... Args>
void print(T firstArg, Args... args) {
cout << firstArg << '\n';
if (sizeof...(args) > 0) {
print(args...);
}
}

int main() {
print(123, "hello", "world");
}

原因是因为if的判断是在运行时决定的,而模板的实例化是在编译时决定的,编译时无法知道运行时的状态。于是编译器看到print(args…)时必须确保它在任何情况下都合法,因此print函数需要补充一个无参数的重载版本。

可变参数模板中…的用法

基本使用

1
2
template<typename... Args>  // Args 是模板参数包(Template Parameter Pack)
void func(Args... args); // args 是函数参数包(Function Parameter Pack)

参数包解包

递归展开

1
2
3
4
5
6
7
8
// 递归终止条件
void print() {}

template<typename T, typename... Args>
void print(T first, Args... rest) {
std::cout << first << " ";
print(rest...); // 递归调用,展开 rest
}

折叠表达式

1
2
3
4
template<typename... Args>
auto sum(Args... args) {
return (args + ...); // 折叠表达式:args[0] + args[1] + ...
}

二元运算符支持所有运算符

完美转发

完美转发的目的是保持参数的左/右值属性

1
2
3
4
template<typename... Args>
void wrapper(Args&&... args) {
target_func(std::forward<Args>(args)...); // 展开为 std::forward<T1>(x1), std::forward<T2>(x2), ...
}

参数翻倍

1
2
3
4
template<typename... Args>
void printDoubled(Args&... args) {
print((args + args)...); // 关键点:参数包展开
}

参数加1

1
2
3
4
template<typename... Args>
void addOne(Args&... args) {
print((args + 1)...);
}

变参下标

1
2
3
4
template<typename C, typename... Idx>
void printElems(C& coll, Idx... idx) {
print(coll[idx]...);
}

sizeof…运算符

用于获取参数包大小,是一个编译器常量

1
2
3
4
5
6
7
8
9
template<typename... Args>
void func(Args... args) {
constexpr size_t count = sizeof...(Args); // 类型参数包的大小
constexpr size_t count2 = sizeof...(args); // 函数参数包的大小
}
// 当调用std::vector<std::string> coll = {"good", "times", "say", "bye"};
// printElems(coll,2,0,3);
// 时,相当于调用了
// print (coll[2], coll[0], coll[3]);

模板技巧

使用this→

对于类模板,如果它的基类也是依赖于模板参数的,那么对它而言即使 x 是继承而来的,使用 this->xx 也不一定是等效的。比如

1
2
3
4
5
6
7
8
9
10
11
12
13
template<typename T>
class Base {
public:
void bar();
};

template<typename T>
class Derived : Base<T> {
public:
void foo() {
bar(); // calls external bar() or error
}
};

Derived 中的 bar()永远不会被解析成 Base 中的 bar()。因此这样做要么会遇到错误,要么就是调用了其它地方的 bar(),比如可能是定义在其它地方的 global 的 bar()

作者

echo

发布于

2025-07-16

更新于

2025-07-16

许可协议

评论