首页 > 编程笔记 > C++笔记 阅读:185

C++11支持函数模板的默认模板参数

C++98/03 中,类模板可以有默认的模板参数,如下:
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(void)
{
    // ...
}
现在这一限制在 C++11 中被解除了。上面的 func 函数在 C++11 中可以直接使用,代码如下:
int main(void)
{
    func();  // 如同一个普通的void(void)类型函数
    return 0;
}
从上面的例子中可以看出来,当所有模板参数都有默认参数时,函数模板的调用如同一个普通函数。对于类模板而言,哪怕所有参数都有默认参数,在使用时也必须在模板名后跟随<>来实例化。

除了上面提到的部分之外,函数模板的默认模板参数在使用规则上和其他的默认参数也有一些不同,它没有必须写在参数表最后的限制。

因此,当默认模板参数和模板参数自动推导结合起来使用时,书写显得非常灵活。我们可以指定函数中的一部分模板参数采用默认参数,而另一部分使用自动推导,比如下面的例子:
template <typename R = int, typename U>
R func(U val)
{
    ret val
}
int main(void)
{
    func(123);
    return 0;
}
但需要注意的是,在调用函数模板时,若显示指定模板的参数,由于参数填充顺序是从右往左的,因此,像下面这样调用:

func<long>(123);   // func的返回值类型是long

函数模板 func 的返回值类型是 long,而不是 int,因为模版参数的填充顺序从右往左,所以指定的模版参数类型 long 会作为 func 的参数类型而不是 func 的返回类型,最终 func 的返回类型为long。

这个细节虽然简单,但在多个默认模板参数和模板参数自动推导穿插使用时很容易被忽略掉,造成使用时的一些意外。

另外,当默认模板参数和模板参数自动推导同时使用时,若函数模板无法自动推导出参数类型,则编译器将使用默认模板参数;否则将使用自动推导出的参数类型。请看下面这个例子:
template <typename T>
struct identity
{
    typedef T type;
};

template <typename T = int>
void func(typename identity<T>::type val, T = 0)
{
    // ...
}

int main(void)
{
    func(123);         // T -> int
    func(123, 123.0);  // T -> double
    return 0;
}
在例子中,通过 identity 外敷模板禁用了形参 val 的类型自动推导。但由于 func 指定了模板参数T的默认类型为 int,因此,在 func(123) 中,func 的 val 参数将为 int 类型。而在 func(123, 123.0) 中,由于 func 的第二个参数 123.0 为 double 类型,模板参数 T 将优先被自动推导为 double,因此,此时 val 参数将为 double 类型。

这里要注意的是,不能将默认模板参数当作模板参数自动推导的“建议”,因为模板参数自动推导总是根据实参推导来的,当自动推导生效时,默认模板参数会被直接忽略。