4.Template Function

模板函数

一个通用的比较大小的函数:

调用这一函数:

  1. 模板的显式实例化

  1. 隐式实例化

概念提升

在写模板函数时往往会对模板参数做出一定的假设,这些假设限制了所编写的模板函数的通用性。

尝试解除这些假设的限制,使得编写的模板的函数更通用。例如,对于countOccurences函数而言,能否解决以下问题:

以下展示逐渐放松countOccurences函数参数限制的过程:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
// How many times does the integer [val] appear in an entire vector of integers?
int countOccurences(const vector<int>& vec, int val) {
int count = 0;
for (size_t i = 0; i < vec.size(); ++i) {
if (vec[] == val) ++count;
}
return count;
}

// How many times does the [type] [val] appear in an entire vector of [type]?
template <typename DataType>
int countOccurences(const vector<DataType>& vec,
DataType val) {
int count = 0;
for (size_t i = 0; i < vec.size(); ++i) {
if (vec[] == val) ++count;
}
return count;
}

// How many times does the [type] [val] appear in an entire [collection] of [type]?
template <typename Collection, typename DataType>
int countOccurences(const Collection& list,
DataType val) {
int count = 0;
for (auto iter = list.begin(); iter != list.end(); ++iter) {
if (*iter == val) ++count;
}
return count;
}

// How many times does the [type] [val] appear in [a range of elements]?
template <typename InputIt, typename DataType>
int countOccurences(InputIt begin, InputIt end,
DataType val) {
int count = 0;
for (auto iter = begin; iter != end; ++iter) {
if (*iter == val) ++count;
}
return count;
}

但最后版本的countOccurences仍未能解决最后一个问题:统计向量后半部分中最多有多少元素小于或等于5。在后面,我们会使用谓词函数来解决这一问题。

隐式接口

编译器会将每个模板参数替换为实例化它的任何参数。

例如,对最后一个版本的countOccurences函数而言:

按照上述实参,将模板参数替换后会出现语法错误:

实际上,模板函数定义了每个模板形参必须满足的隐式接口。如果实例化类型不支持这些,则会出现严重的编译错误。

例如,上述countOccurences函数就有以下隐式接口:

  • InputIt必须支持:
    • copy assignment (iter = begin)
    • prefix operator (++iter)
    • comparable to end (begin != end)
    • dereference operator (*iter)
  • DataType必须支持:
    • comparable to *iter

overload resolution

模板函数重载解析

当存在多个模板函数候选时,C++通过overload resolution来确定使用哪个模板。这个过程主要包括三个步骤:

其中,在第二步substitution的过程中:

  • 编译器尝试用实参类型替换模板函数的模板参数。如果实参类型不满足模板要求的条件(即不符合隐式接口),该模板实例不被生成。
  • SFINAE(Substitution Failure Is Not An Error):如果替换失败(实参类型不符合隐式接口),这不会导致编译错误。相反,失败的模板重载会被排除出候选函数集。
  • 作用:通过SFINAE,可以设计只在满足特定条件时参与重载解析的模板函数,增加类型的条件选择灵活性。

后置返回类型

C++11特性,允许将返回类型放在函数参数列表后,通过->指定。特别适用于模板函数和需要参数类型推断的复杂函数。

示例

  • 函数模板
1
2
3
4
template <typename T>
auto add(T a, T b) -> decltype(a + b) {
return a + b;
}
  • Lambda表达式
1
2
3
auto lambda = [](int x, int y) -> int {
return x + y;
};

利用SFINAE

使用后置返回类型可以利用SFINAE的特性,例如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
/* 1.如果T有size()成员函数,则替换成功。 */
template <typename T>
auto printSize(const T &a) -> decltype(a.size())
{
cout << “printing with size member function : ”;
cout << a.size() << endl;
return a.size();
}

/* 2.如果T能被取反,则替换成功。 */
template <typename T>
auto printSize(const T &a) -> decltype(-a)
{
cout << “printing with negative numeric function : ”;
cout << -a << endl;
return -a;
}

/* 3.如果T是指针,且指向的对象有size()成员函数,则替换成功。 */
template <typename T>
auto printSize(const T &a) -> decltype(a->size())
{
cout << “printing with pointer function : ”;
cout << a->size() << endl;
return a->size();
}

int main()
{
vector<int> vec{1, 2, 3};
printSize(vec); // calls first overload
printSize(vec[]); // calls second overload
printSize(&vec); // calls third overload
printSize(nullptr); // compiler error
}

练习

修改以下函数,使其变得更通用:

  1. 原函数:
1
2
3
4
5
6
7
8
9
10
11
12
tuple<bool, int, int> 
mismatch(const vector<int> &vec1, const vector<int> &vec2)
{
size_t i = 0;
while (i < vec1.size() && vec1[] == vec2[]) {
++i;
}
if (i == vec1.size())
return {false, 0, 0};
else
return {true, vec1[], vec2[]};
}

提升后:

1
2
3
4
5
6
7
8
9
10
template <typename InputIt1, typename InputIt2>
pair<InputIt1, InputIt2> mismatch(InputIt1 first1, InputIt1 last1,
InputIt2 first2)
{
while (first1 != last1 && *first1 == *first2) {
++first1;
++first2;
}
return {first1, first2};
}

示例代码

点击下载示例代码


4.Template Function
https://ci-tz.github.io/2024/02/05/4-Template-Function/
作者
次天钊
发布于
2024年2月5日
许可协议