C++笔记-模板与重载

  1. 函数模板可以被另一个模板或者一个普通非模板函数重载。
在对重载的模板进行调用时,遵循以下顺序:
    如果同样符合要求的函数中只有一个是非模板函数,则选择非模板函数
    如果同样符合的函数中没有非模板函数,在多个函数模板中,某一个比其他模板更特例化,则选择此模板
    否则,此调用有歧义

关于特例化,例如:
const string *sp = &s;
cout << debug_rep(sp) << endl;
//其中debug_rep有两个模板
//debug_rep(const string*&)
//debug_rep(cosnt string*)
此时,正常的函数匹配规则无法区分这两个函数,可能会认为有歧义
但是,重载函数模板的特例化规则,调用被解析为debug_rep(T*),即更特例化的版本
#原因#
    debug_rep(const T&)本质上可以用于任何类型,包含指针类型,因此比debug_rep(T*)更通用。
  1. 在定义任何函数之前,记得声明所有重载的函数版本。这样就不必担心编译器由于未遇到你所希望调用的函数而实例化了一个非你所需的函数。
  2. 可变参数模板——一个接收可变数目参数的模板函数或模板类。
将可变数目的参数称为参数包。
模板参数包:表示零个或多个模板参数
函数参数包:表示零个或多个函数参数
我们用一个省略号来指出一个模板参数或函数参数表示一个包。
在一个模板参数列表中,class...或typename...指出接下来的参数表示零个或多个类型的列表
在函数参数列表中,如果一个参数的类型是一个模板参数包,则此参数也是一个函数参数包
//Args是一个模板参数包;rest是一个函数参数包
//Args表示零个或多个模板类型参数
//rest表示零个或多个函数参数
template <typename T, typename... Args>
void foo(const T &t, const Args& ... rest);
这里声明了foo是一个可变参数函数模板,它有一个名为T的类型参数,和一个名为Args的模板参数包,这个包表示零个或多个额外的类型参数
foo的函数参数列表则包含一个const &类型的参数,指向T类型,还包含一个名为rest的函数参数包,此包包含零个或多个函数参数

例如针对以下调用,编译器会推断包中参数的数目。
int i = 0; double d = 3.14; string s = "how are you";
foo(i, d, s, 42);   //包中含有三个参数
foo(s, 42, "hi");   //包中含有两个参数
foo(d, s);          //包中含有一个参数
foo("hi");          //零个参数的空包
编译器分别实例出:
void foo(const int&, const double&, const string&, const int&);
void foo(const string&, const int &, const char[3]&);
void foo(const double&, const string&);
void foo(const char[3]&);

我们可以通过sizeof... 运算符来返回包中含有的参数的数目
template <typename ... Args> void g(Args ... args)
{
    cout << sizeof...(Args) << endl;
    cout << sizeof...(args) << endl;
}
  1. 编写可变参数模板
//打印函数包中的元素
template <typename T>
ostream &print(ostream &os, const T &t)
{
    return os << t; //对于递归而言,用于结束的函数
}
template <typename T, typename... Args>
ostream &print(ostream &os, const Args... rest)
{
    os << t << ",";
    return print(os, rest...)
}

对于包扩展而言,我们在调用时,传入的时包名
template <typename... Args>
ostream &print(ostream &os, const Args&... rest)
{
    //print(os, debug_ret(a1), debug_ret(a2)...debug_ret(an));
    return print(os, debug_ret(rest)...);
}
debug_ret(rest)...表示对rest的每个元素调用debug_ret
debug_ret(rest...)表示对debug_ret(a1, a2, ..., an)
  1. 转发参数包,即向函数传递参数本来的形式
在保持参数包的原型时,需要做到两步:
//定义时,函数参数需要定义为模板类型参数的右值引用
class StrVec
{
public:
    template <class... Args> void emplace_back(Args&&...);
};
//然后,当emplace_back将这些实参传递给construct时,我们必须使用forward来保持实参的原始类型
template <class... Args>
inline void StrVec::emplace_back(Args&&... args)
{
    chk_n_alloc();
    alloc.construct(first_free++, std::forward<Args>(args)...);
}
//为了解决一些频繁调用的小函数大量消耗栈空间(栈内存)的问题,特别的引入了inline修饰符,表示为内联函数。
  1. 模板特例化
之前定义的compare是一个很好的例子,它展示了函数模板的通用定义不适合一个特定类型(即字符指针)的情况。
我们希望通过compare调用strcmp来比较两个字符指针,而并非比较指针值。
//第一个版本:通用版本,可以比较任意两个类型
template <typename T>
int compare(const T&, const T&);
//第二个版本:处理字符串常量
template <size_t N, size_t M>
int compare(const char(&)[N], const char(&)[M]);
//第三个版本:处理字符数组指针
template <>
int compare(const char *const &p1, const char *const &p2)
{
    return strcmp(p1, p2);
}
在理解该特例化之前,我们必须知道的是,特例化版本的函数参数类型,必须与先前声明的模板中对应的类型匹配。
即先前声明为:
template <typename T> int compare(const T&, const T&);
可以看到参数为一个const类型的引用。但我们特例化版本中,所希望的参数为一个字符串指针const char*
因此,我们需要一个指向此类型的const版本的引用,即类型const char* const &,
一个指向const char的const指针的引用
#Note#
    特例化的本质是实例化一个模板,而非重载它,因此不会影响函数匹配。
  1. 类模板的部分特例化
例如:我们只特例化特定的成员函数而不是整个模板
template <typename T>
struct Foo
{
    Foo(const T &t = T()) : mem(t) { }
    void Bar() { ... }
    T mem;
};
//接下来进行Bar成员函数的特例化
template <>
void Foo<int>::Bar()
{
    //进行应用于int的特例化处理
}

当调用Foo::Bar时
Foo<string> fs; //实例化Foo<string>::Foo()
fs.Bar();       //实例化Foo<string>::Bar()
Foo<int> fi;    //实例化Foo<int>::Foo()
fi.Bar();       //使用我们特例化版本的Foo<int>::Bar()
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值