【c++面向对象编程】第44篇:typename与class的区别,依赖类型名与template消除歧义

目录

[一、typename 与 class:模板参数中的等价性](#一、typename 与 class:模板参数中的等价性)

[二、typename 的真正用途:声明从属依赖名称](#二、typename 的真正用途:声明从属依赖名称)

问题场景

[哪些情况需要 typename?](#哪些情况需要 typename?)

完整示例

[三、从属类型 vs 非从属类型](#三、从属类型 vs 非从属类型)

[四、template 关键字:消除模板成员调用歧义](#四、template 关键字:消除模板成员调用歧义)

问题场景

完整示例

使用场景总结

五、依赖类型名的常见错误

[错误1:忘记写 typename](#错误1:忘记写 typename)

[错误2:在不该写的地方写了 typename](#错误2:在不该写的地方写了 typename)

[错误3:在基类列表和初始化列表中使用 typename](#错误3:在基类列表和初始化列表中使用 typename)

六、完整例子:泛型迭代器辅助函数

七、标准库中的应用

std::allocator_traits

std::iterator_traits

八、常见误区

[误区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
};

判断规则 :如果一个类型名称依赖于模板参数(TU 等),它就是"从属类型",前面需要加 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)中

(这个问题较新,留作扩展)


九、这一篇的收获

你现在应该理解:

  • 模板参数中typenameclass 等价,推荐用 typename

  • 从属类型 :依赖于模板参数的类型,前面必须加 typename

  • 非从属类型 :不依赖模板参数,不需要 typename

  • .template :调用模板成员函数时,用于告诉编译器 < 不是小于号

  • 记忆口诀

    • 类型之名,从属则 typename

    • 模板成员,调用加 template

💡 小作业:写一个 is_container 类型萃取,检测一个类型是否具有 iteratorvalue_type。需要用到 typename 消歧义。然后写一个泛型 printAll 函数,如果传入的是容器就打印所有元素,否则直接打印值。


下一篇预告 :第45篇《萃取(Traits)技术与策略类:STL源码中的智慧》------Traits 是一种编译期获取类型信息的技术。std::iterator_traitsstd::numeric_limits 都是 traits 的应用。下篇讲清楚如何设计自己的 traits 类。

相关推荐
fenglllle3 小时前
JDK8升级JDK17使用CompletableFuture在线程中classloader的变化
java·开发语言·jvm
Hua-Jay3 小时前
OpenCV联合C++/Qt 学习笔记(二十五)----监督学习聚类及K均值聚类
c++·笔记·opencv·学习·计算机视觉·聚类
玖釉-3 小时前
C++ 中的矩阵介绍:以二维矩阵查找为例
c++·windows·算法·矩阵
JAVA面经实录9173 小时前
Java+SpringAI企业级实战项目完整官方文档(生产终版)
java·开发语言·spring·ai编程
梵得儿SHI3 小时前
Java IO 流进阶:Buffer 与 Channel 核心概念解析及与传统 IO 的本质区别
java·开发语言·高并发·nio·channel·buffer·提升io效率
j_xxx404_3 小时前
Linux线程:从内存分页机制(Page Table/TLB/Page Fault)彻底读懂 Linux 线程本质
linux·运维·服务器·开发语言·c++·人工智能·ai
2301_789015623 小时前
C++_string增删查改模拟实现
java·开发语言·c++
没有逆称3 小时前
Java OOM 问题全解析
java·jvm
星河耀银海3 小时前
JAVA 注解(Annotation):从原理到实战应用
java·开发语言·数据库