模板参数也能有默认值——template <typename T = int> class Box { ... }; 之后,Box<> 就等于 Box<int>。三条铁律必须记牢:① 默认值必须"从右往左连续给"(template <class T = int, class U> 是错的);② 同一个作用域里只能给一次默认值;③ 类模板能用 Box<> 省掉全部实参,但那个 <> 不能省(写 Box b; 依然错)。还有一条容易搞混的:函数模板也能有默认模板参数(C++11 起),但它"不能用来推导",所以实际很少用——本节的主角是类模板。
12.2.2 的 Pair<T1, T2> 要写全两个模板实参才能用。本节给它(以及所有多参数类模板)装上"默认值",让"最常用的那一种组合"可以少写几个字。
它和 6.2.3 默认参数是一对镜像:那里默认的是"函数的参数值",这里默认的是"模板的参数类型"。两者都遵守同一条规矩——默认值必须从右边一路给过来,中间不能断。
一、语法:模板参数也可以"带一个默认值"
template <typename T = int> // ★ 不写实参时,T 默认是 int
class Box {
private:
T value;
public:
Box(T v = T()) : value(v) { }
T get() const { return value; }
void show() const { cout << value << endl; }
};
三种用法:
Box<> a; // ★ 空尖括号:全部用默认值 → 等于 Box<int>
Box<double> b(1.5); // ★ 只写第一个:T = double
Box<char> c('x'); // ★ 全指定
Box<> 这个"空尖括号"是本节的标志性写法——它表示"我就是要用那个模板,只是实参全走默认"。
二、三条铁律
铁律 1:默认值必须从右往左连续给
template <typename T, typename U = int> // ✓ 合法
class A { };
template <typename T = int, typename U = int> // ✓ 合法(都有默认)
class B { };
template <typename T = int, typename U> // ✗ 错!
class C { };
报错(GCC):
error: no default argument for 'U'
为什么? 因为调用时实参是从左往右填的:
C<int> c; // T 给了 int,U 给谁?—— 你没写,而 U 也没默认 → 无解
规则一句话:
一旦某个模板参数有了默认值,它右边的所有参数都必须有默认值。 和函数的默认参数,是同一条规矩(6.2.3)。
铁律 2:同一个作用域里,默认值只能给一次
template <typename T = int> class Box; // 第一次声明,给了默认值
template <typename T = int> class Box { ... };// ✗ 错:重复给默认值
template <> class Box<int> { ... }; // ✓ 特化是另一回事(12.3.1)
正确做法:只在一处写默认值(通常写在第一次声明或定义里,看代码怎么组织)。
报错:
error: redefinition of default argument。
铁律 3:类模板可以用 Box<>,但 <> 本身不能省
Box<> a; // ✓ 全部走默认
Box b; // ✗ 错:`class template argument required`
这一点和函数模板感觉完全不同:函数模板调用时可以不写 <...>(编译器从参数类型推)——类模板没有"参数"可推,所以即使所有模板参数都有默认值,也必须写出那对尖括号。
一句话:默认值能替你省掉"里面的东西",但省不掉"那对尖括号"。
三、函数模板的默认模板参数:能写,但很少用
从 C++11 起,函数模板也允许给默认模板参数:
template <typename T = int>
T f(T x = T()) { return x; }
但有个关键限制:默认模板参数"不能用来推导 T"。
f(3); // ✓ T 从参数推出来 = int(默认值没用上)
f<int>(); // ✓ 显式指定 T = int,x 用函数默认值 0
f(); // ✗ 错!—— 靠"默认模板参数"推不出来
为什么 f() 是错的:C++ 规定"函数模板的类型参数不能从默认模板参数推导"——只有你显式写了 <...>,默认值才有机会生效。
实际结论:函数模板的默认模板参数几乎没用(因为函数调用通常不写
<>)。这个特性主要是给类模板用的——本节后面的例子、以及标准库,都是类模板在用。
四、别和"函数默认参数"搞混
两者长得像、名字像,但默认的是完全不同的东西:
| 函数默认参数(6.2.3) | 默认模板参数(本节) | |
|---|---|---|
| 给谁默认值 | 函数的变量参数(如 int n) |
模板参数(类型如 T,或值如 int N) |
| 写法 | void f(int n = 10); |
template <typename T = int> |
| 什么时候生效 | 调用函数时省略实参 | 写模板实参时省略(Box<>) |
| "从右往左" | 同一条规矩 | 同一条规矩 |
| 能不能省掉括号 | ——(本来就没有) | 不能,<> 必须写 |
两者可以同时出现在一个函数上:
template <typename T = int>
void f(T a = T(), int n = 10) { ... } // 前面是"默认模板参数",后面是"函数默认参数"
五、动手 Demo:Box 的三种写法
#include <iostream>
#include <string>
using namespace std;
template <typename T = int, typename U = double>
class Box {
private:
T a;
U b;
public:
Box(T x = T(), U y = U()) : a(x), b(y) { }
void show() const {
cout << "(" << a << ", " << b << ")" << endl;
}
};
int main() {
Box<> d; // ★ 全默认:T = int, U = double
Box<string> s("hi"); // ★ 只给第一个:T = string, U = double
Box<char, int> c('A', 65); // ★ 全指定
Box<string, string> ss("左", "右");
d.show();
s.show();
c.show();
ss.show();
return 0;
}
看到什么算成功:
(0, 0)
(hi, 0)
(A, 65)
(左, 右)
读结果:
| 写法 | 实际得到的类 | 依据 |
|---|---|---|
Box<> d; |
Box<int, double> |
两个默认值都生效 |
Box<string> s("hi"); |
Box<string, double> |
只覆盖第一个,第二个仍是默认的 double → 所以 b 打印出 0(double 的零值) |
Box<char, int> c('A', 65); |
全指定 | —— |
Box<string, string> ss("左","右"); |
全指定 | —— |
30 秒实验(把两条铁律各撞一次):
- 把类模板头改成
template <typename T = int, typename U>(删掉U的默认值) → 编译错:no default argument for 'U'。 - 改回
U = double,再把main里的Box<> d;改成Box d;(去掉尖括号) → 编译错:class template argument required for 'class Box'。 - 两条报错都亲手见一次,以后一眼能认出来。
六、常见误区
- "有默认值就能写
Box b;了"——不能。<>必须写(Box<> b;)。默认值省的是"里面的实参",不是"那对尖括号"。 - "
template <class T = int, class U>也行吧"——不行。默认值必须从右往左连续:一旦左边有了默认值,右边的全部都要有。 - "函数模板也能靠默认模板参数省略类型"——不能。
f()这种写法是错的:默认模板参数不能用于推导。函数模板的类型通常靠函数参数推出来。 - "默认模板参数和函数默认参数是一回事"——不是。前者默认的是"类型"(或非类型的值),后者默认的是"变量的值"。
- "
Box<>和Box都行,前面那个只是风格"——不是风格,前者合法、后者报错。 - "默认值可以随便重复写"——同一作用域只能给一次,重复给报
redefinition of default argument。
自测三问(点开对答案)
1. `template- ☐ 12.2.4 为具体类型的参数提供默认值(下一节)
相关笔记
- 简单类模板 —— 上一节:
Pair<T1, T2> - 为具体类型的参数提供默认值 —— 下一节:为什么"要"把具体类型设为默认
- 默认参数 —— 函数的默认参数(镜像的另一半)
- 函数模板的定义 —— 函数模板的类型推导
- 定制类模板 —— 另一条路:为具体类型"换实现"
- 🔷 C++基础 · 索引 —— 全课路线图