Error C7510 ‘iterator’: use of dependent type name must be prefixed with ‘typename’
//报错代码
template <typename T>
void myprint(T& v) {
T::iterator it = v.begin(); //error c7510 occur
for (; it != v.end(); it++)
{
std::cout << *it << std::endl;
}
}
//改为如下
typename T::iterator it = v.begin(); //告诉编译器 T::iterator是类型而不是变量
分析
我想定义一个指针it
,指向的类型是包含在类作用域T
中的iterator
//可能存在包含iterator类型的结构
struct AClass{
struct iterator{
//...
}
}
//如此实例化
myprint<aClass>();
问题
限定名和非限定名
限定名(qualified name),故名思义,是限定了命名空间的名称。看下面这段代码,
cout
和endl
就是限定名:#include <iostream> int main() { std::cout << "Hello world!" << std::endl; }
cout
和endl
前面都有std::
,它限定了std
这个命名空间,因此称其为限定名。如果在上面这段代码中,前面用
using std::cout;
或者using namespace std;
,然后使用时只用cout
和endl
,它们的前面不再有空间限定std::
,所以此时的cout
和endl
就叫做非限定名(unqualified name)。依赖名和非依赖名
依赖名(dependent name)是指依赖于模板参数的名称,而非依赖名(non-dependent name)则相反,指不依赖于模板参数的名称。看下面这段代码:
template <class T> class MyClass { int i; vector<int> vi; vector<int>::iterator vitr; T t; vector<T> vt; vector<T>::iterator viter; };
因为是内置类型,所以类中前三个定义的类型在声明这个模板类时就已知。然而对于接下来的三行定义,只有在模板实例化时才能知道它们的类型,因为它们都依赖于模板参数
T
。因此,T
,vector<T>
和vector<T>::iterator
称为依赖名。前三个定义叫做非依赖名。更为复杂一点,如果用了
typedef T U; U u;
,虽然T
没再出现,但是U
仍然是依赖名。由此可见,不管是直接还是间接,只要依赖于模板参数,该名称就是依赖名。类作用域
在类外部访问类中的名称时,可以使用类作用域操作符,形如
MyClass::name
的调用通常存在三种:静态数据成员、静态成员函数和嵌套类型:struct MyClass { static int A; static int B(); typedef int C; }
MyClass::A
,MyClass::B
,MyClass::C
分别对应着上面三种。
通过前面类作用域一节的介绍,我们可以知道,T::iterator
实际上可以是以下三种中的任何一种类型:
- 静态数据成员
- 静态成员函数
- 嵌套类型
前面例子中的AClass::iterator
是嵌套类型,完全没有问题。可如果是静态数据成员呢?如果实例化foo
模板函数的类型是像这样的:
struct BClass { static int iterator; // ...};
然后如此实例化myprint
的类型参数:
myprint<BClass>(new Bclass);
那么,T::iterator * iter;
被编译器实例化为BClass::iterator * iter;
,这是什么?前面是一个静态成员变量而不是类型,那么这便成了一个乘法表达式,只不过iter
在这里没有定义,编译器会报错:
error C2065: ‘iter’ : undeclared identifier
但如果iter
是一个全局变量,那么这行代码将完全正确,它是表示计算两数相乘的表达式,返回值被抛弃。
同一行代码能以两种完全不同的方式解释,而且在模板实例化之前,完全没有办法来区分它们,这绝对是滋生各种bug的温床。这时C++标准委员会再也忍不住了,与其到实例化时才能知道到底选择哪种方式来解释以上代码,委员会决定引入一个新的关键字,这就是typename
。
使用typename的规则
最后这个规则看起来有些复杂,可以参考MSDN:
- typename在下面情况下禁止使用:
- 模板定义之外,即typename只能用于模板的定义中
- 非限定类型,比如前面介绍过的
int
,vector<int>
之类 - 基类列表中,比如
template <class T> class C1 : T::InnerType
不能在T::InnerType
前面加typename - 构造函数的初始化列表中
- 如果类型是依赖于模板参数的限定名,那么在它之前必须加typename(除非是基类列表,或者在类的初始化成员列表中)
- 其它情况下typename是可选的,也就是说对于一个不是依赖名的限定名,该名称是可选的,例如
vector<int> vi;