16.1定义模板

重载多个相似的函数是麻烦的:
比如重载能接受多个类型的compare。

使用函数模板之后可以定义成这样:

template <typename T>
int compare(const T &v1, const T &v2)
{
    if(v1 < v2) return -1;
    if(v2 < v1) return 1;
    return 0;
}

模板定义以关键字template开始,后跟一个模板参数列表,这是一个逗号分隔的一个或者多个模板参数
注:模板参数列表不能为空

使用模板时,我们显示或隐式的指定模板实参


16.1.1函数模板

实例化函数模板

当我们调用一个函数模板时,编译器一般用函数实参来推断模板实参。
编译器用推断出的模板参数来为我们实例化一个特定版本的函数。


模板类型参数

在模板参数列表中,typename和class没有什么不同。
template<typename T, class U> calc (const T&, const U&)


非类型模板参数

除了定义类型参数,还可以在模板中定义非类型参数。
一个非类型参数表示一个值,而不是一个类型,我们使用特定的类型名,而不是关键字typename或者class.
可以用来指定数组大小。

template<unsigned N, unsigned M>
int compare(const char (&p)[N], const char (&p2)[N])
{
    return strcmp(p1, p2);
}

当我们调用compare(“hi”,”momom”)时,编译器实例化出:
int compare(const char (&p1)[3], const char (&p2)[4])
绑定到非类型参数的实参必须是一个常量表达式。

函数模板可以声明为 inline 和 constexpr

模板编译:
因为我们调用一个类函数时,编译器只需要掌握函数的声明,所以一般将类定义和函数声明放到头文件中,而普通函数和类的成员函数的定义放在源文件中。
模板的区别:为了生成一个实例化版本,编译器需要掌握函数模板或类模板成员函数的定义。因此,模板的头文件通常包括声明和定义。


16.1.2类模板

使用类模板,需要提供显式的模板实参。
car
car_1;

类模板的成员函数定义在类模板之外时,必须以template开始。

类模板的成员函数只有程序用到,才会进行实例化。

在类内简化模板类名的使用:类模板的作用域中,我们可以直接使用模板名,而不提供实参。
比如:BlobPtr& operator++

如果类包含友元的声明:

  1. 如果是非模板友元,友元被授权访问所有的模板实例。
  2. 如果是模板友元,可以只授权给特定实例。
template <typename T> class C2 {
    friend class Pal<T>; //C2的每个实例将相同实例化的Pal声明为友元。
    template <typename X> friend class Pal2; //Pal2的所有实例都是C2的每个实例的友元
    friend class Pal3; //非模板类,是C2所有实例的友元。
};

static静态数据成员
直接定义static数据成员,则所有模板实例共享相同的静态变量。
也可以让实例有独有的static对象。
size_t Foo
::ctr = 0;

当然访问的时候也要指定,访问的是那个静态变量。


16.1.3模板参数

如果希望通知编译器一个名字表示类型时,要使用关键字 typename
我们可以给模板提供默认模板实参

template
class num

num<> A; // 使用默认实参也要有尖括号

类似函数默认实参,只有右侧所有参数都有默认实参是,它才能有默认实参。


16.1.4成员模板

一个普通的类可以包含本身是模板的成员函数,一样要在声明前面加 template…
对于类模板,成员和类也可以由自己,独立的模板。
在类模板外定义一个成员模板时,需要同时提供两个模板参数列表。

template <typename T> class Blob{
    template <typename It> Blob(It b, It e);
}
template <typename T>
template <typename It>
    Blob<T>::Blob(It b,It e)

16.1.5控制实例化

因为,相同的实例可以出现在多个对象文件中,进而存在在多个独立的编译生成文件中。这会带来很多额外的开销。
所以我们可以通过显示实例化来避免这种开销。

extern template class Blob<string>;
template int compare(const int&, const int&);

extern表明程序其他位置存在该实例化的非extern声明。
当编译器遇到extern声明时,它不会在本文件中生成实例化代码

16.2模板实参推断

从函数实参来确定模板实参的过程叫做模板实参推断

类型转换与模板类型参数
只有有限的几种自动转换可以用于模板参数:

  1. const转换
  2. 数组或函数指针转换

尾置返回值类型
尾置返回类型跟在形参列表后面并以一个 “->” 符号开头。
为了表示函数真正的返回类型跟在形参列表之后,我们在本应该出现返回类型的位置放一个 auto:

auto getArrayType(int i) -> int(*)[10];

vector<int> vi = {1,2,3,4,5}
auto &i = fcn(vi.begin(), vi.end());
template <typename T>
auto fcn(T beg, T end) -> decltype(*beg)
{
    return *beg;// 返回序列中一个元素的引用
}

当参数是一个函数模板实例的地址时,程序上下文必须满足:对每个模板参数,能唯一确定其类型或值。


引用折叠:

  1. 所有的右值引用叠加到右值引用上还是右值引用
  2. 所有的其他引用类型之间的折叠都将变为左值引用

右值引用作为参数

template <typename T> void f3(T &&val)
{
    T t = val;// 拷贝(右值) or 绑定引用(左值)
    t = fcn(t); //赋值只改变t,还是既改变T也改变val
    if(val == t){ } // 若T是左值引用类型,则一直为true
}

template <typename T> void f(T&&) //绑定到非const右值
template <typename T> void f(const T&) //绑定到const右值和左值

