模板的优化

模板的右尖括号

在泛型编程中,模板实例化有一个非常繁琐的地方,那就是连续的两个右尖括号 >> 会被编译器解析成右移操作符,而不是模板参数表的结束。
先看一段关于容器遍历的代码,在创建的类模板 Base 中提供了遍历容器的操作函数 traversal()

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
#include <iostream>
#include <vector>
using namespace std;

template <typename T>
class Base
{
public:
void traversal(T& t)
{
auto it = t.begin();
for (; it != t.end(); ++it)
{
cout << *it << " ";
}
cout << endl;
}
};


int main()
{
vector<int> v {1,2,3,4,5,6,7,8,9};
Base<vector<int>> b;
b.traversal(v);

return 0;
}

如果使用 C++98/03 标准来编译上边的这段代码,会得到错误提示如下:

1
2
test.cpp:25:20: error: '>>' should be '> >' within a nested template argument list
Base<vector<int>> b;

根据错误提示中描述模板的两个右尖括之间需要添加空格,这样写起来就非常的麻烦,C++11 改进了编译器的解析规则,尽可能地将多个右尖括号 >> 解析成模板参数结束符,方便编写模板相关的代码。
上面的这段代码,在支持 C++11 的编译器中编译是没有问题的,如果使用 g++ 直接编译需要加参数 -std=c++11:

默认模板参数

在 C++98/03 标准中,类模板可以有默认的模板参数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
#include <iostream>
using namespace std;

template <typename T = int, T t = 520>
class Test
{
public:
void print()
{
cout << "current value: " << t << endl;
}
};

int main()
{
Test<> t;
t.print();

Test<int, 1024> t1;
t1.print();

return 0;
}

但是不支持函数的默认模板参数,在 C++11 中添加了对函数模板默认参数的支持:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#include <iostream>
using namespace std;

template <typename T = int> // C++98/03不支持这种写法,C++11中支持这种写法
void func(T t)
{
cout << "current value: " << t << endl;
}

int main()
{
func(100);

return 0;
}

上面的代码可知:当所有模板参数都有默认参数时,函数模板的调用像一个普通函数。
但对于类模板而言,就算所有参数都有默认参数,在使用时也必须在模板名后添加 <> 来实例化。

另外:函数模板的默认模板参数在使用规则上和其他的默认参数也有一些不同,它没有必须写在参数表最后的限制。
默认模板参数模板参数自动推导结合起来时,书写非常灵活。可以指定函数模板中的一部分模板参数使用默认参数,另一部分使用自动推导。
示例代码如下:

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
#include <iostream>
#include <string>
using namespace std;

template <typename R = int, typename N>
R func(N arg)
{
return arg;
}

int main()
{
auto ret1 = func(520); // 函数的返回值类型使用了默认的模板参数,函数的参数类型是自动推导出来为 int 类型
cout << "return value-1: " << ret1 << endl;

auto ret2 = func<double>(52.134); // 函数的返回值指定为 double 类型,函数参数是通过实参推导出为 double 类型
cout << "return value-2: " << ret2 << endl;

auto ret3 = func<int>(52.134); // 函数的返回值指定为 int 类型,函数参数是通过实参推导出为 double 类型
cout << "return value-3: " << ret3 << endl;

auto ret4 = func<char, int>(100); // 函数的参数为指定为 int 类型,函数的返回值指定为 char 类型,不需要推导
cout << "return value-4: " << ret4 << endl;

return 0;
}

默认模板参数模板参数自动推导同时使用时(优先级从高到低):
如果可以推导出参数类型,那么使用推导出的类型。
如果函数模板无法推导出参数类型,那么编译器会使用默认模板参数。
如果无法推导出模板参数类型并且没有设置默认模板参数,编译器就会报错。

示例代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
#include <iostream>
#include <string>
using namespace std;

// 函数模板定义
template <typename T, typename U = char>
void func(T arg1 = 100, U arg2 = 100)
{
cout << "arg1: " << arg1 << ", arg2: " << arg2 << endl;
}

int main()
{
// 模板函数调用
func('a'); // 参数 T 被自动推导为 char 类型,U 使用的默认模板参数为 char 类型
func(97, 'a'); // 参数 T 被自动推导为 int 类型,U 使用推导出的类型为 char
// func(); // 编译器报错:参数 T 没有指定默认模板类型,并且无法自动推导

return 0;
}

参考资料

https://subingwen.cn/cpp/template


模板的优化
https://lcf163.github.io/2021/09/15/模板的优化/
作者
乘风的小站
发布于
2021年9月15日
许可协议