在 c++11之前,编写带有任意数量参数的函数的唯一方法是使用可变参数函数,最有名的要数 C Runtime Library 的 printf (...)。
如果您曾经使用这种方法编写过代码,你就会知道这种方法有多么麻烦。 除了类型不安全之外,还需要纠正一些错误。而且,每次使用都要小心翼翼,说不定哪里就出错了。所以,下面给大家介绍一个超cool的东西,可变参数模板。
可变参数模板
直接来看个例子,最经典的就是一个add函数的实现了,它将所有参数加在一起:
template <typename T>
T add(T value)
{
return value;
}
template <typename T, typename... Ts>
T add(T head, Ts... rest)
{
return head + add(rest...);
}
当你写下如下的两个调用时:
auto test1 = add(1,2,3,4,5);
auto test2 = add("what's"s, " up"s, "!"s);
std::cout << test1 << std::endl;
std::cout << test2 << std::endl;
它会输出:
15
what's up!
发生了什么
这看起来很像递归不是吗。其实不是,来看下为什么。
当你写下这样的函数的时候,编译器会为你生成以下函数:
template<>
int add<int, int, int, int, int>(int head, int __rest1, int __rest2, int __rest3,
int __rest4)
{
return head + add(__rest1, __rest2, __rest3, __rest4);
}
template<>
int add<int, int, int, int>(int head, int __rest1, int __rest2, int __rest3)
{
return head + add(__rest1, __rest2, __rest3);
}
template<>
int add<int, int, int>(int head, int __rest1, int __rest2)
{
return head + add(__rest1, __rest2);
}
template<>
int add<int, int>(int head, int __rest1)
{
return head + add(__rest1);
}
template<>
int add<int>(int value)
{
return value;
}
当你在你的程序调用add函数时,在编译阶段,编译器会为你生成每个调用对应的函数,包括参数的个数和类型。其实上面的结果告诉我们,是函数重载在起作用。注意了哦,是函数重载,可不是递归。
更多
虽然在上面的例子它很好的运行了,并且给出了我们预期的结果。但是,假如我们变化一下调用的方式呢?
add("what's"s, ' ', "up!"s);
上面的例子中,我们在中间加了一个char类型的 ' ',因为std::string是允许我们将一个char + std::string或者std::string + char这样的调用的,我们可以将一个char类型字符的拼接到一个string类型的字符串,形成一个更长的字符串。所以我们这样应该也是可以的。但是当我写下这样的代码,按下F5,编译器却报给了我一个错误。下面是报错截图:
发生了什么呢?再看一下我们编译器为我们特化的函数,要注意到,每个函数的返回类型,T,都是和第一个参数的类型相同,所以当我特化到add(char, std::string)的时候,它的返回类型是char,但是实际上我们的函数内部返回的却是一个string类型的东西,这样就会造成返回类型不匹配。怎么解决呢?
怎么解决
还记得我们的auto吗?哈哈,这个好东西又派上用场了,我们需要将我们的add函数返回类型声明的auto,这样,编译器就会自动推导出一个正确的返回类型。
template <typename T>
auto add(T value)
{
return value;
}
template <typename T, typename... Ts>
auto add(T head, Ts... rest)
{
return head + add(rest...);
}
这样,当你再这样调用的时候:
add("what's"s, ' ', "up!"s);
It works!