std::move

template <typename T>
typename remove_reference<T>::type&& move(T&& t)
{
    return static_cast<typename remove_reference<T>::type&&>(t) // 显示类型转换
}

std::move的工作流程见书P611
remove_reference移除引用
可以使用static_cast进行显示类型转换,把左值转换为右值,不过还是使用move最好。


转发
某些函数需要将一个或者多个实参连同类型不变的转发给其他函数。(包括const 和 左右值特性)

template <typename F, typename T1,typename T2>
void flip1(F f, T1 t1, T2 t2) //顶层const和引用丢失了
{
    f(t2,t1);
}
void f(int v1,int &v2) //注意v2是一个引用
{
    cout << v1 " " << ++v2 << endl;
}
f(42,i);// f改变了实参i
flip1(f,j,42) //不会改变j
//所以改成
template <typename F, typename T1,typename T2>
void flip2(F f, T1 &&t1, T2 &&t2) //可以传递左值引用,但不能接受f的参数有右值引用
{
    f(t2,t1);
}
f(42,i);// f改变了实参i
flip2(f,j,42) //会改变j

//使用了forward
template <typename F, typename T1,typename T2>
void flip(F f, T1 &&t1, T2 &&t2) //可以传递左值引用,但不能接受f的参数有右值引用
{
    f(std::forward<T2>(t2), std::forward<T1>(t1));
}

forward可以保持实参类型的所有细节

16.3重载和模板

涉及函数模板的,函数匹配规则:

  • 对于一个调用,其候选函数包括所有模板实参推断成功的函数模板实例
  • 注意可以进行类型转换,但函数模板可以进行的类型转换很少
  • 如果一个函数可以提供比其他函数更好的匹配则选择它,如果有多个同样的匹配:
    1. 如果只有一个非模板函数,则选择它
    2. 如果没有非模板函数,则选择更特例化的模板
    3. 否则该调用有歧义

注意要名字查找规则,声明不在前面就找不到,可能匹配到不需要的版本。

16.4可变参数模板

可变数目的参数被称为参数包,包括函数参数包和模板参数包。

template <typename T, typename... Args> //...用做扩展
void foo(const T &t,const Args& ... rest);

上面的代码表明了:foo是一个可变参数函数模板,它有一个名为T的类型参数,和一个Args的模板参数包。
foo的参数列表表示:包含一个const& 类型的参数,还有一个名为rest的函数参数包。
int i = 0; double d = 4.2; string s = “how”
foo(i,s,42,d);
生成:void foo(const int&,const string&,const int&,const double&)

可变参数函数通常是递归的。
sizeof…(Args) 可以返回包中含有多少元素。

包扩展

template <typename... Args>
ostream &errorMsg(ostream &os, const Args&... rest)
{
    return print(os, debug_rep(rest)...);
}

扩展中的模式,会独立地应用在包中的每一个元素,上面的代码是对每个rest调用debug_rep,然后返回结果并作为函数参数。

可以结合forward保持原来参数的性质,和包扩展实现包转发。
return print(os, std::forward
(rest)…);

16.5模板特例化

函数特例化

template <typename T> int compare(const T&, const T&); //通用模板
template<> //特例化模板
int compare(const char* const &p1, const char* const &p2)// T为const char* , 同时指定是常量指针而不是指向常量的指针
{
    return strcmp(p1,p2);
}

特例化一个函数模板时,必须为原模版的每个模板参数提供实参。
在特例化中,我们提前实例化了模板,而非重载,所以特例化不影响函数匹配。
模板及其特例化版本应该声明在同一个头文件中。

类模板的特例化
类模板也可以特例化:

namespace std{
    template <>
    struct hash<Sales_data>//对标准库中hash模板进行特例化
    {
        typedef size_t result_type;
        ...
    };
//如果使用了Sales_data的私有成员,要把std::hash声明为Sales_data的友元。    
}

类模板的部分特例化
类模板可以进行部分特例化:
部分实例化版本的模板参数列表可以是原始模板参数列表的一个子集或者一个特例化版本。

template <class T> struct remove_reference
{
    typedef T type;
}
template <class T> struct remove_reference<T&>
{
    typedef T type;
}
template <class T> struct remove_reference<T&&>
{
    typedef T type;
}

模板名后的<T&>是给特例化的模板参数指定实参,这些实参要和原始模板中的参数按位置对应。

总结:

image

原文地址:http://www.cnblogs.com/hy227/p/16878771.html

1. 本站所有资源来源于用户上传和网络,如有侵权请邮件联系站长! 2. 分享目的仅供大家学习和交流,请务用于商业用途! 3. 如果你也有好源码或者教程,可以到用户中心发布,分享有积分奖励和额外收入! 4. 本站提供的源码、模板、插件等等其他资源,都不包含技术服务请大家谅解! 5. 如有链接无法下载、失效或广告,请联系管理员处理! 6. 本站资源售价只是赞助,收取费用仅维持本站的日常运营所需! 7. 如遇到加密压缩包,默认解压密码为"gltf",如遇到无法解压的请联系管理员! 8. 因为资源和程序源码均为可复制品,所以不支持任何理由的退款兑现,请斟酌后支付下载 声明:如果标题没有注明"已测试"或者"测试可用"等字样的资源源码均未经过站长测试.特别注意没有标注的源码不保证任何可用性