• 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类型的指针。如果不使用typenameT::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.templateto_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, empty
template<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 testreturn *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 sequencereturn (*(end() - 1));}const_reference back() const{   // return last element of nonmutable sequencereturn (*(end() - 1));}

C++基础——关于模板的技巧性基础知识(typename、成员模板、模板的模板参数)相关推荐

  1. 模板引擎Freemarker基础知识

    Freemarker基础知识 Freemarker是什么 FreeMarker 基础指令 List指令 遍历Map数据 if指令 其它指令 运算符 空值处理 内建函数 入门Demo 要导入的依赖 配置 ...

  2. 全面总结C++类模板使用的基础知识

    ✨引言 书接上文,今天来学习C++模板知识中的第二大模块,也就是类模板的使用. <C++提高编程>专栏主要针对C++泛型编程和STL技术做详细讲解,深入研究C++的使用,对C/C++感兴趣 ...

  3. C++模板的一些基础知识

    一.函数模板 可看出就是将函数返回类型和形参类型去掉. 1.代码1 #include <iostream> #include <vector> #include <str ...

  4. 【c++ templates读书笔记】【4】技巧性基础知识

    1.关键字typename 引入关键字typename是为了说明:模板内部的标识符可以是一个类型.当某个依赖与模板参数的名称是一个类型时,就应该使用typename. template<T> ...

  5. 计算机基础与应用相关的论文,计算机基础方面论文范文资料,与国内高校计算机基础教育相关毕业论文模板范文...

    计算机基础方面论文范文资料,与国内高校计算机基础教育相关毕业论文模板范文 关于计算机基础及计算机及大学计算机方面的免费优秀学术论文范文,计算机基础方面毕业设计论文,关于国内高校计算机基础教育相关论文范 ...

  6. 零基础该如何学习Web前端知识?

    想要跳槽到IT行业人在近几年越来越多,大部分都是想要学习web前端技术,但是这其中有很多都是零基础学员,大家都想知道零基础该如何学习Web前端知识?我们来看看下面的详细介绍. 零基础该如何学习Web前 ...

  7. 学习python需要什么基础-自学python需要什么基础,要掌握哪些知识?

    Python语言的应用非常广泛.我们一定要掌握一些基础知识的储备,如果缺少一些基础知识的储备,那么我们在学习Python语言的过程中将会会感觉到非常难,比如,在学习Python语言的过程中我们需要具备 ...

  8. 学python需要什么文化基础-数据分析需要掌握那些基础知识?

    在这个信息对称的时代,数据分析师的工作将为领导决策提供重要的价值,在企业的地位备受重视.因此,这个职业也吸引了越来越多的人,那么想成为一名数据分析师,以下这些基础的知识是必须要掌握的. />北京 ...

  9. Python基础班---第一部分(基础)---Python基础知识---第一个Python程序

    01. 第一个 HelloPython 程序 1.1 Python 源程序的基本概念 Python 源程序就是一个特殊格式的文本文件,可以使用任意文本编辑软件做 Python 的开发 Python 程 ...

最新文章

  1. try~Catch语句中异常的处理过程
  2. ThinkPHP多应用/多模块配置
  3. ArrayList的泛型可以不写吗
  4. 港科大陈凯、杨强教授新书重磅发布,系统揭秘隐私计算 | 文末送书
  5. JSP作用域与特殊对象
  6. pythontcp文件传输_python socket实现文件传输(防粘包)
  7. 利用Erdas监督分类方法提取城镇用地信息完整实验操作步骤
  8. python保存代码_python入门(5)使用文件编辑器编写代码并保存执行
  9. iSCSI 2-环境搭建一
  10. 字母e和i如何发音?
  11. [HTML5实现人工智能]小游戏《井字棋》发布,据说IQ上200才能赢
  12. 02 敏捷开发测试流程
  13. 纯CSS制作-旋转立方体效果
  14. C和C++不安全?Android 支持 Rust 开发操作系统
  15. Foundry 中文文档发布啦
  16. 基于Python的双USB摄像头实时预览保存软件
  17. c语言 一张圆薄饼,切100刀,最多能切成多少块 c语言,甜甜圈上切两刀,最多能切成多少块?...
  18. SDJZU_新生_递推动规_HDU 2569 彼岸
  19. 自我介绍以及未来规划
  20. 模拟对象测试技术Mock(一)

热门文章

  1. scala类型匹配注意事项
  2. 文本编辑器查看 cprintf颜色_做生信,你需要一款好用的文本编辑器
  3. Qt总结二十一:Qt控件一(QWidget)
  4. 0xc000007b——应用程序无法正常启动解决办法
  5. 【操作系统】进程管理(二)
  6. 1、微博RPC框架Motan
  7. 0. 跟踪标记 (Trace Flag) 简介
  8. 《编写可读代码的艺术》---变量和可读性
  9. C语言系列(二):最近重拾C语言的想法,谈到C中易错点,难点;以及开源代码中C语言的一些常用技巧,以及如何利用define、typedef、const等写健壮的C程序...
  10. Android 为View实现双击效果