第一章:函数模板
函数模板是指被参数化的模板,可以代表一类函数。
1.1 初识函数模板
函数模板和普通函数看起来很相似,但是部分元素是未确定的。
1.1.1 定义模板
下面定义了从两个值中返回最大值的函数。
template<typename T>
T max(T a, T b)
{
// if b < a then yield a else yield b
return b < a ? a : b;
}
我们使用
template<typename T>
声明了模板化的参数是T
。
由于历史原因,你可以用 class
或 typename
定义类型参数,但是 struct
不行。推荐使用 typename
避免歧义。
1.1.2 使用模板
通过以下程序来使用模板:
#include "max1.hpp"
#include <iostream>
#include <string>
int main()
{
int i = 42;
std::cout << "max(7,i): " << ::max(7,i) << '\n';
double f1 = 3.4; double f2 = -6.7;
std::cout << "max(f1,f2): " << ::max(f1,f2) << '\n';
std::string s1 = "mathematics"; std::string s2 = "math";
std::cout << "max(s1,s2): " << ::max(s1,s2) << '\n';
}
我们可以看到分别对 int
, float
, std::string
三个类型使用了模板。输出为:
max(7,i): 42
max(f1,f2): 3.4
max(s1,s2): mathematics
此外,我们通过使用 ::max
指明了 max 是在全局 namespace 的max,而非 std::max
。
模板并非被编译成一种能够处理所有类型的函数,而是对每个使用模板的类型都生成一个函数。因此,对于 int
的调用,在编译时将 T
替换为 int
,编译了如下函数:
int max (int a, int b)
{
return b < a ? a : b;
}
类似的,其他两个调用相当于用 float
和 std::string
替换了 T
。
这个过程称为模板的“实例化”。
1.1.3 两步翻译
如果实例化一个模板不支持的类型,会引起编译错误。模板是通过两个步骤编译的:
- “定义”阶段,无视模板参数
T
,检查模板语法
- 发现语法错误,例如缺少
;
,使用未定义的变量等 - 检查不依赖
T
的static_assert
- “实例化”阶段,再次检查,包括依赖
T
的部分
例如:
template<typename T>
void foo(T t)
{
undeclared(); // first-phase compile-time error if undeclared() unknown
undeclared(t); // second-phase compile-time error if undeclared(T) unknown
static_assert(sizeof(int) > 10, // always fails if sizeof(int)<=10
"int too small");
static_assert(sizeof(T) > 10, //fails if instantiated for T with size <=10
"T too small");
}
编译和链接
两步翻译引出一个处理模板时的难题:当使用的函数模板需要实例化时,编译器需要看到模板的实现。
对于普通的函数,编译和链接是分开的。只需要声明函数就足够编译了,而函数模板则需要看到实现。我们会在第九章讨论如何解决这个问题,为了简便,我们教程中都会将模板的实现放在头文件里。
1.2 模板类型推断
当我们调用函数模板时,T
是由我们传入的参数类型决定的。
类型推断中的类型转换
自动类型转换在类型推断中的限制:
-
如果所有的参数都是传引用(
&
),则不会进行类型转换。 -
如果所有的参数都是传值,则只允许“简单”转换:
const
和volatile
会被忽略,引用会被转为被引用类型,数组和函数会被转为指针。
例如:
template<typename T>
T max (T a, T b);
…
int const c = 42;
max(i, c); // OK: T is deduced as int
max(c, c); // OK: T is deduced as int
int& ir = i;
max(i, ir); // OK: T is deduced as int
int arr[4];
foo(&i, arr); // OK: T is deduced as int*
然而,如果需要类型转换,例如 int
到 float
,就失败。例如:
max(4, 7.2); // ERROR: T can be deduced as int or double
std::string s;
foo("hello", s); //ERROR: T can be deduced as char const[6] or std::string
我们需要提前处理好类型再调用。
1.3 多个模板参数
如果我们确实需要比较不同类型的参数,那我们可能需要写如下代码
template<typename T1, typename T2>
T1 max (T1 a, T2 b)
{
return b < a ? a : b; }
…
auto m = ::max(4, 7.2); // OK, but type of first argument defines
但是这个返回值显然很奇怪,以上的例子会返回 7(int) 而非 7.2(float)。
从 C++14 开始,我们可以让编译器推断返回类型,于是可以更优美地实现:
template<typename T1, typename T2>
auto max (T1 a, T2 b)
{
return b < a ? a : b;
}
有时我们希望有同样类型的返回值,这时可以使用 std::common_type<>::type
返回多个类型中最 general 的类型。例如:
#include <type_traits>
template<typename T1, typename T2>
std::common_type_t<T1,T2> max (T1 a, T2 b)
{
return b < a ? a : b;
}
(吐槽:所以为什么不一开始就转成一个类型,搞这么复杂)
1.4 默认模板参数
不常用,略过
1.5 模板函数重载
就像普通函数,模板函数也能被 overload。
// maximum of two int values:
int max (int a, int b)
{
return b < a ? a : b;
}
// maximum of two values of any type:
template<typename T>
T max (T a, T b)
{
return b < a ? a : b;
}
int main()
{
::max(7, 42); // calls the nontemplate for two ints
::max(7.0, 42.0); // calls max<double> (by argument deduction)
::max(’a’, ’b’); //calls max<char> (by argument deduction)
::max<>(7, 42); // calls max<int> (by argument deduction)
::max<double>(7, 42); // calls max<double> (no argument deduction)
::max(’a’, 42.7); //calls the nontemplate for two ints
}
可以看到,规则有:
- 采用在不转化类型前提下最匹配的函数
- 同等匹配度条件下,优先非模板函数,但可以用
<>
指明使用模板生成的函数
我们可以利用这个原则,优化对于指针类型或者const char*
类型的比较。
#include <cstring>
#include <string>
// maximum of two values of any type:
template<typename T>
T max (T a, T b)
{
return b < a ? a : b;
}
// maximum of two pointers:
template<typename T>
T* max (T* a, T* b)
{
return *b < *a ? a : b;
}
// maximum of two C-strings:
char const* max (char const* a, char const* b)
{
return std::strcmp(b,a) < 0 ? a : b;
}
int main ()
{
int a = 7;
int b = 42;
auto m1 = ::max(a,b); // max() for two values of type int
std::string s1 = "hey"; "
std::string s2 = "you"; "
auto m2 = ::max(s1,s2); // max() for two values of type std::string
int* p1 = &b;
int* p2 = &a;
auto m3 = ::max(p1,p2); // max() for two pointers
char const* x = hello"; "
char const* y = "world"; "
auto m4 = ::max(x,y); // max() for two C-strings
}