- 关键词typename的另一种用途
- 将成员函数和嵌套类作为模板的模板模板参数template template paramters
- 零值初始化zero Initialization
- 字符串字面常量string literals作为模板参数
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。
1.1 迭代器应用
typename的一个典型应用是在template程序代码中使用STL容器供应的迭代器iterators:
#include <iostream>
template <typename T>
void printcoll(T const& coll)
{
typename T::const_iterator pos;
typename T::const_iterator end(coll.end());
for(pos = coll.begin(); pos != end; ++pos) {
std::cout << *pos << ' ';
}
std::cout << std::endl;
}
在这个函数模板中, coll是个STL容器,其元素类型为T。这里使用了STL容器的迭代器类型iteration type遍历coll中的所有元素。迭代器类型为const_iterator,每个STL容器都声明有这个类型:
class stlcontainer {
...
typedef ... iterator;
typedef ... const_iterator;
...
};
使用模板类型template Type T的const_iterator时,必须写出全民并在最前面加上关键词typename:
typename T::const_iterator pos;
1.2 .template 结构体(construct)
考虑如下程序,其中使用标准的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的起始,而非一个小于号。这里的参数bs依赖于模板参数template paramter N。
结论是“.template” 或"->template"记号只在templates之内才能被使用,且必须紧跟着与模板参数相关的东西。
2 .使用this指针
如果class template拥有base class,那么其内出现的成员名称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()内解析exit符号时,定义于Base的exit会被编译器忽略。因此,要么获得一个编译器错误,要么是调用外部额外写的exit()函数。
目前避免这样的情况有一个准则:
使用与template相关的符号时,建议总是以this->或Base<T>::进行修饰。
3.成员模板Member Templates
class的成员既可以是templates:既可以是nested class templates,也可以是member function templates。通常只有当两个stack类型相同,也就是当两个stacks拥有相同类型的元素时,你才能对它们相互赋值assign,也就是将某个stack赋值给另一个。不能把某种类型的stack赋值给另一种类型的stack,即使这两种类型之间可以隐式转型:
Stack<int> intStack1, intStack2;
Stack<float> floatStack;
...
intStack1 = intStack2; //OK
floatStack = intStack1; //ERROR: 两个stacks类型不同
然而,可以把assignment运算符定义为一个template,就可以把两个“类型不同,但其元素可以隐式转型的”堆栈互相赋值。为完成此时,Stack<>需要如下面声明:
template <typename T>
class Stack {
private:
std::deque<T> elems;
public:
void push(T const &);
void pop();
T top() const;
bool empty() const {
return elems.empty();
}
template <typename T2>
Stack<T>& operator= (Stack<T2> const&)
}
相比原先的Stack,有如下改动:
1)增加一个assignment运算符,使Stack可被赋予一个拥有不同元素类型T2的Stack.
2)这个Stack使用Deque作为内部容器。
具体新增加的assignment运算符定义实现如下:
template <typename T>
template <typename T2>
Stack<T>& Stack<T>::operator= (Stack<T2> const& op2)
{
if((void*)this == (void*) &op2) {
return *this;
}
Stack<T2> tmp(op2);
elems.clear();
while(!tmp.empty()){
elems.push_front(tmp.top());
tmp.pop();
}
return *this;
}
在拥有template parameter T的模板中定义一个内层的inner模板参数template parameter T2:
template <typename T>
template <typename T2>
...
现在有了这个赋值模板成员member template,就可以把一个int stack赋值给一个float stack:
Stack<int> intStack1, intStack2;
Stack<float> floatStack;
...
floatStack = intStack1;
这看起来绕过了类型检查,其实必要的类型检查还是有的,发生在源端的元素拷贝到目的端时进行:elems.push_front(tmp.top());
这里会将整数类型的元素转换成浮点类型元素。
因此试图将字符堆栈转换成浮点堆栈是不能成功的,因为string不能转换成float类型。
前面的模板赋值运算符并不取代default 赋值运算符。如果你在相同类型的Stack之间赋值,编译器还是会采用default assignment运算符。
这里,我们也可以把内部容器参数化:
template <typename T, typename CONT= std::deque<T> >
Class Stack{
private:
CONT elems;
public:
void push(T const&);
void pop();
T top() const;
bool empty() const {
return elems.empty();
}
template <typename T2, typename CONT2>
Stack<T,CONT>& operator= (Stack<T2,CONT2> const&);
};
此时,template assignment运算符实现如下:
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) {
return *this;
}
Stack<T2, CONT2> tmp(op2);
elems.clear();
while(!tmp.empty()) {
elems.push_front(tmp.top());
tmp.pop();
}
return *this;
}
4. 模板模板参数(双模板参数)Template Template parameters
一个模板参数template parameter本身也可以是个类模板class template,这就是(类)模板(的)模板参数的由来,类模板的模板参数非常有用。
为了使用其他类型的元素容器,stack class使用者必须两次知道元素类型,一次是元素类型本身,另一次是容器类型:
Stack<int, std::vector<int> > vStack;
但是如果使用模板模板参数,就可以仅仅指明元素类型,无须再指定容器类型:
Stack<int, std::vector> vStack;
为了实现这种特性,你必须把第二个模板参数template parameter声明为模板(的)模板参数。
原则上代码可以写为:
template <typename T,
template <typename ELEM> class CONT = std::deque >
Class Stack {
private:
CONT<T> elems;
public:
void push(T const&);
void pop();
T top() const;
bool empty() const {
return elems.empty();
}
};
与先前的差别是,第二个模板参数被声明为一个类模板class template,上面黑体部分,
template <typename ELEM> class CONT
其默认值也由原先的std::deque<T> 变更为std::deque. 这个参数必须是个class template,并以第一参数的类型完成实例化:
CONT<T> elems;
但是这样编译还是有问题,这就是:
模板模板参数的匹配问题。
本例的问题在于:标准库中的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;
...
};
最后的Stack template实现如下。
1)template2stack.hpp
#ifndef TEMPLATE2STACK_H
#define TEMPLATE2STACK_H
#include <deque>
#include <stdexcept>
#include <memory>
template <typename T,
template <typename ELEM,
typename = std::allocator<ELEM> >
class CONT = std::deque>
class Template2Stack{
private:
CONT<T> elements;
public:
void push(T const&);
void pop();
T top() const;
bool empty() const {
return elements.empty();
}
template<typename T2,
template <typename ELEM2,
typename = std::allocator<ELEM2> >
class CONT2>
Template2Stack<T, CONT>& operator= (Template2Stack<T2, CONT2> const&);
};
template <typename T, template <typename, typename> class CONT>
void Template2Stack<T,CONT>::push(T const& elem)
{
elements.push_back(elem);
}
template <typename T, template <typename, typename> class CONT>
void Template2Stack<T,CONT>::pop()
{
if(elements.empty()){
throw std::out_of_range("template2Stack<>::pop(): empty stack");
}
elements.pop_back();
}
template <typename T, template <typename, typename> class CONT>
T Template2Stack<T,CONT>::top() const
{
if(elements.empty()){
throw std::out_of_range("template2Stack<>::top(): empty stack");
}
elements.back();
}
template <typename T, template <typename, typename> class CONT>
template <typename T2, template <typename, typename> class CONT2>
Template2Stack<T,CONT>&
Template2Stack<T,CONT>::operator= (Template2Stack<T2,CONT2> const& op2){
if((void*)this == (void*) &op2){
return *this;
}
Template2Stack<T2, CONT2> tmp(op2);
elements.clear();
while (!tmp.empty()) {
elements.push_front(tmp.top());
tmp.pop();
}
return *this;
}
#endif // TEMPLATE2STACK_H
2) stacktest.cpp
#include <QCoreApplication>
#include <iostream>
#include <string>
#include <cstdlib>
#include <vector>
#include <template2stack.h>
int main(int argc, char *argv[])
{
QCoreApplication a(argc, argv);
try {
Template2Stack<int,std::deque> intStack;
Template2Stack<float,std::deque> floatStack;
intStack.push(42);
intStack.push(7);
floatStack.push(7.7);
floatStack.push(42.42);
floatStack = intStack;
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;
}
return a.exec();
}
5. 零值初始化
对于基本类型如int,double, pointer,type来说,并没有一个default构造函数将它们初始化为有意义的值,任何一个未初始化的区域变量(local variable),其值都是未定义的:
void foo()
{
int x; //x未定义
int* ptr; //ptr未定义
}
解决方法:
template <typename T>
void foo()
{
T x= T();
}
6. 字符串常数作为模板参数
以引用方式by reference传递给函数模板参数,可能会有意想不到的错误
类型不匹配,编译不过。
7. 小结
当要操作一个取决于模板参数的类型名称时,应该在其前面冠以关键词typename .嵌套类和成员函数也可以是templates.
assignment运算符的template版本并不会取代default assignment运算符。
可以把class templates作为template parameters使用,称为template template parameters.
template template arguments 必须完全匹配其对应参数。预设的模板参数会被编译,忽略,特别要小心。