目录
[一、typename 与 class:模板参数中的等价性](#一、typename 与 class:模板参数中的等价性)
[二、typename 的真正用途:声明从属依赖名称](#二、typename 的真正用途:声明从属依赖名称)
[哪些情况需要 typename?](#哪些情况需要 typename?)
[三、从属类型 vs 非从属类型](#三、从属类型 vs 非从属类型)
[四、template 关键字:消除模板成员调用歧义](#四、template 关键字:消除模板成员调用歧义)
[错误1:忘记写 typename](#错误1:忘记写 typename)
[错误2:在不该写的地方写了 typename](#错误2:在不该写的地方写了 typename)
[错误3:在基类列表和初始化列表中使用 typename](#错误3:在基类列表和初始化列表中使用 typename)
[误区1:认为 typename 和 class 在所有场景都等价](#误区1:认为 typename 和 class 在所有场景都等价)
[误区2:在所有 T::xxx 前都加 typename](#误区2:在所有 T::xxx 前都加 typename)
[误区3:把 typename 和 .template 混淆](#误区3:把 typename 和 .template 混淆)
[误区4:在 C++20 前使用 typename 在概念(concept)中](#误区4:在 C++20 前使用 typename 在概念(concept)中)
一、typename 与 class:模板参数中的等价性
cpp
// 以下两种写法完全等价
template <typename T>
void func(T t) {}
template <class T>
void func(T t) {}
历史原因:class 是 C++98 引入模板时的关键字,typename 是后来加入的。现在更推荐使用 typename,因为它更准确地表达了"类型参数"的含义(不一定非得是类)。
唯一例外 :模板模板参数中只能用 class(C++17 后也可以用 typename)。
cpp
// 模板模板参数(老语法只能用 class)
template <template <typename> class Container>
struct MyClass {};
// C++17 开始也可以写 typename
template <template <typename> typename Container>
struct MyClass {}; // C++17
二、typename 的真正用途:声明从属依赖名称
这是 typename 最重要的用途------也是最容易混淆的地方。
问题场景
cpp
template <typename T>
void printSize(const T& container) {
// T::size_type 是什么?是一个类型,还是一个静态成员变量?
T::size_type size = container.size(); // ❌ 编译错误
cout << size << endl;
}
编译器在解析 T::size_type 时,不知道 T 是什么(模板参数直到实例化才确定)。按照 C++ 规则,如果编译器不能确定一个名称是类型,它就认为不是类型(默认当作成员变量或静态函数)。
解决方案 :用 typename 明确告诉编译器"这是一个类型"。
cpp
template <typename T>
void printSize(const T& container) {
typename T::size_type size = container.size(); // ✅ 正确
cout << size << endl;
}
// 使用
vector<int> v = {1, 2, 3};
printSize(v); // T = vector<int>, T::size_type = size_t
哪些情况需要 typename?
| 场景 | 是否需要 typename | 示例 |
|---|---|---|
模板参数中的 T::XXX |
✅ 需要 | typename T::value_type |
使用 std::vector<T>::iterator |
✅ 需要 | typename std::vector<T>::iterator |
基类列表中的 Base<T>::Type |
❌ 不需要 | class Derived : public Base<T>::Type |
| 成员初始化列表 | ❌ 不需要 | Class() : Base<T>::Type(10) {} |
完整示例
cpp
#include <iostream>
#include <vector>
using namespace std;
// 泛型函数:打印容器中所有元素
template <typename Container>
void printAll(const Container& c) {
// 需要 typename:iterator 依赖于 Container
typename Container::const_iterator it;
for (it = c.begin(); it != c.end(); ++it) {
cout << *it << " ";
}
cout << endl;
}
// 泛型函数:获取容器中值的类型
template <typename Container>
struct ValueTypeOf {
using type = typename Container::value_type; // 需要 typename
};
int main() {
vector<int> v = {1, 2, 3, 4};
printAll(v);
ValueTypeOf<vector<int>>::type x = 100; // x 是 int
cout << x << endl;
return 0;
}
三、从属类型 vs 非从属类型
cpp
template <typename T>
struct MyClass {
// 非从属类型:不依赖于 T,编译器直接知道
static int staticValue;
// 从属类型:依赖于 T,需要用 typename
using Iterator = typename T::iterator; // ✅ 需要 typename
using ValueType = typename T::value_type; // ✅ 需要 typename
// 非从属:std::vector<int> 是具体类型
using IntVec = std::vector<int>; // ❌ 不需要 typename
};
判断规则 :如果一个类型名称依赖于模板参数(T、U 等),它就是"从属类型",前面需要加 typename。
四、template 关键字:消除模板成员调用歧义
类似 typename 的歧义也发生在调用模板成员函数时。
问题场景
cpp
template <typename T>
void process(T& obj) {
// obj.templateMethod<int>() 是什么意思?
// 编译器不知道 templateMethod 是模板函数还是成员变量
obj.templateMethod<int>(); // 如果不加 template,编译错误
}
编译器看到 < 和 >,可能认为这是小于号和大于号,而不是模板参数列表。
解决方案 :用 .template 明确告诉编译器。
cpp
template <typename T>
void process(T& obj) {
// .template 告诉编译器:后面是一个模板成员函数
obj.template templateMethod<int>();
// 同样适用于指针:->template
T* ptr = &obj;
ptr->template templateMethod<double>();
}
完整示例
cpp
#include <iostream>
using namespace std;
class MyClass {
public:
template <typename U>
void print() {
cout << "template method called, U = " << typeid(U).name() << endl;
}
};
template <typename T>
void callPrint(T& obj) {
// 如果这里不加 template,编译会失败
obj.template print<int>();
obj.template print<double>();
// 如果 print 不是模板函数,就不需要
// obj.print(); // 普通成员函数
}
int main() {
MyClass obj;
callPrint(obj); // 编译成功
return 0;
}
使用场景总结
| 语法 | 用途 | 示例 |
|---|---|---|
typename |
声明从属类型名称 | typename T::iterator |
.template |
调用模板成员函数 | obj.template func<int>() |
->template |
通过指针调用模板成员函数 | ptr->template func<int>() |
::template |
通过作用域运算符调用 | T::template func<int>() |
五、依赖类型名的常见错误
错误1:忘记写 typename
cpp
template <typename T>
void func(const T& container) {
T::value_type x = container[0]; // ❌ 编译错误
// 应该是 typename T::value_type
}
错误2:在不该写的地方写了 typename
cpp
class MyClass {
typename int x; // ❌ 错误:int 不是从属类型
};
错误3:在基类列表和初始化列表中使用 typename
cpp
template <typename T>
class Derived : public Base<T>::Type { // ❌ 这里不能写 typename
public:
Derived() : Base<T>::Type(10) {} // ❌ 这里也不能写 typename
};
六、完整例子:泛型迭代器辅助函数
cpp
#include <iostream>
#include <vector>
#include <list>
#include <typeinfo>
using namespace std;
// 1. 获取容器元素的类型(使用 typename)
template <typename Container>
struct ElementType {
using type = typename Container::value_type; // 从属类型
};
// 2. 泛型求和函数(需要 typename 声明 iterator)
template <typename Container>
typename Container::value_type // 返回值类型(从属)
sum(const Container& c) {
typename Container::const_iterator it; // 需要 typename
typename Container::value_type total = 0; // 需要 typename
for (it = c.begin(); it != c.end(); ++it) {
total += *it;
}
return total;
}
// 3. 调用对象的模板成员函数(需要 .template)
class Printer {
public:
template <typename T>
void print(const T& value) {
cout << "Printer: " << value << endl;
}
};
template <typename P, typename... Args>
void callPrintAll(P& printer, Args... args) {
// 这里需要 .template,因为 print 是模板函数
// 而且参数包展开中也同样需要
(printer.template print<Args>(args), ...); // C++17 折叠表达式
}
int main() {
// 测试 typename 依赖类型
vector<int> vec = {1, 2, 3, 4, 5};
list<double> lst = {1.1, 2.2, 3.3};
cout << "vector sum: " << sum(vec) << endl;
cout << "list sum: " << sum(lst) << endl;
// 测试元素类型萃取
ElementType<vector<int>>::type x = 100;
ElementType<list<double>>::type y = 3.14;
cout << "x = " << x << ", y = " << y << endl;
// 测试 .template 调用
Printer p;
callPrintAll(p, 42, 3.14, "hello");
return 0;
}
输出:
text
vector sum: 15
list sum: 6.6
x = 100, y = 3.14
Printer: 42
Printer: 3.14
Printer: hello
七、标准库中的应用
std::allocator_traits
cpp
template <typename T>
struct allocator_traits {
// 需要 typename:pointer 是从属类型
using pointer = typename T::pointer;
// 需要 .template:rebind 是模板成员
template <typename U>
using rebind = typename T::template rebind<U>;
};
std::iterator_traits
cpp
template <typename Iter>
struct iterator_traits {
using iterator_category = typename Iter::iterator_category;
using value_type = typename Iter::value_type;
using difference_type = typename Iter::difference_type;
// ...
};
八、常见误区
误区1:认为 typename 和 class 在所有场景都等价
模板参数中等价,但 typename 还有消歧义作用,class 不行。
误区2:在所有 T::xxx 前都加 typename
只有从属类型需要。非从属(如 std::vector<int>::iterator 不依赖 T)不需要。
误区3:把 typename 和 .template 混淆
typename 声明类型,.template 声明模板成员函数调用。
误区4:在 C++20 前使用 typename 在概念(concept)中
(这个问题较新,留作扩展)
九、这一篇的收获
你现在应该理解:
-
模板参数中 :
typename和class等价,推荐用typename -
从属类型 :依赖于模板参数的类型,前面必须加
typename -
非从属类型 :不依赖模板参数,不需要
typename -
.template:调用模板成员函数时,用于告诉编译器<不是小于号 -
记忆口诀:
-
类型之名,从属则
typename -
模板成员,调用加
template
-
💡 小作业:写一个
is_container类型萃取,检测一个类型是否具有iterator和value_type。需要用到typename消歧义。然后写一个泛型printAll函数,如果传入的是容器就打印所有元素,否则直接打印值。
下一篇预告 :第45篇《萃取(Traits)技术与策略类:STL源码中的智慧》------Traits 是一种编译期获取类型信息的技术。std::iterator_traits、std::numeric_limits 都是 traits 的应用。下篇讲清楚如何设计自己的 traits 类。