首页 > 代码库 > template(4.1)
template(4.1)
5高阶基本技术(Tricky Basics)
本章涵盖实际编程之中层次较高的一些 template基本知识,包括关键词 typename的另一种用途、将member functio(n 成员函数)和 nested clas(s 嵌套类别)定为templates、奇特的 template template parameters、零值初始化(zero initialization)、以字符串字面常数(string literals)作为 function templates arguments 的细节问题…等等。有时候这些问题可能涉及较多技巧,但每一位程序员都应该至少对它们有大略的了解。
5.1 关键词 typename
关键词typename 是C++标准化过程中被引入的,目的在于向编译器说明template 内的某个标识符是个类型(而不是其它什么东西)。考虑下面的例子:
template <typename T> class MyClass { typename T::SubType * ptr; ... };
在这里,第二个 typename 关键词的意思是:SubType 是 class T 内部定义的一个类型,从而ptr是一个「指向 T::SubType 类型」的指针。如果上例没有使用关键词 typename,SubType 会被认为是 class T 的一个 static 成员,于是被编 译器理解为一个具体变量或一个对象,导致以下式子:
T::SubType * ptr
所表达的意义变成:class T 的 static 成员 SubType 与 ptr 相乘。通常 如果 某个与 template parameter 相关的名称是个 类型( type) 时,你就 必须加上关键字typename。更详细的讨论见 9.3.2 节。
typename 的一个典型应用是在 template 程序代码中使用「STL 容器供应的迭代器(iterators)」:
// basics/printcoll.hpp
/* The following code example is taken from the book * "C++ Templates - The Complete Guide" * by David Vandevoorde and Nicolai M. Josuttis, Addison-Wesley, 2002 * * (C) Copyright David Vandevoorde and Nicolai M. Josuttis 2002. * Permission to copy, use, modify, sell and distribute this software * is granted provided this copyright notice appears in all copies. * This software is provided "as is" without express or implied * warranty, and with no claim as to its suitability for any purpose. */ #include <iostream> // print elements of an STL container template <typename T> void printcoll (T const& coll) { typename T::const_iterator pos; // iterator to iterate over coll typename T::const_iterator end(coll.end()); // end position for (pos=coll.begin(); pos!=end; ++pos) { std::cout << *pos << ‘ ‘; } std::cout << std::endl; }
在这个 function template 中,coll 是个 STL 容器,其元素类型为 T。这里使用了 STL 容器的迭代器类型(iterator type)遍历 coll 的所有元素。迭代器类型为const_iterator,每一个STL 容器都声明有这个类型:
class stlcontainer { ... typedef ... iterator; // 可读可写的迭代器 typedef ... const_iterator; // 惟读迭代器 ... };
使用 template type T 的 const_iterator 时,你必须写出全名,并在最前面加上关键词 typename:typename T::const_iterator pos;
.template 构件 ( construct)引入关键词 typename 之后,人们又发现了一个类似问题。考虑以下程序代码,其中使用标准的bitset 类型:
template<int N> void printBitset (std::bitset<N> const& bs) { std::cout << bs.template to_string<char,char_traits<char>,allocator<char> >(); }
此例中的 .template 看起来有些古怪,但是如果没有它,编译器无法得知紧跟其后的 "<" 代表的是个 template argument list 的起始,而非一个「小于」符号。注意,只有当位于 "." 之前的构件(construct)取决于某个 template parameter 时,这个问题才会发生。以上例子中,参数 bs 便是取决(受控)于 template parameter N。结论是 ".template" 或 "->template" 记号只在 templates 之内才能被使用,而且它们必须紧 跟着「与 template parameters 相关」的某物体。细节请见 9.3.3 节, p.132。
5.2 使用 this->
如果 class templates 拥有 base classes,那么其内出现的成员名称 x 并非总是等价于 this->x,即使 x 系继承而来。例如:template <typename T>
class Base { public:
void exit();
}; template <typename T> class Derived : public Base<T> { public: void foo() { exit(); } };
本例在 foo()内解析(resolving)"exit" 符号时,定义于 Base 的exit()会被编译器忽略。因此,你要么获得一个编译错误,要么就是调用一个外部的 exit()。
我们将在 9.4.2 节详细讨论这个问题。目前可视为一个准则:使用与 template 相关的符号时,建议总是以this-> 或Base<T>:: 进行修饰。为避免任何不确定性,可考虑在templates 内对所有成员存取动作(member accesses)进行以上修饰。
5.3 Member Templates(成 员模板 )
Class的成员也可以是templates:既可以是nested class templates,也可以是member function templates。让我再次使用 Stack<> class templates 来展示这项技术的优点,并示范如何运用这种技术。通常只有当两个 stacks 类型相同,也就是当两个stacks 拥有相同类型的元素时,你才能对它们相互赋值(assign),也就是将某个 stack 整体赋值给另一个。你不能把某种类型的 stack赋值给另一种类型的stack,即使这两种类型之间可以隐式转型:
Stack<int> intStack1, intStack2; // stacks for ints Stack<float> floatStack; // stack for floats ... intStack1 = intStack2; // OK:两个 stacks 拥有相同类型 floatStack = intStack1; // ERROR:两个 stacks 类型不同 default assignment 运算符要求左右两边拥有相同类型,而以上情况中,拥有不同类型元素的两个 stacks,其类型并不相同。然而,只要把 assignment 运算符定义为一个 template,你就可以让两个「类型不同,但其元素 可隐式转型」的 stacks 互相赋值。为完成此事,Stack<> 需要这样的声明:
// basics/stack5decl.cpp template <typename T> class Stack { private: std::deque<T> elems; // 元素 public: void push(T const&); // push 元素 void pop(); // pop 元素 T top() const; // 传回 stack 顶端元素 bool empty() const { // stack 是否为空 return elems.empty(); } // 以「元素类型为 T2」的 stack 做为赋值运算的右手端。 template <typename T2> Stack<T>& operator= (Stack<T2> const&); };
对比原先的 Stack,这个版本有如下改动:
1. 增加一个 assignment 运算符,使 Stack 可被赋予一个「拥有不同元素类型 T2」的 stack。
2. 这个 stack 如今使用 deque 作为内部容器。这个改动是上述改动的连带影响。
新增加的 assignment 运算符实作如下:
// basics/stack5assign.hpp
/* The following code example is taken from the book * "C++ Templates - The Complete Guide" * by David Vandevoorde and Nicolai M. Josuttis, Addison-Wesley, 2002 * * (C) Copyright David Vandevoorde and Nicolai M. Josuttis 2002. * Permission to copy, use, modify, sell and distribute this software * is granted provided this copyright notice appears in all copies. * This software is provided "as is" without express or implied * warranty, and with no claim as to its suitability for any purpose. */ template <typename T> template <typename T2> Stack<T>& Stack<T>::operator= (Stack<T2> const& op2) { if ((void*)this == (void*)&op2) { // assignment to itself? return *this; } Stack<T2> tmp(op2); // create a copy of the assigned stack elems.clear(); // remove existing elements while (!tmp.empty()) { // copy all elements elems.push_front(tmp.top()); tmp.pop(); } return *this; }
我们先看看什么样的语法可以定义一个 member template。在拥有 template parameter T 的 template中定义一个内层的(inner)template parameter T2:
template <typename T> template <typename T2> ...
实作这个运算符(成员函数)时,你可能希望取得「赋值符号右侧之op2 stack」的所有必要资料,但是这个stack的类型和目前(此身)类型不同(是的,如果你以两个不同的类型实例化同一个class template,你会得到两个不同的类型),所以只能通过public接口来得到那些数据。 因此惟一能够取得 op2 数据的办法就是调用其 top()函数。你必须经由 top()取得op2 的所有数据,而这必须借助 op2 的一份拷贝来实现:每取得一笔数据,就运用 pop()把该数据从op2 的拷贝中移除。由于top()传回的是stack之中最后(最晚)被推入的元素,所以我们需要反方向把元素安插回去。基于这种需求,这里使用了deque,它提供push_front()操作,可以把一 个元素安插到容器最前面。有了这个 member template,你就可以把一个 int stack 赋值(assign)给一个 float stack:
Stack<int> intStack1, intStack2; //stack for ints Stack<float> floatStack; //stack for floats ... floatStack = intStack1; // OK:两个 stacks 类型不同,但 int 可转型为 float。
当然,这个赋值动作并不会改动 stack和其元素的类型。赋值完成后,floatStack 的元素类型还是 float,而 top()仍然传回 float 值。也许你会认为,这么做会使得类型检查失效,因为你甚至可以把任意类型的元素赋值给另一个 stack。然而事实并非如此。必要的类型检查会在「来源端 stack」的元素(拷贝)被安插到「目 的端 stack」时进行:
elems.push_front(tmp.top());
如 果 你将一个 string stack 赋值 给一个 float stack , 以 上 述 句 编译时就会发生错 误: 「elems.push_front()无法接受 tmp.top()的返回类型」。具体的错误讯息因编译器而异,但含义类似。
Stack<std::string> stringStack; // stack of strings Stack<float> floatStack; // stack of floats … floatStack = stringStack; // 错误:std::string 无法转型为 float
注意,前述的 template assignment 运算符并不取代 default assignment 运算符。如果你在相同类型的 stack 之间赋值,编译器还是会采用 default assignment 运算符。
和先前一样,你可以把内部容器的类型也参数化:
// basics/stack6decl.hpp
/* The following code example is taken from the book * "C++ Templates - The Complete Guide" * by David Vandevoorde and Nicolai M. Josuttis, Addison-Wesley, 2002 * * (C) Copyright David Vandevoorde and Nicolai M. Josuttis 2002. * Permission to copy, use, modify, sell and distribute this software * is granted provided this copyright notice appears in all copies. * This software is provided "as is" without express or implied * warranty, and with no claim as to its suitability for any purpose. */ template <typename T, typename CONT = std::deque<T> > class Stack { private: CONT elems; // elements public: void push(T const&); // push element void pop(); // pop element T top() const; // return top element bool empty() const { // return whether the stack is empty return elems.empty(); } // assign stack of elements of type T2 template <typename T2, typename CONT2> Stack<T,CONT>& operator= (Stack<T2,CONT2> const&); };
此时的 template assignment 运算符可实作如下:
// basics/stack6assign.hpp
/* The following code example is taken from the book * "C++ Templates - The Complete Guide" * by David Vandevoorde and Nicolai M. Josuttis, Addison-Wesley, 2002 * * (C) Copyright David Vandevoorde and Nicolai M. Josuttis 2002. * Permission to copy, use, modify, sell and distribute this software * is granted provided this copyright notice appears in all copies. * This software is provided "as is" without express or implied * warranty, and with no claim as to its suitability for any purpose. */ template <typename T, typename CONT> template <typename T2, typename CONT2> Stack<T,CONT>& Stack<T,CONT>::operator= (Stack<T2,CONT2> const& op2) { if ((void*)this == (void*)&op2) { // assignment to itself? return *this; } Stack<T2,CONT2> tmp(op2); // create a copy of the assigned stack elems.clear(); // remove existing elements while (!tmp.empty()) { // copy all elements elems.push_front(tmp.top()); tmp.pop(); } return *this; }
记住,对 class templates 而言,只有「实际被调用的成员函数」才会被实例化。因此如果你不至于令不同(元素)类型的 stacks 彼此赋值,那么甚至可以拿 vector 当作内部元素的容器(译注: 而先前的程序代码完全不必改动):
// stack for ints,使用 vector 作为内部容器
Stack<int,std::vector<int> > vStack; ... vStack.push(42 ); vStack.push(7); std::cout << vStack.top() << std::endl;
由于 template assignment 运算符并未被用到,编译器不会产生任何错误讯息抱怨说「内部容器 无法支持 push_front()操作」。以上例子的完整实作全部包含于以 stack6 开头的文件中,位于子目录 basics 之下。
5.4 Template Template Parameters(双 重 模板参 数)
一个 template parameter 本身也可以是个 class template,这一点非常有用。我们将再次以 stack class template 说明这种用法。
为了使用其它类型的元素容器,stack class 使用者必须两次指定元素类型:一次是元素类型本身, 另一次是容器类型:
Stack<int,std::vector<int> > vStack; // int stack,以 vector 为容器,如果使用 template template parameter,就可以只指明元素类型,无须再指定容器类型: Stack<int,std::vector> vStack; // int stack,以 vector 为容器
为了实现这种特性,你必须把第二个 template parameter 声明为 template template parameter。
原 则上程序代码可以写为:
// basics/stack7decl.cpp
template <typename T,template <typename ELEM> class CONT = std::deque > class Stack { private: CONT<T> elems; // 元素 public: void push(T const&); // push 元素 void pop(); // pop 元素 T top() const; // 传回 stack 顶端元素 bool empty() const { // stack 是否为空 return elems.empty(); } };
与先前的 stack 差别在于,第二个 template parameter 被声明为一个 class template:template <typename ELEM> class CONT其默认值则由 std::deque<T> 变更为 std::deque。这个参数必须是个 class template,并以第一参数的类型完成实例化:CONT<T> elems;
本例「以第一个 template parameter 对第二个 template parameter 进行实例化」只是基于例子本身的需要。实际运用 时你可以使用 class template 内的任何类型来实例化一个 template template parameter。和往 常一 样,你也 可以改 用 关键词 class 而不使 用关键 字 typename来声明一个template parameter;但 CONT 定义的是一个 class 类型,因此你必须使用关键词 class 来声明它。所以,
//下面的程序代码是正确的: template <typename T,template <typename ELEM> class CONT = std::deque > //OK class Stack { ... }; //下面的程序代码则是错误的: template <typename T,template <typename ELEM> typename CONT = std::deque > //ERROR class Stack { ... };
由于 template template parameter 中的 template parameter 实际并未用到,因此你可以省略其名称:
template <typename T,template <typename> class CONT = std::deque > class Stack { ... };
所有成员函数也必须按此原则修改:必须指定其第二个template parameter为template template parameter。同样的规则也适用于成员函数的实作部份。例如成员函数 push()应该实作如下:
template <typename T, template <typename> class CONT> void Stack<T,CONT>::push (T const& elem) { elems.push_back(elem); // 追加元素 }
另请注意,function templates 不允许拥有 template template parameters。Template Template Argument 的 匹配(matching)
如果你试图使用上述新版 Stack,编译器会报告一个错误:默认值 std::deque 不符合 template template parameter CONT 的要求。问题出在 template template argument 不但必须是个 template,而且 其参数 必须严格匹配它 所替换 之 template template parameter 的 参数。Template template argument 的默认值不被考虑,因此如果不给出拥有默认值的自变量值时,编译器会认为匹配失败。
本例的问题在于:标准库中的 std::deque template 要求不只一个参数。第二参数是个配置器(allocator),它虽有默认值,但当它被用来匹配CONT 的参数时,其默认值被编译器强行忽略了。办法还是有的。我们可以重写 class 声明语句,使 CONT 参数要求一个「带两个参数」的容器:
template <typename T,template <typename ELEM,typename ALLOC = std::allocator<ELEM> > class CONT = std::deque> class Stack { private: CONT<T> elems; // 元素 ... };
由于 ALLOC 并未在程序代码中用到,因此你也可以把它省略掉。Stack template 的最终版本如下。此一版本支持对「不同元素类型」之 stacks 的彼此赋值动作:
// basics/stack8.hpp
/* The following code example is taken from the book * "C++ Templates - The Complete Guide" * by David Vandevoorde and Nicolai M. Josuttis, Addison-Wesley, 2002 * * (C) Copyright David Vandevoorde and Nicolai M. Josuttis 2002. * Permission to copy, use, modify, sell and distribute this software * is granted provided this copyright notice appears in all copies. * This software is provided "as is" without express or implied * warranty, and with no claim as to its suitability for any purpose. */ #ifndef STACK_HPP #define STACK_HPP #include <deque> #include <stdexcept> #include <memory> template <typename T, template <typename ELEM, typename = std::allocator<ELEM> > class CONT = std::deque> class Stack { private: CONT<T> elems; // elements public: void push(T const&); // push element void pop(); // pop element T top() const; // return top element bool empty() const { // return whether the stack is empty return elems.empty(); } // assign stack of elements of type T2 template<typename T2, template<typename ELEM2, typename = std::allocator<ELEM2> >class CONT2> Stack<T,CONT>& operator= (Stack<T2,CONT2> const&); }; template <typename T, template <typename,typename> class CONT> void Stack<T,CONT>::push (T const& elem) { elems.push_back(elem); // append copy of passed elem } template<typename T, template <typename,typename> class CONT> void Stack<T,CONT>::pop () { if (elems.empty()) { throw std::out_of_range("Stack<>::pop(): empty stack"); } elems.pop_back(); // remove last element } template <typename T, template <typename,typename> class CONT> T Stack<T,CONT>::top () const { if (elems.empty()) { throw std::out_of_range("Stack<>::top(): empty stack"); } return elems.back(); // return copy of last element } template <typename T, template <typename,typename> class CONT> template <typename T2, template <typename,typename> class CONT2> Stack<T,CONT>& Stack<T,CONT>::operator= (Stack<T2,CONT2> const& op2) { if ((void*)this == (void*)&op2) { // assignment to itself? return *this; } Stack<T2,CONT2> tmp(op2); // create a copy of the assigned stack elems.clear(); // remove existing elements while (!tmp.empty()) { // copy all elements elems.push_front(tmp.top()); tmp.pop(); } return *this; } #endif // STACK_HPP
下面程序使用了上述最终版本的 stack:
// basics/stack8test.cpp
/* The following code example is taken from the book * "C++ Templates - The Complete Guide" * by David Vandevoorde and Nicolai M. Josuttis, Addison-Wesley, 2002 * * (C) Copyright David Vandevoorde and Nicolai M. Josuttis 2002. * Permission to copy, use, modify, sell and distribute this software * is granted provided this copyright notice appears in all copies. * This software is provided "as is" without express or implied * warranty, and with no claim as to its suitability for any purpose. */ #include <iostream> #include <string> #include <cstdlib> #include <vector> #include "stack8.hpp" int main() { try { Stack<int> intStack; // stack of ints Stack<float> floatStack; // stack of floats // manipulate int stack intStack.push(42); intStack.push(7); // manipulate float stack floatStack.push(7.7); // assign stacks of different type floatStack = intStack; // print float stack std::cout << floatStack.top() << std::endl; floatStack.pop(); std::cout << floatStack.top() << std::endl; floatStack.pop(); std::cout << floatStack.top() << std::endl; floatStack.pop(); } catch (std::exception const& ex) { std::cerr << "Exception: " << ex.what() << std::endl; } // stack for ints using a vector as an internal container Stack<int,std::vector> vStack; //... vStack.push(42); vStack.push(7); std::cout << vStack.top() << std::endl; vStack.pop(); }
程序运行的输出结果为:
7
42
Exception: Stack<>::top(): empty stack
7
注意,template template parameter 是极晚近才加入的C++ 特性,因此上面这个程序可作为一个极佳工具,用来评估你的编译器对 template 特性的支持程度。
5.5 零值初始化( Zero Initialization)
对于基本类型如 int、double、pointer type(指针类型)来说,并没有一个 default 构造函数将它 们初始化为有意义的值。任何一个未初始化的区域变量(local variable),其值都是未定义的:
void foo() { int x; // x 的值未有定义 int* ptr; // ptr 指向某处(而不是哪儿都不指向) }
你可能在 template 程序代码中声明某个变量,并且想令这个变量被初始化为其默认值;但是当变 数是个内建类型(built-in type)时,你无法确保它被正确初始化:
template <typename T> void foo() { T x; // 如果 T 是内建类型,则 x 值未有定义 }
为解决这个问题,你可以在声明内建类型的变量时,明确调用其 default 构造函数,使其值为零(对bool 类型而言则是 false)。也就是说 int()导致 0 值。这样一来你就可以确保内建类型的变 数有正确初值:
template <typename T> void foo() { T x = T(); // 如果 T 是内建类型,则 x 被初始化为 0 或 false }
Class template 的各个成员,其类型有可能被参数化。为确保初始化这样的成员,你必须定义一个构造函数,在其「成员初值列」(member initialization list)中对每个成员进行初始化:
template <typename T> class MyClass{
private:
T x; public: MyClass() : x() { // 这么做可以确保:即使 T 为内建类型,x 也能被初始化。 } ... };
5.6 以字符串字面常数(String Literals)作为 Function Template Arguments以 by reference 传递方式将「字符串字面常数」(string literals)传递给 function template parameters时,有可能遇上意想不到的错误:
// basics/max5.cpp
/* The following code example is taken from the book * "C++ Templates - The Complete Guide" * by David Vandevoorde and Nicolai M. Josuttis, Addison-Wesley, 2002 * * (C) Copyright David Vandevoorde and Nicolai M. Josuttis 2002. * Permission to copy, use, modify, sell and distribute this software * is granted provided this copyright notice appears in all copies. * This software is provided "as is" without express or implied * warranty, and with no claim as to its suitability for any purpose. */ #include <string> // note: reference parameters template <typename T> inline T const& max (T const& a, T const& b) { return a < b ? b : a; } int main() { std::string s; ::max("apple","peach"); // OK: same type ::max("apple","tomato"); // ERROR: different types ::max("apple",s); // ERROR: different types }
问题出在这几个字符串字面常数(string literals)的长度不同,因而其底层的 array 类型也不同。换句话说 "apple" 和 "peach" 的 array 类型都是 char const[6],而 "tomato" 的 array型别是char const[7]。上述调用只有第一个合法,因为两个参数具有相同类型;然而如果你使用 by value 传递方式,就可以传递不同类型的字符串字面常数(string iterals),其对应的 array大小不同:
// basics/max6.hpp
/* The following code example is taken from the book * "C++ Templates - The Complete Guide" * by David Vandevoorde and Nicolai M. Josuttis, Addison-Wesley, 2002 * * (C) Copyright David Vandevoorde and Nicolai M. Josuttis 2002. * Permission to copy, use, modify, sell and distribute this software * is granted provided this copyright notice appears in all copies. * This software is provided "as is" without express or implied * warranty, and with no claim as to its suitability for any purpose. */ #include <string> // note: nonreference parameters template <typename T> inline T max (T a, T b) { return a < b ? b : a; } int main() { std::string s; ::max("apple","peach"); // OK: same type ::max("apple","tomato"); // OK: decays to same type ::max("apple",s); // ERROR: different types }
这种方式之所以可行,因为在自变量推导过程中,惟有当参数并不是一个 reference 类型时,「array 转为 pointer」的转型动作(常被称为退化, decay)才会发生。这个规则可藉以下例子加以说明:
// basics/refnonref.cpp
/* The following code example is taken from the book * "C++ Templates - The Complete Guide" * by David Vandevoorde and Nicolai M. Josuttis, Addison-Wesley, 2002 * * (C) Copyright David Vandevoorde and Nicolai M. Josuttis 2002. * Permission to copy, use, modify, sell and distribute this software * is granted provided this copyright notice appears in all copies. * This software is provided "as is" without express or implied * warranty, and with no claim as to its suitability for any purpose. */ #include <typeinfo> #include <iostream> template <typename T> void ref (T const& x) { std::cout << "x in ref(T const&): " << typeid(x).name() << ‘\n‘; } template <typename T> void nonref (T x) { std::cout << "x in nonref(T): " << typeid(x).name() << ‘\n‘; } int main() { ref("hello"); nonref("hello"); }
在这个例子中,同一个字符串字面常数(string literal)分别被传递给两个 function templates,其一声明参数为 reference,其二声明参数为 non-reference。两个函数都使用 typeid 运算符打印其具现化后的参数类型。typeid 运算符会传回一个左值(lvalue),其类型为 std::type_info,其内封装了「typeid 运算符所接收之算式(expression)」的类型表述(representation)。std::type_info 的成员函式 name()把这份「类型表述」以易读的字符串形式传回给调用者。C++ Standard 并不要求name() 传回有意义的内容,但是在良好的 C++ 编译器中,它会传回一个字符串内含「typeid运算符的 参数类型」的完好描述。在某些编译器实作品中,这个字符串可能以重排(mangled)形式出现,但也有些反重排工具(demangler)可以把它调整回人类可读的形式。例如上面程序的输出可能如下:
x in ref(T const&): char [6] x in nonref(T): const char *
如果你曾经在程序中把「char array」和「char pointer」混用,你可能被这个令人惊奇的问题搞得头昏脑胀。不幸的是,没有一个普遍适用的办法可以解决这个问题。根据所处情况
的不同,你可以:
以by value 传递方式代替by reference 传递方式。然而这会带来不必要的拷贝。
分别对by value传递方式和by reference传递方式进行重载。然而这会带来模棱两可问题(ambiguities, 歧义性),请参考 B.2.2 节。
以具体类型(例如 std::string)重载之 以 array 类型重载之。例如:
template <typename T, int N, int M> T const* max (T const (&a)[N], T const (&b)[M]) { return a < b ? b : a; }
强迫使用者进行显式转型(explicit conversions)本例之中,最好的方式是对 string 重载(请参考 2.4 节)。这么做有其必要。如果不这么做,对两个字符串字面常数(string literals)调用 max()是合法的,但 max()会以 operator< 比较两个指针的大小,而所比较的其实是指针的地址,不是两个字符串的字面值。这也是为什么使用 std::string 比使用 C-style 字符串更好的原因之一。
5.7 摘要
当你要操作一个取决于(受控于)template parameter 的类型名称时,应该在其前面冠以关键字 typename。嵌套类别(nested classes)和成员函数(member functions)也可以是 templates。应用之一是, 你可以对「不同类型但彼此可隐式转型」的两个 template classes 互相操作,而类型检验(type checking)仍然起作用。
assignment(赋值)运算符的 template 版本并不会取代 default assignment 运算符。
你可以把 class templates 作为 template parameters 使用,称为 template template parameters。
Template template arguments 必须完全匹配其对应参数。预设的 template arguments 会被编译 器忽略,要特别小心。
当你实例化(instantiated)一个隶属内建类型(built-in type)的变量时,如果打算为它设定初值,可明确调用其 default 构造函数。只有当你以 by value 方式使用字符串字面常数(string literals)时,字符串底部的 array 才会被转 型(退化)为一个字符指针(也就是发生 "array-to-pointer" 转换)。