C++模版中的typename关键字和嵌套依赖类型(依赖名称)

本文深入探讨了C++模板中typename和class的使用区别,特别是在嵌套依赖类型名的情况下,如何正确使用typename来避免编译错误,并解释了在继承列表或成员初始化列表中的基类初始化时无需使用typename的例外情况。

关于C++中模版常用的的typename和class,先写结论吧:

  • 在声明模版参数时,class和typename关键是等价的,可以相互替换。
  • 在涉及“嵌套依赖类型名”(nested dependent type name)的时候,必须用typename关键字去标识。
  • 规则2有个例外,就是在继承列表或者成员初始化列表中的基类初始化时,可以不用typename去标识“嵌套依赖类型”。
在早期的C++标准中,模版参数的关键字是通过class来标识的,后来引入了typename关键字。typename关键字本质上是标识一个类型,所以在模版参数定义时可以代替class。然而typename关键字有更重要的作用,即用来标识“嵌套依赖类型”,那么什么是“嵌套依赖类型”【有时也称为依赖名称或依赖类型,指这个名称依赖于模版参数T,可以通过比较“限定性名称”来理解,“限定性名称”是指由具体的类作为前缀,也就是这个名称在具体的类中,使用是需要具体的类加双冒号作为前缀来引用】,可以看下面一个例子:
?
1
2
3
4
5
6
template < typename T>
void print2nd( const T& container)
{
     T::const_iterator * x;
     ...
}
在print2nd这样一个模版函数中,变量x的类型依赖于模版参数T,因此它的具体类型只有在编译时的模版推导过程(template deduction)中才能够被确定。这样的类型称之为“嵌套依赖类型”(nested dependent type name),但是很遗憾,编译器并不知道const_iterator是一个类型还是一个静态变量。在默认情况下,C++标准会让编译器会把const_iterator做为一个静态变量处理(你没看错,编译器很多时候比我们想象的愚蠢),所以你会得到如下的编译错误:
?
1
2
3
leoxiang@debian:~$ g++ -std=c++0x test .cpp
test .cpp: In function ‘void print2nd(const T&)’:
test .cpp:16: error: ‘x’ was not declared in this scope
这时候,就需要typename关键字出场了,它显式的告诉编译器T::const_iterator是一个类型,因此这一行的语义就是声明一个嵌套依赖类型的局部变量,而不是一个静态变量乘以x。
?
1
2
3
4
5
6
template < typename T>
void print2nd( const T& container)
{
     typename T::const_iterator * x;
     ...
}
好了,最后说一个不需要使用typename的例外:就是在继承列表或者成员初始化列表中的基类初始化时,可以不用typename去标识“嵌套依赖类型”。说起来比较绕,看例子就明白了:
?
1
2
3
4
5
6
7
8
9
10
template < typename T>
class Derived: public Base<T>::Nested //in base class list, no typename
{
  public :
   explicit Derived( int x)
  : Base<T>::Nested(x) //in member init list, no typename
  {
    typename Base<T>::Nested temp; //nested dependent type, need typename
  }
};

### 嵌套依赖名称的解析问题 在 C++ 模板编程中,当一个类型名称依赖模板参数时,编译器可能无法确定该名称类型、静态成员、函数或其他实体。这种不确定性称为“名称解析歧义”。例如,在模板内部使用 `T::nested_type` 时,`T` 是模板参数,而 `nested_type` 可能是一个类型别名、静态常量、函数或成员变量。由于 `T` 的具体类型模板实例化之前是未知的,编译器无法判断 `T::nested_type` 的确切含义。 在这种情况下,C++ 语言要求使用 `typename` 关键字来明确指定该名称是一个类型。如果省略 `typename`,编译器将默认它不是一个类型,从而可能导致编译错误。例如: ```cpp template<typename T> class Wrapper { T::nested_type* ptr; // 编译错误:无法确定 T::nested_type 是类型 }; ``` 通过添加 `typename`,可以明确告诉编译器 `T::nested_type` 是一个类型,从而避免歧义: ```cpp template<typename T> class Wrapper { typename T::nested_type* ptr; // 正确:明确 T::nested_type 是类型 }; ``` 这种用法确保了模板在实例化时能够正确解析嵌套依赖类型名称,避免因类型不确定性导致的错误[^1]。 ### `typename` 与 `class` 的区别 虽然 `typename` `class` 在声明模板类型参数时功能相同,但在处理嵌套依赖类型名称时,`typename` 是唯一合法的选择。例如,以下代码将导致编译错误: ```cpp template<typename T> void exampleFunction() { class T::SomeType var; // 错误:不能使用 class 来指定嵌套依赖类型 } ``` 相比之下,使用 `typename` 是正确的做法: ```cpp template<typename T> void exampleFunction() { typename T::SomeType var; // 正确:使用 typename 明确指定嵌套依赖类型 } ``` 因此,`typename` 在模板编程中具有更广泛的用途,特别是在涉及依赖类型名称的情况下[^2]。 ### 示例说明 以下是一个完整的示例,展示了 `typename` 在解决嵌套依赖类型名称歧义中的作用: ```cpp #include <iostream> template<typename T> class Wrapper { public: typename T::value_type value; // 使用 typename 明确 T::value_type 是类型 }; struct Example { using value_type = int; // 定义嵌套类型别名 }; int main() { Wrapper<Example> w; w.value = 42; std::cout << w.value << std::endl; // 输出 42 return 0; } ``` 在这个例子中,`Wrapper` 类模板依赖类型 `T`,并使用 `T::value_type` 来声明一个成员变量。由于 `T::value_type` 是一个依赖模板参数的嵌套类型,必须使用 `typename` 来明确它是一个类型。否则,编译器将无法正确解析该类型,导致编译错误[^3]。 ### 总结 在 C++ 模板编程中,`typename` 关键字用于解决嵌套依赖类型名称的解析歧义。当一个类型名称依赖模板参数时,编译器无法自动判断它是否为类型。此时,必须使用 `typename` 来显式声明该名称是一个类型,以确保模板在实例化时能够正确解析。与 `class` 不同,`typename` 不仅可以用于声明模板类型参数,还可以用于指定嵌套依赖类型名称,使其在模板编程中具有更广泛的应用场景[^5]。 --- ###
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值