<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	>

<channel>
	<title>梁剑的Blog &#187; typename</title>
	<atom:link href="http://icomes.net/tag/typename/feed/" rel="self" type="application/rss+xml" />
	<link>http://icomes.net</link>
	<description>做有趣的事，做有用的人</description>
	<lastBuildDate>Fri, 20 Aug 2010 16:36:35 +0000</lastBuildDate>
	<language>en</language>
	<sy:updatePeriod>hourly</sy:updatePeriod>
	<sy:updateFrequency>1</sy:updateFrequency>
	<generator>http://wordpress.org/?v=3.0.1</generator>
		<item>
		<title>C++箴言：理解typename的两个含义</title>
		<link>http://icomes.net/2008/12/10/c%e7%ae%b4%e8%a8%80%ef%bc%9a%e7%90%86%e8%a7%a3typename%e7%9a%84%e4%b8%a4%e4%b8%aa%e5%90%ab%e4%b9%89/</link>
		<comments>http://icomes.net/2008/12/10/c%e7%ae%b4%e8%a8%80%ef%bc%9a%e7%90%86%e8%a7%a3typename%e7%9a%84%e4%b8%a4%e4%b8%aa%e5%90%ab%e4%b9%89/#comments</comments>
		<pubDate>Wed, 10 Dec 2008 01:32:13 +0000</pubDate>
		<dc:creator>梁剑</dc:creator>
				<category><![CDATA[C++]]></category>
		<category><![CDATA[技术笔记]]></category>
		<category><![CDATA[template]]></category>
		<category><![CDATA[typedef]]></category>
		<category><![CDATA[typename]]></category>

		<guid isPermaLink="false">http://tridot.cn/?p=205</guid>
		<description><![CDATA[当然，这是转载的，放在这里只是做个记录。 写得很好，解决了我的疑惑。 我的疑惑是： 对于这段代码， template &#60;typename T_thread_type, typename T_dispatch_type&#62; class Pool { &#8230; typedef typename std::queue&#60;T_dispatch_type,typename std::deque&#60;T_dispatch_type&#62; &#62; queue_type; 为什么要用到两个typename，而且放到这样的位置？ 看完下面的文字后，我的结论是：上面不是标准用法，这里的typename是多余的，标准写法应该是： typedef std::queue&#60;T_dispatch_type,std::deque&#60;T_dispatch_type&#62; &#62; queue_type; 因为在这个定义中，不确定的是T_dispatch_type，而不是std。 &#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212; 问题：在下面的 template declarations（模板声明）中 class 和 typename 有什么不同？ template&#60;class T&#62; class Widget; // uses &#8220;class&#8221; template&#60;typename T&#62; class Widget; // uses &#8220;typename&#8221; 答案：没什么不同。在声明一个 template type parameter（模板类型参数）的时候，class 和 typename 意味着完全相同的东西。一些程序员更喜欢在所有的时间都用 [...]]]></description>
			<content:encoded><![CDATA[<p>当然，这是转载的，放在这里只是做个记录。<br />
写得很好，解决了我的疑惑。</p>
<p>我的疑惑是：</p>
<p>对于这段代码，</p>
<p style="padding-left: 30px;">template &lt;typename T_thread_type, typename T_dispatch_type&gt;<br />
class Pool<br />
{<br />
&#8230;<br />
typedef <strong>typename</strong> std::queue&lt;T_dispatch_type,<strong>typename</strong> std::deque&lt;T_dispatch_type&gt; &gt; queue_type;</p>
<p>为什么要用到两个typename，而且放到这样的位置？</p>
<p>看完下面的文字后，我的结论是：上面不是标准用法，这里的typename<strong>是多余的</strong>，标准写法应该是：</p>
<p style="padding-left: 30px;">typedef std::queue&lt;T_dispatch_type,std::deque&lt;T_dispatch_type&gt; &gt; queue_type;</p>
<p>因为在这个定义中，不确定的是T_dispatch_type，而不是std。</p>
<p>&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;</p>
<p>问题：在下面的 template declarations（模板声明）中 class 和 typename 有什么不同？</p>
<table border="1" width="90%" align="center" bgcolor="#e3e3e3" bordercolor="#cccccc">
<tbody>
<tr>
<td>template&lt;class T&gt; class Widget; // uses &#8220;class&#8221;<br />
template&lt;typename T&gt; class Widget; // uses &#8220;typename&#8221;</td>
</tr>
</tbody>
</table>
<p>答案：没什么不同。在声明一个 template type parameter（模板类型参数）的时候，class 和 typename 意味着完全相同的东西。一些程序员更喜欢在所有的时间都用 class，因为它更容易输入。其他人（包括我本人）更喜欢 typename，因为它暗示着这个参数不必要是一个 class type（类类型）。少数开发者在任何类型都被允许的时候使用 typename，而把 class 保留给仅接受 user-defined types（用户定义类型）的场合。但是从 C++ 的观点看，class 和 typename 在声明一个 template parameter（模板参数）时意味着完全相同的东西。</p>
<p>然而，C++ 并不总是把 class 和 typename 视为等同的东西。有时你必须使用 typename。为了理解这一点，我们不得不讨论你会在一个 template（模板）中涉及到的两种名字。</p>
<p>假设我们有一个函数的模板，它能取得一个 STL-compatible container（STL 兼容容器）中持有的能赋值给 ints 的对象。进一步假设这个函数只是简单地打印它的第二个元素的值。它是一个用糊涂的方法实现的糊涂的函数，而且就像我下面写的，它甚至不能编译，但是请将这 些事先放在一边——有一种方法能发现我的愚蠢：</p>
<table border="1" width="90%" align="center" bgcolor="#e3e3e3" bordercolor="#cccccc">
<tbody>
<tr>
<td>template&lt;typename C&gt; // print 2nd element in<br />
void print2nd(const C&amp; container) // container;<br />
{<br />
// this is not valid C++!<br />
if (container.size() &gt;= 2) {<br />
C::const_iterator iter(container.begin()); // get iterator to 1st element<br />
++iter; // move iter to 2nd element<br />
int value = *iter; // copy that element to an int<br />
std::cout &lt;&lt; value; // print the int<br />
}<br />
}</td>
</tr>
</tbody>
</table>
<p>我突出了这个函数中的两个 local variables（局部变量），iter 和 value。iter 的类型是 C::const_iterator，一个依赖于 template parameter（模板参数）C 的类型。一个 template（模板）中的依赖于一个 template parameter（模板参数）的名字被称为 dependent names（依赖名字）。当一个 dependent names（依赖名字）嵌套在一个 class（类）的内部时，我称它为 nested dependent name（嵌套依赖名字）。C::const_iterator 是一个 nested dependent name（嵌套依赖名字）。实际上，它是一个 nested dependent type name（嵌套依赖类型名），也就是说，一个涉及到一个 type（类型）的 nested dependent name（嵌套依赖名字）。</p>
<p>print2nd 中的另一个 local variable（局部变量）value 具有 int 类型。int 是一个不依赖于任何 template parameter（模板参数）的名字。这样的名字以 non-dependent names（非依赖名字）闻名。（我想不通为什么他们不称它为 independent names（无依赖名字）。如果，像我一样，你发现术语 &#8220;non-dependent&#8221; 是一个令人厌恶的东西，你就和我产生了共鸣，但是 &#8220;non-dependent&#8221; 就是这类名字的术语，所以，像我一样，转转眼睛放弃你的自我主张。）</p>
<p>nested dependent name（嵌套依赖名字）会导致解析困难。例如，假设我们更加愚蠢地以这种方法开始 print2nd：</p>
<table border="1" width="90%" align="center" bgcolor="#e3e3e3" bordercolor="#cccccc">
<tbody>
<tr>
<td>template&lt;typename C&gt;<br />
void print2nd(const C&amp; container)<br />
{<br />
C::const_iterator * x;<br />
&#8230;<br />
}</td>
</tr>
</tbody>
</table>
<p>这看上去好像是我们将 x 声明为一个指向 C::const_iterator 的 local variable（局部变量）。但是它看上去如此仅仅是因为我们知道 C::const_iterator 是一个 type（类型）。但是如果 C::const_iterator 不是一个 type（类型）呢？如果 C 有一个 static data member（静态数据成员）碰巧就叫做 const_iterator 呢？再如果 x 碰巧是一个 global variable（全局变量）的名字呢？在这种情况下，上面的代码就不是声明一个 local variable（局部变量），而是成为 C::const_iterator 乘以 x！当然，这听起来有些愚蠢，但它是可能的，而编写 C++ 解析器的人必须考虑所有可能的输入，甚至是愚蠢的。</p>
<p>直到 C 成为已知之前，没有任何办法知道 C::const_iterator 到底是不是一个 type（类型），而当 template（模板）print2nd 被解析的时候，C 还不是已知的。C++ 有一条规则解决这个歧义：如果解析器在一个 template（模板）中遇到一个 nested dependent name（嵌套依赖名字），它假定那个名字不是一个 type（类型），除非你用其它方式告诉它。缺省情况下，nested dependent name（嵌套依赖名字）不是 types（类型）。（对于这条规则有一个例外，我待会儿告诉你。）</p>
<p>记住这个，再看看 print2nd 的开头：</p>
<table border="1" width="90%" align="center" bgcolor="#e3e3e3" bordercolor="#cccccc">
<tbody>
<tr>
<td>template&lt;typename C&gt;<br />
void print2nd(const C&amp; container)<br />
{<br />
if (container.size() &gt;= 2) {<br />
C::const_iterator iter(container.begin()); // this name is assumed to<br />
&#8230; // not be a type</td>
</tr>
</tbody>
</table>
<p>这为什么不是合法的 C++ 现在应该很清楚了。iter 的 declaration（声明）仅仅在 C::const_iterator 是一个 type（类型）时才有意义，但是我们没有告诉 C++ 它是，而 C++ 就假定它不是。要想转变这个形势，我们必须告诉 C++ C::const_iterator 是一个 type（类型）。我们将 typename 放在紧挨着它的前面来做到这一点：</p>
<table border="1" width="90%" align="center" bgcolor="#e3e3e3" bordercolor="#cccccc">
<tbody>
<tr>
<td>template&lt;typename C&gt; // this is valid C++<br />
void print2nd(const C&amp; container)<br />
{<br />
if (container.size() &gt;= 2) {<br />
typename C::const_iterator iter(container.begin());<br />
&#8230;<br />
}<br />
}</td>
</tr>
</tbody>
</table>
<p>通用的规则很简单：在你涉及到一个在 template（模板）中的 nested dependent type name（嵌套依赖类型名）的任何时候，你必须把单词 typename 放在紧挨着它的前面。（重申一下，我待会儿要描述一个例外。）</p>
<p>typename 应该仅仅被用于标识 nested dependent type name（嵌套依赖类型名）；其它名字不应该用它。例如，这是一个取得一个 container（容器）和这个 container（容器）中的一个 iterator（迭代器）的 function template（函数模板）：</p>
<table border="1" width="90%" align="center" bgcolor="#e3e3e3" bordercolor="#cccccc">
<tbody>
<tr>
<td>template&lt;typename C&gt; // typename allowed (as is &#8220;class&#8221;)<br />
void f(const C&amp; container, // typename not allowed<br />
typename C::iterator iter); // typename required</td>
</tr>
</tbody>
</table>
<p>C 不是一个 nested dependent type name（嵌套依赖类型名）（它不是嵌套在依赖于一个 template parameter（模板参数）的什么东西内部的），所以在声明 container 时它不必被 typename 前置，但是 C::iterator 是一个 nested dependent type name（嵌套依赖类型名），所以它必需被 typename 前置。</p>
<p>&#8220;typename must precede nested dependent type names&#8221;（“typename 必须前置于嵌套依赖类型名”）规则的例外是 typename 不必前置于在一个 list of base classes（基类列表）中的或者在一个 member initialization list（成员初始化列表）中作为一个 base classes identifier（基类标识符）的 nested dependent type name（嵌套依赖类型名）。例如：</p>
<table border="1" width="90%" align="center" bgcolor="#e3e3e3" bordercolor="#cccccc">
<tbody>
<tr>
<td>template&lt;typename T&gt;<br />
class Derived: public Base&lt;T&gt;::Nested {<br />
// base class list: typename not<br />
public: // allowed<br />
explicit Derived(int x)<br />
: Base&lt;T&gt;::Nested(x) // base class identifier in mem<br />
{<br />
// init. list: typename not allowed</p>
<p>typename Base&lt;T&gt;::Nested temp; // use of nested dependent type<br />
&#8230; // name not in a base class list or<br />
} // as a base class identifier in a<br />
&#8230; // mem. init. list: typename required<br />
};</td>
</tr>
</tbody>
</table>
<p>这样的矛盾很令人讨厌，但是一旦你在经历中获得一点经验，你几乎不会在意它。</p>
<p>让我们来看最后一个 typename 的例子，因为它在你看到的真实代码中具有代表性。假设我们在写一个取得一个 iterator（迭代器）的 function template（函数模板），而且我们要做一个 iterator（迭代器）指向的 object（对象）的局部拷贝 temp，我们可以这样做：</p>
<table border="1" width="90%" align="center" bgcolor="#e3e3e3" bordercolor="#cccccc">
<tbody>
<tr>
<td>template&lt;typename IterT&gt;<br />
void workWithIterator(IterT iter)<br />
{<br />
typename std::iterator_traits&lt;IterT&gt;::value_type temp(*iter);<br />
&#8230;<br />
}</td>
</tr>
</tbody>
</table>
<p>不要让 std::iterator_traits&lt;IterT&gt;::value_type 吓倒你。那仅仅是一个 standard traits class（标准特性类）的使用，用 C++ 的说法就是 &#8220;the type of thing pointed to by objects of type IterT&#8221;（“被类型为 IterT 的对象所指向的东西的类型”）。这个语句声明了一个与 IterT objects 所指向的东西类型相同的 local variable（局部变量）(temp)，而且用 iter 所指向的 object（对象）对 temp 进行了初始化。如果 IterT 是 vector&lt;int&gt;::iterator，temp 就是 int 类型。如果 IterT 是 list&lt;string&gt;::iterator，temp 就是 string 类型。因为 std::iterator_traits&lt;IterT&gt;::value_type 是一个 nested dependent type name（嵌套依赖类型名）（value_type 嵌套在 iterator_traits&lt;IterT&gt; 内部，而且 IterT 是一个 template parameter（模板参数）），我们必须让它被 typename 前置。</p>
<p>如果你觉得读 std::iterator_traits&lt;IterT&gt;::value_type 令人讨厌，就想象那个与它相同的东西来代表它。如果你像大多数程序员，对多次输入它感到恐惧，那么你就需要创建一个 typedef。对于像 value_type 这样的 traits member names（特性成员名），一个通用的惯例是 typedef name 与 traits member name 相同，所以这样的一个 local typedef 通常定义成这样：</p>
<table border="1" width="90%" align="center" bgcolor="#e3e3e3" bordercolor="#cccccc">
<tbody>
<tr>
<td>template&lt;typename IterT&gt;<br />
void workWithIterator(IterT iter)<br />
{<br />
typedef typename std::iterator_traits&lt;IterT&gt;::value_type value_type;</p>
<p>value_type temp(*iter);<br />
&#8230;<br />
}</td>
</tr>
</tbody>
</table>
<p>很多程序员最初发现 &#8220;typedef typename&#8221; 并列不太和谐，但它是涉及 nested dependent type names（嵌套依赖类型名）规则的一个合理的附带结果。你会相当快地习惯它。你毕竟有着强大的动机。你输入 typename std::iterator_traits&lt;IterT&gt;::value_type 需要多少时间？</p>
<p>作为结束语，我应该 提及编译器与编译器之间对围绕 typename 的规则的执行情况的不同。一些编译器接受必需 typename 时它却缺失的代码；一些编译器接受不许 typename 时它却存在的代码；还有少数的（通常是老旧的）会拒绝 typename 出现在它必需出现的地方。这就意味着 typename 和 nested dependent type names（嵌套依赖类型名）的交互作用会导致一些轻微的可移植性问题。</p>
<p>Things to Remember</p>
<p>·在声明 template parameters（模板参数）时，class 和 typename 是可互换的。</p>
<p>·用 typename 去标识 nested dependent type names（嵌套依赖类型名），在 base class lists（基类列表）中或在一个 member initialization list（成员初始化列表）中作为一个 base class identifier（基类标识符）时除外。</p>
]]></content:encoded>
			<wfw:commentRss>http://icomes.net/2008/12/10/c%e7%ae%b4%e8%a8%80%ef%bc%9a%e7%90%86%e8%a7%a3typename%e7%9a%84%e4%b8%a4%e4%b8%aa%e5%90%ab%e4%b9%89/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
	</channel>
</rss>
