先看这两篇文章。
https://blog.csdn.net/vanturman/article/details/80269081
https://blog.csdn.net/pizzq/article/details/1487004
使用typename的规则
最后这个规则看起来有些复杂,可以参考MSDN:
- typename在下面情况下禁止使用:
- 模板定义之外,即typename只能用于模板的定义中
- 非限定类型,比如前面介绍过的
int
,vector<int>
之类 - 基类列表中,比如
template <class T> class C1 : T::InnerType
不能在T::InnerType
前面加typename - 构造函数的初始化列表中
- 如果类型是依赖于模板参数的限定名,那么在它之前必须加typename(除非是基类列表,或者在类的初始化成员列表中)
- 其它情况下typename是可选的,也就是说对于一个不是依赖名的限定名,该名称是可选的,例如
vector<int> vi;
一句话,如果是用于模板定义的依赖于模板参数名称(除非是基类列表,或者在类的初始化成员列表中),那么只有使用typename修饰编译器才会将改名称当成类型.
上面注意两点:
1.记住这里修饰的是限定名,而不是限定名后面的变量!!
2.这里说的是类型依赖于模板参数,是模板参数不是类型,比如MyClass<T>,这是一个类型。
template <typename T>
class MyClass
{
public:
typename T::myIterator a;
typename T::myIterator2 b = 4;
private:
};
class ContatinsType
{
public:
struct myIterator{ };
typedef int myIterator2;
private:
};
看这句:typename T::myIterator a; 这里的myIterator是限定名(使用了限定符::),也依赖于模板名称(使用了T)
因此这里一定要使用typename 修饰表明这里的限定名myIterator(typename修饰的是限定名,而不是a,b)是一个类型名称。如果不用,看下面的代码,我们将代码修改如下:
template <typename T>
class MyClass
{
public:
T::myIterator a;
private:
};
class ContatinsType
{
public:
static int myIterator;
private:
};
int ContatinsType::myIterator = 5;
这里MyClass没有使用typename修饰myIterator,那怎么知道这是一个名称,还是变量,而且它在类ContainsType中的确是一个变量!!
以上可以看出,依赖于模板名称的限定名可以是变量(上面那个就是),可以是一个函数,也可以是一个类型(上上面的代码段),那么不使用typename修饰怎么知道这个限定名是类型名称!
下面举例说明不需要typename的例子。
#include <iostream>
#include <vector>
using namespace std;
template <typename T>
class ContatinsType
{
public:
typedef int myInt;
private:
};
template <typename T>
class MyClass
{
public:
typedef ContatinsType<int>::myInt myInt2;
private:
};
int main()
{
MyClass<ContatinsType<int>>::myInt2 i2 = 3;
cout << "i2: "<< i2<< endl;
}
为什么这里只是使用了typedef ContainsType<int>::myInt myInt2;因为这里ContainsType<int>是一个类型,也就是说myInt不是依赖模板参数的限定符!!所以,这里的typename可以不使用。
通常我们会在模板里使用它,例子如下:
template<typename T>
class MyIteator
{
public:
static T a;//只是声明,并没有定义
};
int MyIteator<int>::a = 3;//类内static成员类外定义
template <typename T>
class MyClass
{
public:
using _MyBase = MyIteator<T>;
using _iteator = typename _MyBase;//使用typename表明 _MyBase是一个类名称,而非什么变量,表达式等等
};
int main()
{
MyClass<int>::_iteator myIteator;
cout << myIteator.a << endl;
}
这样使用的方式和vector<int>::iteator i_iteator;这样声明一个vector<int>::iteator类型的对象很相似。
上面MyClass还可以修改为:
template <typename T>
class MyClass
{
public:
typedef typename MyIteator<T> _iteator;
};
这里的_iteator是依赖于模板参数的名称,但它不是限定名,因此可以省略typename
template<typename T>
class MyIteator
{
public:
static T a;//只是声明,并没有定义
};
int MyIteator<int>::a = 3;//类内static成员类外定义
template <typename T>
class MyClass
{
public:
typedef MyIteator<T>* _iteator;
};
int main()
{
MyClass<int>::_iteator myIteator;
cout << myIteator->a << endl;
}
像上面这样也是可以的。最好还是写上比较好,别人一看就知道是什么。
在泛型编程里,我们还经常使用一种叫特性的东西,先看代码:
#include <iostream>
#include <vector>
using namespace std;
//主模板
template <typename T> class SigmaTraits{};
template<>
class SigmaTraits<char> {
public:
typedef char ReturnType;
};
template<>
class SigmaTraits<int>
{
public:
typedef int ReturnType;
private:
};
template <typename T>
typename SigmaTraits<T>::ReturnType Sigma( const T const* start, const T const* end ) {
typedef typename SigmaTraits<T>::ReturnType ReturnType;
int s = ReturnType();//Value initialization https://en.cppreference.com/w/cpp/language/value_initialization
while (start!= end)
{
s += *start++;
}
return s;
}
int main()
{
char vchar[] = "a";
cout << Sigma<char>(vchar, vchar + strlen(vchar)) << endl;
int nums[] = {1,2,3};
cout << Sigma<int>(nums, nums + sizeof(nums)/sizeof(nums[0])) << endl;
}
模板类SigmaTraits叫做特性模板,它含有其参数型别T的一个特性,也就是ReturnType。
在模板函数Sigm( const T const* start, const T const* end )中就使用了typename, typedef typename SigmaTraits<T>::ReturnType ReturnType; 使用 typename 表明限定名 SigmaTraits<T>::ReturnType 是一个类型名称。虽然在这个例子中你可以不使用typename,因为 SigmaTraits<T>是类型,不是模板参数T,只有是依赖于模板参数Td的限定名才会强制使用typename表明限定名是一个类型名称(开头提到的注意2: 这里说的是类型依赖于模板参数,是模板参数不是类型,比如MyClass<T>,这是一个类型)。
参考: