一. 内容
-
提一个问题,以下 template 声明式中,class 和 typename 有什么不同?
template<class T> class Demo; template<typename T> class Demo;
答案:没有不同。
当我们声明 template 类型参数时, class 和 typename 的意义完全相同
。某些程序员喜欢 class,因为可以少打几个字。
其他人,包括我,比较喜欢 typename ,因为它
暗示参数并非一定是个 class 类型
。 -
但是,C++ 并不总把 class 和 typename 视为等价,有时候你一定得使用 typename。为了了解,我们先谈谈你可以在 template 内使用(refer to)的两种名称:从属名称和非从属名称。举个例子:
template<typename T> void PrintContainer(const T& Container){ if (Container.Size()>0) { T::const_iterator iter(Container.begin()); //注意 iter ++iter; int value=*iter; //注意 value std::cout<<value<<"\n"; } }
我在代码中强调两个 local 变量:iter 和 value。
iter 的类型是 T::const_iterator,实际是什么取决于 template 参数 T。template 内出现的名称如果
依赖
于某个 template 参数,我们就称之为从属名称
(dependent names)。如果从属名称在 class 内呈嵌套状,我们就称之为嵌套从属名称(nested dependent names)。T::const_iterator就是这样的名称,实际上它还是一个嵌套从属类型名称(nested dependent type name),也就是个嵌套从属名称并且指出是什么类型
。value 的类型是 int 。它
不依赖
于任何 template 参数。我们便称之为非从属名称
(non-dependent names)。 -
嵌套从属名称可能导致解析(parsing)困难。比如上述的 T::const_iterator,如果 const_iterator 不是类型,如果 T 有一个 static 成员变量碰巧被命名为 const_iterator 怎么办呢?C++默认解析此歧义情况为:
如果解析器在 template 中遭遇到一个嵌套从属名称,它便假设这个名称不是类型,而是变量
,除非你告诉它是。告诉解析器为类型只需要在加上 typename 关键字。一般性规则:
任何时候当你想要在 template 使用嵌套从属类型名称,就在其前面加上 typename 关键字
。唯一的例外是在 base class list (基类列)和 member initialization list(成员初值列),举个打印的例子:class Message { public: Message() = default; explicit Message(std::string InText): Text(std::move(InText)) {} void SetText(std::string InText) { Text = std::move(InText); } std::string GetText() { return Text; } private: std::string Text; }; template <typename T> class MessageContainer { public: typedef T ElementType; }; template <typename T> class Printer : private MessageContainer<T>::ElementType { //无需使用 typename public: //使用 typename 表明是一个类型,而不是变量 //使用 typedef 给过长的类型起别名,方便少打字 typedef typename MessageContainer<T>::ElementType ElementType; explicit Printer(): MessageContainer<T>::ElementType() { //无需使用 typename std::cout << typeid(ElementType).name() << "\n"; //class Message } void Log(std::string Text) { ElementType::SetText(Text); std::cout << ElementType::GetText() << "\n"; } }; inline void TryWithPrinter() { Printer<Message> Printer; Printer.Log("HelloWorld"); }
-
对于过长的 typename,可以使用 typedef 给类型起别名:
template<typename T> void PrintContainer(const T& Container){ if (Container.Size()>0) { typedef typename T::const_iterator ConstIterator; ConstIterator iter(Container.begin()); //... } }
二. 总结
- 声明 template 参数时,前缀关键字 class 和 typename 可互换。
- 请使用关键字 typename 标识嵌套从属类型名称;但不得在 base class lists(基类列)或 member initialization lists(成员初值列)内以它作为 base class 修饰符。