目录一、typename 与 class模板参数中的等价性二、typename 的真正用途声明从属依赖名称问题场景哪些情况需要 typename完整示例三、从属类型 vs 非从属类型四、template 关键字消除模板成员调用歧义问题场景完整示例使用场景总结五、依赖类型名的常见错误错误1忘记写 typename错误2在不该写的地方写了 typename错误3在基类列表和初始化列表中使用 typename六、完整例子泛型迭代器辅助函数七、标准库中的应用std::allocator_traitsstd::iterator_traits八、常见误区误区1认为 typename 和 class 在所有场景都等价误区2在所有 T::xxx 前都加 typename误区3把 typename 和 .template 混淆误区4在 C20 前使用 typename 在概念concept中九、这一篇的收获一、typename 与 class模板参数中的等价性cpp// 以下两种写法完全等价 template typename T void func(T t) {} template class T void func(T t) {}历史原因class是 C98 引入模板时的关键字typename是后来加入的。现在更推荐使用typename因为它更准确地表达了“类型参数”的含义不一定非得是类。唯一例外模板模板参数中只能用classC17 后也可以用typename。cpp// 模板模板参数老语法只能用 class template template typename class Container struct MyClass {}; // C17 开始也可以写 typename template template typename typename Container struct MyClass {}; // C17二、typename 的真正用途声明从属依赖名称这是typename最重要的用途——也是最容易混淆的地方。问题场景cpptemplate 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明确告诉编译器“这是一个类型”。cpptemplate typename T void printSize(const T container) { typename T::size_type size container.size(); // ✅ 正确 cout size endl; } // 使用 vectorint v {1, 2, 3}; printSize(v); // T vectorint, T::size_type size_t哪些情况需要 typename场景是否需要 typename示例模板参数中的T::XXX✅ 需要typename T::value_type使用std::vectorT::iterator✅ 需要typename std::vectorT::iterator基类列表中的BaseT::Type❌ 不需要class Derived : public BaseT::Type成员初始化列表❌ 不需要Class() : BaseT::Type(10) {}完整示例cpp#include iostream #include vector using namespace std; // 泛型函数打印容器中所有元素 template typename Container void printAll(const Container c) { // 需要 typenameiterator 依赖于 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() { vectorint v {1, 2, 3, 4}; printAll(v); ValueTypeOfvectorint::type x 100; // x 是 int cout x endl; return 0; }三、从属类型 vs 非从属类型cpptemplate typename T struct MyClass { // 非从属类型不依赖于 T编译器直接知道 static int staticValue; // 从属类型依赖于 T需要用 typename using Iterator typename T::iterator; // ✅ 需要 typename using ValueType typename T::value_type; // ✅ 需要 typename // 非从属std::vectorint 是具体类型 using IntVec std::vectorint; // ❌ 不需要 typename };判断规则如果一个类型名称依赖于模板参数T、U等它就是“从属类型”前面需要加typename。四、template 关键字消除模板成员调用歧义类似typename的歧义也发生在调用模板成员函数时。问题场景cpptemplate typename T void process(T obj) { // obj.templateMethodint() 是什么意思 // 编译器不知道 templateMethod 是模板函数还是成员变量 obj.templateMethodint(); // 如果不加 template编译错误 }编译器看到和可能认为这是小于号和大于号而不是模板参数列表。解决方案用.template明确告诉编译器。cpptemplate typename T void process(T obj) { // .template 告诉编译器后面是一个模板成员函数 obj.template templateMethodint(); // 同样适用于指针-template T* ptr obj; ptr-template templateMethoddouble(); }完整示例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 printint(); obj.template printdouble(); // 如果 print 不是模板函数就不需要 // obj.print(); // 普通成员函数 } int main() { MyClass obj; callPrint(obj); // 编译成功 return 0; }使用场景总结语法用途示例typename声明从属类型名称typename T::iterator.template调用模板成员函数obj.template funcint()-template通过指针调用模板成员函数ptr-template funcint()::template通过作用域运算符调用T::template funcint()五、依赖类型名的常见错误错误1忘记写 typenamecpptemplate typename T void func(const T container) { T::value_type x container[0]; // ❌ 编译错误 // 应该是 typename T::value_type }错误2在不该写的地方写了 typenamecppclass MyClass { typename int x; // ❌ 错误int 不是从属类型 };错误3在基类列表和初始化列表中使用 typenamecpptemplate typename T class Derived : public BaseT::Type { // ❌ 这里不能写 typename public: Derived() : BaseT::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 printArgs(args), ...); // C17 折叠表达式 } int main() { // 测试 typename 依赖类型 vectorint vec {1, 2, 3, 4, 5}; listdouble lst {1.1, 2.2, 3.3}; cout vector sum: sum(vec) endl; cout list sum: sum(lst) endl; // 测试元素类型萃取 ElementTypevectorint::type x 100; ElementTypelistdouble::type y 3.14; cout x x , y y endl; // 测试 .template 调用 Printer p; callPrintAll(p, 42, 3.14, hello); return 0; }输出textvector sum: 15 list sum: 6.6 x 100, y 3.14 Printer: 42 Printer: 3.14 Printer: hello七、标准库中的应用std::allocator_traitscpptemplate typename T struct allocator_traits { // 需要 typenamepointer 是从属类型 using pointer typename T::pointer; // 需要 .templaterebind 是模板成员 template typename U using rebind typename T::template rebindU; };std::iterator_traitscpptemplate 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::vectorint::iterator不依赖 T不需要。误区3把 typename 和 .template 混淆typename声明类型.template声明模板成员函数调用。误区4在 C20 前使用 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 类。
【c++面向对象编程】第44篇:typename与class的区别,依赖类型名与template消除歧义
目录一、typename 与 class模板参数中的等价性二、typename 的真正用途声明从属依赖名称问题场景哪些情况需要 typename完整示例三、从属类型 vs 非从属类型四、template 关键字消除模板成员调用歧义问题场景完整示例使用场景总结五、依赖类型名的常见错误错误1忘记写 typename错误2在不该写的地方写了 typename错误3在基类列表和初始化列表中使用 typename六、完整例子泛型迭代器辅助函数七、标准库中的应用std::allocator_traitsstd::iterator_traits八、常见误区误区1认为 typename 和 class 在所有场景都等价误区2在所有 T::xxx 前都加 typename误区3把 typename 和 .template 混淆误区4在 C20 前使用 typename 在概念concept中九、这一篇的收获一、typename 与 class模板参数中的等价性cpp// 以下两种写法完全等价 template typename T void func(T t) {} template class T void func(T t) {}历史原因class是 C98 引入模板时的关键字typename是后来加入的。现在更推荐使用typename因为它更准确地表达了“类型参数”的含义不一定非得是类。唯一例外模板模板参数中只能用classC17 后也可以用typename。cpp// 模板模板参数老语法只能用 class template template typename class Container struct MyClass {}; // C17 开始也可以写 typename template template typename typename Container struct MyClass {}; // C17二、typename 的真正用途声明从属依赖名称这是typename最重要的用途——也是最容易混淆的地方。问题场景cpptemplate 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明确告诉编译器“这是一个类型”。cpptemplate typename T void printSize(const T container) { typename T::size_type size container.size(); // ✅ 正确 cout size endl; } // 使用 vectorint v {1, 2, 3}; printSize(v); // T vectorint, T::size_type size_t哪些情况需要 typename场景是否需要 typename示例模板参数中的T::XXX✅ 需要typename T::value_type使用std::vectorT::iterator✅ 需要typename std::vectorT::iterator基类列表中的BaseT::Type❌ 不需要class Derived : public BaseT::Type成员初始化列表❌ 不需要Class() : BaseT::Type(10) {}完整示例cpp#include iostream #include vector using namespace std; // 泛型函数打印容器中所有元素 template typename Container void printAll(const Container c) { // 需要 typenameiterator 依赖于 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() { vectorint v {1, 2, 3, 4}; printAll(v); ValueTypeOfvectorint::type x 100; // x 是 int cout x endl; return 0; }三、从属类型 vs 非从属类型cpptemplate typename T struct MyClass { // 非从属类型不依赖于 T编译器直接知道 static int staticValue; // 从属类型依赖于 T需要用 typename using Iterator typename T::iterator; // ✅ 需要 typename using ValueType typename T::value_type; // ✅ 需要 typename // 非从属std::vectorint 是具体类型 using IntVec std::vectorint; // ❌ 不需要 typename };判断规则如果一个类型名称依赖于模板参数T、U等它就是“从属类型”前面需要加typename。四、template 关键字消除模板成员调用歧义类似typename的歧义也发生在调用模板成员函数时。问题场景cpptemplate typename T void process(T obj) { // obj.templateMethodint() 是什么意思 // 编译器不知道 templateMethod 是模板函数还是成员变量 obj.templateMethodint(); // 如果不加 template编译错误 }编译器看到和可能认为这是小于号和大于号而不是模板参数列表。解决方案用.template明确告诉编译器。cpptemplate typename T void process(T obj) { // .template 告诉编译器后面是一个模板成员函数 obj.template templateMethodint(); // 同样适用于指针-template T* ptr obj; ptr-template templateMethoddouble(); }完整示例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 printint(); obj.template printdouble(); // 如果 print 不是模板函数就不需要 // obj.print(); // 普通成员函数 } int main() { MyClass obj; callPrint(obj); // 编译成功 return 0; }使用场景总结语法用途示例typename声明从属类型名称typename T::iterator.template调用模板成员函数obj.template funcint()-template通过指针调用模板成员函数ptr-template funcint()::template通过作用域运算符调用T::template funcint()五、依赖类型名的常见错误错误1忘记写 typenamecpptemplate typename T void func(const T container) { T::value_type x container[0]; // ❌ 编译错误 // 应该是 typename T::value_type }错误2在不该写的地方写了 typenamecppclass MyClass { typename int x; // ❌ 错误int 不是从属类型 };错误3在基类列表和初始化列表中使用 typenamecpptemplate typename T class Derived : public BaseT::Type { // ❌ 这里不能写 typename public: Derived() : BaseT::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 printArgs(args), ...); // C17 折叠表达式 } int main() { // 测试 typename 依赖类型 vectorint vec {1, 2, 3, 4, 5}; listdouble lst {1.1, 2.2, 3.3}; cout vector sum: sum(vec) endl; cout list sum: sum(lst) endl; // 测试元素类型萃取 ElementTypevectorint::type x 100; ElementTypelistdouble::type y 3.14; cout x x , y y endl; // 测试 .template 调用 Printer p; callPrintAll(p, 42, 3.14, hello); return 0; }输出textvector sum: 15 list sum: 6.6 x 100, y 3.14 Printer: 42 Printer: 3.14 Printer: hello七、标准库中的应用std::allocator_traitscpptemplate typename T struct allocator_traits { // 需要 typenamepointer 是从属类型 using pointer typename T::pointer; // 需要 .templaterebind 是模板成员 template typename U using rebind typename T::template rebindU; };std::iterator_traitscpptemplate 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::vectorint::iterator不依赖 T不需要。误区3把 typename 和 .template 混淆typename声明类型.template声明模板成员函数调用。误区4在 C20 前使用 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 类。