彻底搞懂 C++ 中的 `typename`
## 1. 为什么要 `typename`?
在模板代码里,编译器在**第一次解析模板定义**时并不知道模板实参到底是什么。只要某个名字**依赖于模板参数**,就属于所谓的 **dependent name**。
当这个名字**被当作类型**来用时,就可能出现“语法歧义”:
```cpp
template<class T>
void foo() {
T::X * p; // 乘法?还是声明指针?
}
```
- 如果 `T::X` 是一个**类型**,那么 `T::X* p;` 是声明指针。
- 如果 `T::X` 是一个**静态成员变量**,那么 `T::X * p;` 就成了“把变量 `T::X` 乘以 `p`”!
为了消除歧义,C++ 标准规定:
> **凡是出现在模板内部、依赖于模板参数,并且被当作类型使用的名字,前面必须写 `typename`。**
---
## 2. 必须加 `typename` 的典型场景
### 2.1 依赖基类/成员类型
```cpp
template<class Container>
void print(const Container& c) {
// Container::const_iterator 依赖于模板参数 Container
for (typename Container::const_iterator it = c.begin(); it != c.end(); ++it)
std::cout << *it << ' ';
}
```
### 2.2 模板成员类型
```cpp
template<class T>
class Wrapper {
// T::template_type<U> 依赖于 T 和 U
template<class U>
using inner_t = typename T::template template_type<U>;
};
```
> 注意:如果成员本身是模板,还要再叠加一个 `template` 关键字。
### 2.3 类型萃取(type traits)
```cpp
template<class T>
using remove_ref_t = typename std::remove_reference<T>::type;
```
---
## 3. **禁止**或**不必**写 `typename` 的场景
| 场景 | 示例 | 是否需要 |
|------|------|----------|
| 非依赖类型 | `std::vector<int>::iterator` | ❌ |
| 基类列表 | `class MyVector : public T::base` | ❌ |
| 成员初始化列表 | `MyVector() : T::base() {}` | ❌ |
| using/typedef 声明左侧 | `using iterator = T::iterator;` | ❌(C++20 起可省) |
| 模板实参推导 | `std::vector v;` | ❌ |
---
## 4. C++20 的“上下文自动推导”
C++20 开始,在 **using 声明、别名模板、变量声明** 等**足够明确的上下文**里,可以省略 `typename`:
```cpp
template<class T>
using my_iter = T::iterator; // C++20 OK
```
但以下仍必须写:
```cpp
template<class T>
void foo() {
T::iterator it; // ❌ C++20 仍报错
}
```
> 建议:为了向前兼容与可读性,**统一显式写 `typename`**,除非团队代码规范明确采用 C++20 新特性。
---
## 5. 常见错误与编译提示
| 错误代码 | 编译器信息(GCC) |
|----------|-------------------|
| `T::X * p;` | `error: need 'typename' before 'T::X' because 'T' is a dependent scope` |
| `T::template apply<int>;` | `error: 'template' (as a disambiguator) is only allowed within templates` |
---
## 6. 完整示例:综合所有要点
```cpp
#include <iostream>
#include <vector>
// 1. 依赖成员类型
template<class C>
void print(const C& c) {
for (typename C::const_iterator it = c.begin(); it != c.end(); ++it)
std::cout << *it << ' ';
}
// 2. 模板成员类型
template<class T>
struct AllocAdaptor {
template<class U>
using rebind = typename T::template rebind<U>::other;
};
// 3. 类型萃取
template<class T>
using ValueType = typename T::value_type;
int main() {
std::vector<int> v{1, 2, 3};
print(v); // 1.
AllocAdaptor<std::allocator<int>> a;
ValueType<decltype(v)> x = 42; // 3.
std::cout << '\n' << x;
}
```
---
## 7. 速查口诀
> **依赖类型需 typename**
> **基类/成员初始化省**
> **模板再加 template**
> **C++20 部分能省省**