在 C++ 98/03 标准中,类模板可以有默认的模板参数,但是却不支持函数的默认模板参数:

1
2
3
4
5
template <typename T, typename U = int, U N = 0>
struct Foo { };

template <typename T = int> // error in C++98/03: default template arguments
void func() { } // in C++11: T = int

现在这一限制在 C++11 中被解除了,上面的 func 函数在 C++11 中可以直接使用。

函数模板的默认模板参数在使用规则上和其他的默认参数也有一些不同,它没有必须写在参数表最后的限制。甚至于,根据实际场景中函数模板被调用的情形,编译器还可以自行推导出部分模板参数的类型(优先于默认类型)。

这意味着,当默认模板参数和编译器自行推导出模板参数类型的能力一起结合使用时,代码的书写将变得异常灵活。我们可以指定函数中的一部分模板参数采用默认参数,而另一部分使用自动推导,比如下面的例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
template <typename R = int, typename U>
R func(U val)
{
return val;
}

int main()
{
func(97); // R=int, U=int
func<char>(97); // R=char, U=int
func<double, int>(97); // R=double, U=int
return 0;
}

C++11 标准中,我们可以像 func(97) 这样调用模板函数,因为编译器可以根据实参 97 自行推导出模板参数 U 的类型为 int,并且根据返回值 val=97 推导出 R 的类型也为 int;而 func<char>(97) 手动指定了模板参数 R 的类型为 char(默认模板参数将无效),并通过实参 97 推导出了 U = int;最后 func<double,int>(97) 手动指定的 R 和 U 的类型值,因此无需编译器自行推导。


当默认模板参数和自行推导的模板参数同时使用时,若无法推导出函数模板参数的类型,编译器会选择使用默认模板参数;如果模板参数既无法推导出来,又未设置其默认值,则编译器直接报错。例如:

1
2
3
4
5
6
7
8
9
template <typename T, typename U = double>
void func(T val1 = 0, U val2 = 0) { }

int main()
{
func('c'); // T=char, U=double
func(); // T=?, U=double,编译报错
return 0;
}

其中,func('c') 的这种调用方式,编译器通过实参 ‘c’ 可以推导出 T=char,但由于未传递第 2 个实参,因此模板参数 U 使用的是默认参数 double;但 func() 的调用方式是不行的,虽然 val1 设置有默认值,但编译器无法通过该默认值推导出模板参数 T 的类型。