当前位置: 首页 > news >正文

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

目录

一、typename 与 class:模板参数中的等价性

二、typename 的真正用途:声明从属依赖名称

问题场景

哪些情况需要 typename?

完整示例

三、从属类型 vs 非从属类型

四、template 关键字:消除模板成员调用歧义

问题场景

完整示例

使用场景总结

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

错误1:忘记写 typename

错误2:在不该写的地方写了 typename

错误3:在基类列表和初始化列表中使用 typename

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

七、标准库中的应用

std::allocator_traits

std::iterator_traits

八、常见误区

误区1:认为 typename 和 class 在所有场景都等价

误区2:在所有 T::xxx 前都加 typename

误区3:把 typename 和 .template 混淆

误区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 类。

http://www.jsqmd.com/news/868310/

相关文章:

  • 避开RK3566以太网PHY调试的那些‘坑’:从硬件C15到DTS配置的完整避坑指南
  • 从分子设计到社交网络:聊聊DiGress在图生成领域的实战潜力与当前局限
  • BE-ToF技术:突破传统飞行时间成像的深度感知新方案
  • 2026年靠谱的铣刀/东莞钨钢铣刀深度厂家推荐 - 品牌宣传支持者
  • 别再死记硬背API了!用AirSim Python API写一个自动巡逻的无人机脚本(附完整代码)
  • 避开BLE开发第一个坑:搞懂广播帧里的TxAdd、ChSel字段,让你的智能硬件不再‘隐身’
  • 基于SpringBoot2+vue2的智能学习平台系统
  • 锂电池健康评估:避开NASA/Oxford数据IC分析中的三个常见坑(滤波、异常值、容量增生)
  • Qt Designer里那个神秘的‘控件提升’到底怎么用?手把手教你把Matplotlib画布嵌进去
  • 华为校招0509笔试 商品购买查询 设备运行监控 虚拟机任务调度问题 真题解析
  • 基于Python + LLM的AI导演:让多智能体协作自动完成复杂任务
  • 避坑指南:IBM V5000存储初始化时遇到的CMMVC8020E报错怎么解决?
  • 别再只盯着CNN了!用MedViT这个混合模型,搞定医学图像分类的鲁棒性难题
  • 不只是烧录:用Jetson Orin Nano + OpenCV 4.4.0 + ROS Noetic搭建你的第一个边缘视觉AI项目
  • 告别Python版本冲突!用Anaconda的conda命令5分钟搞定Python 3.8专属虚拟环境
  • 从零到一:手把手教你用MounRiver Studio配置沁恒CH32V208工程(附官方例程结构解析)
  • 复合AI系统基准测试与优化实践指南
  • RK3588/3568嵌入式视觉开发:为什么我选择OpenCV 3.4.3 + FFmpeg 4.2.9这个“经典组合”?
  • 洛克王国:世界 — 解包与 Mod 尝试完整记录
  • 2026 年一人公司创业热潮:政策与 AI 驱动,机遇背后暗藏风险
  • 终极RPG Maker游戏资源解密工具:无需安装的浏览器解决方案
  • UE5.1 Lumen阴影发黑别头疼!手把手教你排查“远处树木变黑”的硬件光追坑
  • 【c++面向对象编程】第45篇:萃取(Traits)技术与策略类:STL源码中的智慧
  • Cadence AMS数模混合仿真保姆级教程:从Virtuoso环境搭建到仿真加速全流程
  • VLC隐藏玩法:结合Lua脚本实现智能视频播放(比如根据时间切换片单)
  • 告别云端:用Llama.cpp+Q4量化模型,在Jetson Orin Nano上打造你的私有AI助手
  • FastbootEnhance:Windows平台终极Fastboot工具箱与Payload提取器完整指南
  • 基于SpringBoot2+vue2的流浪宠物管理系统
  • Multi-Agent系统的高可用架构:容灾设计、故障隔离与快速恢复方案
  • 告别数据混乱!用腾讯TBDS的数据血缘与数据地图,5分钟理清你的数据资产