C++基础——关于模板的技巧性基础知识(typename、成员模板、模板的模板参数)
来源:互联网 发布:mac如何设置开机密码 编辑:程序博客网 时间:2024/06/07 01:36
- typename
- template
- 成员模板
- 模板的模板
- 模板的模板 的实参匹配
本文继续深入探讨模板的基础知识,covers 内容如下:
- 关键字
typename
的另一种用法 - 将成员函数和嵌套类也定义成模板
- 模板的模板参数(template template parameters)
typename
在C++标准化的过程中,引入关键字typename
是为了说明:模板类型参数内部的标识符(associated type,常见于STL中的各种容器)也可以是一个类型:
template<typename T>class MyClass{ typename T::SubType* ptr;}
上述程序中,第二个typename被用来说明,SubType是定义与类T
内部的一种类型,也就是associated type
,因而,ptr是一个指向T::SubType
类型的指针。如果不使用typename
,T::SubType
会被优先看做T的一个静态成员,也就是一个具体而变量或对象,于是,下面的表达式:
T::SubType * ptr;
会被看做类T的静态成员SubType和ptr的乘积。
通常情况下,当要以类型的形式使用类型模板参数的associated types(即定义于其内部的类型)时,就必须使用typename关键字。
下面我们来考虑一个typename的典型应用场景,即在模板代码中访问STL容器的迭代器(const_iterator,作为每一个容器类的associated type)。
template<typename COLL>void printColl(ostream& os, const COLL& coll){ typename COLL::const_iterator pos; typename COLL::const_iterator end(coll.end()); for(pos = coll.begin(); pos != end; ++pos) os << *pos << " "; os << endl;}
.template
我们以一个小例子说明这一语法特性:
虽然bitset<>模板类提供了简单的与string交互(转换)的接口,如bitset<>的string参数类型的构造函数,也有十分方便的to_string
的成员函数,但两者都是成员模板,就不能像一般的成员函数那样使用。关于这部分内容更详尽的内容请见
template<size_t N>string to_string(const bitset<N>& b){ return b.template to_string<char, char_traits<char>, allocator<char> >();}
这个函数的目的是对b.to_string
这一成员函数模板赋值的封装,
string s = b.template to_string<char, char_traits<char>, allocator<char> >();// 借助于上面的辅助函数string s = to_string<6>(b);
在老式的c++编译器中,这里的.template不能省略,如果没有这个.template,编译器无法确定,其后的<
是小于号,还是模板参数列表的起始符号。
成员模板
类的成员也可以是模板。嵌套类和成员函数都可以作为模板。
我们在之前的讨论
template<typename T>class Stack{public: void push(const T& x) { elems.push_back(x);} // 尾端入 void pop() { elems.pop_back();} // 尾端出,实现一种FILO的机制 T& top() { return elems.back();} const T& top() const { return elems.back();} bool empty() const { return elems.empty();} private: std::deque<T> elems;}
通常而言,栈之间只有在元素类型完全相同时才能相互赋值,换句话说,类型不同的栈,无法进行赋值,即使这两种(元素的)类型之间存在隐式类型抓换,如:
Stack<int> is1, is2;Stack<double> ds3;//...is1 = is2; // OK: 具有相同类型的栈 is3 = is1; // ERROR:两边栈的类型不同
类中国缺省赋值运算符要求=
两边具有相同的元素类型。通过定义一个模板形式的赋值运算符,元素类型不同的两个栈就可实现相互赋值。
template<typename T>class Stack{public: // ... template<typename T2> Stack<T>& operator=(const Stack<T2>& rhs); // 返回值不含const 属性,保证了`s1 = s2 = s3`}
新的赋值运算符的实现大致如下:
template<typename T> template<typename T2>Stack<T>& Stack<T>::operator=(const Stack<T2>& rhs){ if ((void*)this == (void*)&rhs) return *this; // identity test,同一性测试 Stack<T2> tmp(rhs); elems.clear(); while(!tmp.empty()) { elems.push_back(tmp.top()); tmp.pop(); } return *this;}
新的类型检查发生在:
elems.push_back(tmp.top());
同样地,在实现中,也可以把内部容器类型实现为一个模板参数:
// CONT类型的容器,必须支持Stack<, >类模板体内的操作或者接口// 如push_back, pop_back, back, emptytemplate<typename T, typename CONT = std::deque<T> >class Stack{public: void push(const T& x) { elems.push_back(x);} void pop() { elems.pop_back();} T& top() { return elems.back();} const T& top() const { return elems.back();} bool empty() const { return elems.empty();} template<typename T2, typename CONT2> Stack<T, CONT>& operator=(const Stack<T2, CONT2>& rhs);private: CONT elems;}
template<typename T, typename CONT> template<typename T2, typename CONT2>Stack<T, CONT>& Stack<T, CONT>::operator=(const Stack<T2, CONT2>& rhs){ if((void*)this == (vois*)&rhs) return *this; Stack<T2, CONT2> tmp(rhs); elems.clear(); while (!tmp.empty()) { elems.push_back(tmp.top()); tmp.pop(); } return *this;}
模板的模板
这是一种口语化的说法,准确地说,以模板作为另一个模板的模板参数,是不是更绕,:-D。我们继续以Stack模板类举例,探讨模板的模板存在的必要性,再往大点说是C++模板技术演化的线索。
// 这时的第二个模板参数是一种模板的实例化// 必须这样传递,Stack<int, deque<int> > // Stack<int, queue<int> >template<typename T, typename CONT = std::deque<T> >class Stack{...};Stack<int, std::vector<int> > intStack;
我们看到上述代码是存在冗余的(忽然想到一句话,模板类是不是可以称得上一种具有参数的类呢,正如函数一样。),即第一个模板参数是第二个模板参数的参数,且有可能发生参数的传递错误,如Stack<int, deque<double> >
。我们猜想是否存在如下的没有冗余的声明式:
Stack<int, std::vector> intStack;
为了支持这样的语法特性:
template<typename T, template<typename ELEM> class CONT = std::deque >class Stack{public: void push(const T& x); void pop(); T& top(); const T& top() const; bool empty() const { return elems.empty();}private: CONT<T> elems;}
这与前述代码的不同之处在于,第二个参数从一个类模板实例化变成了一个类模板,,缺省值自然也从std::deque<T>
变成了std::deque
,在使用时,第二个参数必须是一个类模板,并且由第一个模板参数传递进来的类型进行实例化(这个例子比较特殊,一般地可以在类模板内部以任意允许的类型实例化模板的模板参数):
CONT<T> elems;
之前的讲述模板基础知识的时候,我们曾说过,作为模板参数的声明,通常可以使用typename替换关键字class,然而,上述的CONT是欲定义的一个模板类,必须用关键字class修饰。
template<typename T, template<typename ELEM> class CONT = std::deque>class Stack { ... }; // 正确template<typename T, template<typename ELEM> typename CONT = std::deque>class Stack { ... } // 错误
就像我们可以在.hpp
文件的类成员函数或者全局函数的声明中省略形参名,保留形参类型一样,我们也可以对这里的作为模板参数的模板的参数,也即ELEM
,因为不涉及的函数体,或者即是涉及函数体,在函数体内也用不到,可见在真实的内存模型中,操作的内存单元,而不是形参名(大概保存在一个叫符号表的结构中,这部分内容可参阅编译原理或者C++内存模型相关细节)。
template<typename T, template<typename> class CONT = std::deque>class Stack{ ... }
另外,还必须对成员函数的成员函数的声明进行相应的修改,将第二个模板参数指定为模板的模板参数:
template<typename T, template<typename> class CONT>void Stack<T, CONT>::push(const T& x){ elems.push_back(x);}
模板的模板 的实参匹配
这时如果不加修改的使用新版本的Stack
,甚至不调用客户端代码,不进行实例化,在编译阶段就将得到一个错误信息,缺省值std::deque和模板的模板参数CONT并不匹配
。问题在于,模板的模板实参(比如本例中的std::deque
)也是一个具有参数(以A为例)的模板(std::deque
的模板参数如下所示,也是一个具有两个模板参数的模板):
template<class _Ty, class _Alloc = allocator<_Ty> > class deque : public
它将替换这里模板的模板参数(也即是这里的CONT),而模板的模板参数是一个具有参数B的模板(也即是本例的template)匹配过程要求参数A和参数B必须完全匹配,而这里,如前所示,并不能完全匹配,一个是两个模板参数的类模板,而一个只有一个模板参数 。这时我们可以重写类的声明,以使参数个数相匹配。
template<typename T, template<typename ELEM, template ALLOC = std::allocator<ELEM> class CONT = std::deque>class Stack{private: CONT<T> elems; // CONT<T, allocator<T> > // deque<T, allocator<T> >}
同样这时,ALLOC也可省略不写,因为在实现中并不会用到。现在我们便可以写出完整版的Stack的声明与定义了:
template<typename T, template<typename ELEM, typename = std::alloctor<ELEM> > class CONT>class Stack{public: void push(const T&); void pop(); T& top(); const T&() const; bool empty() const; // 成员模板 template<typename T2, template<typename ELEM2, typname ALLOC=std::allocator<ELEM2> > class CONT2> Stack<T, CONT>& operator=(const Stack<T2, CONT2>&);private: CONT<T> elems; // 这里完成实例化}template<typename T, template<typename, typename> class CONT>void Stack<T, CONT>::push(const T& x){ elems.push_back(x);}///template<typename T, template<typename, typename> class CONT> template<typename T2, template<typename, typename> class CONT2>Stack<T, CONT>& Stack<T, CONT>::operator=(const Stack<T2, CONT2>& rhs){ if ((void*)this == (void*)&rhs) // identity test return *this; Stack<T2, CONT2> tmp(rhs); elems.clear(); while(!tmp.empty()) { push(tmp.top()); tmp.pop(); } return *this;}
关于两个版本的
top()
成员函数,一个是const
的返回常量引用
的,一个是没有这些常量修饰的,这本质上上是因为Stack
模板类内部的CONT
容器的back()
函数(也即STL对所有容器类的一种要求或者说是规范,返回两个版本的返回容器内部元素,一个mutable sequence,另一个是nonmutable sequence
)
// deque,内部可变和不可变两个版本的back()成员函数typedef typename _Mybase::reference reference;typedef typename _Mybase::const_reference const_reference;reference back() { // return last element of mutable sequence return (*(end() - 1)); }const_reference back() const { // return last element of nonmutable sequence return (*(end() - 1)); }
- C++基础——关于模板的技巧性基础知识(typename、成员模板、模板的模板参数)
- 模板技巧性基础知识
- 模板的模板参数
- 模板笔记004 - 技巧性基础知识
- 关于模板中typename的用法
- 模板中typename的作用
- 模板类 的 typename 关键字
- 关于模板匹配的基础知识
- 从零开始学C++之模板(三):缺省模板参数(借助标准模板容器实现Stack模板)、成员模板、关键字typename
- 从零开始学C++之模板(三):缺省模板参数(借助标准模板容器实现Stack模板)、成员模板、关键字typename
- 从零开始学C++之模板(三):缺省模板参数(借助标准模板容器实现Stack模板)、成员模板、关键字typename
- 成员模板、模板的偏特化和特化、模板模板参数
- 模板类型的模板参数
- 成员模板和参数模板
- 类模板2——静态成员以及非类型的类模板参数
- 关于模板的类型参数
- c++中关于模板的typename和class的区别
- 成员的类模板
- Eclipse SVN的使用
- openssl 证书操作命令
- 进销存系统(1):开源ECP编译安装
- Ubantu下创建eclipse快捷图标
- 使用RequireJS来加载MooTools Classes
- C++基础——关于模板的技巧性基础知识(typename、成员模板、模板的模板参数)
- SHA1WithRSA算法-简介
- 奇异值分解SVD学习笔记
- View 的setVisibility有三个值:VISIBLE、INVISIBLE和GONE的区别
- 创建qcow2文件
- 字符串---转换字符串格式为字符+连续出现的次数
- 一个关于二维指针的问题
- 【VR】Leap Motion 官网文档(二) Unity插件概述
- asm disk header 的自动备份和恢复