C++11的可变参数模板函数

        C++11增加了模板功能,在C++11之前,类模板和函数模板只能包含有固定数量的模板参数,现在C++11中的新特性可变参数模板允许模板定义中包含0到任意个模板参数。可变参数模板和普通模板的语义是一样的,只是在写法上稍有区别,声明可变参数模板时需要在typename或class后面带上省略号"..." 。

        省略号的作用有两个:

        1、声明一个参数包,这个参数包中可以包含0到任意个模板参数。

        2、在模板定义的右边,可以将参数包展开成一个一个独立的参数。

可变参数模板函数

        一个可变参数模板函数的定义如下:

#include <iostream>
using namespace std;

template<class... T>
void f(T ...args)
{
    cout << sizeof...(args) << endl;
}

int main()
{    
    f();                ///0
    f(1, 2);            ///2
    f(1, 2.5, "");      ///3

    return 0;
}

        从上面的例子中可以看到,参数包可以容纳0到N个模板参数,这几个参数类型可以为任意类型。f()没有传入参数,所以参数包为空,输出的size为0,后面两次调用分别传入两个和三个参数,故输出的size分别为2和3。

        如果需要用参数包中的参数,则一定要将参数包展开。有两种展开参数包的方法:一种方法是通过递归的模板函数来将参数包展开,另外一种是通过逗号表达式和初始化列表方式展开参数包。

递归函数方式展开参数包

        通过递归函数展开参数包,需要提供一个参数包展开的函数和一个递归终止函数,递归终止函数正是用来终止递归的,看下面的例子:

#include <iostream>
using namespace std;

///递归终止函数
void print()
{
    cout << "empty" << endl;
}

///展开函数
template<class T, class ...Args>
void print(T head, Args ...rest)
{
    cout << "parameter " << head << endl;
    print(rest...);
}

int main()
{
    print(1, 2, 3, 4);

    return 0;
}

        上例会输出每一个参数,直到为空时输出empty。有两个函数,一个是递归函数,另一个是递归终止函数,参数包Args...在展开的过程中调用自己,每调用一次参数包中的参数就会减少一个,直到所有的参数包都展开为止,当没有参数时,则调用非模板函数print终止递归过程。

        递归的调用过程如下:

print(1, 2, 3, 4);
print(2, 3, 4);
print(3, 4);
print(4);
print();

逗号表达式和初始化列表展开参数包

        递归函数展开参数包是一种标准做法,也比较好理解,但也有一个缺点,就是必须有一个重载的递归终止函数,即必须有一个同名的终止函数来终止递归,这样会感觉稍有不便,因此可以借助逗号表达式和初始化列表来展开参数包。

#include <iostream>
#include <stdio.h>
using namespace std;

template<class T>
void printarg(T t)
{
    cout << t << endl;
}

template<class... Args>
void expand(Args... args)
{
    int arr[] = {(printarg(args), 0)...};
    ///printf("%d\n", arr[0]);
}

int main()
{
    expand(1, 2, 3, 4);

    return 0;
}

        这个例子将分别打印出1,2,3,4四个数字。这种展开参数包的方式,不需要通过递归终止函数,是直接在expand函数体中展开的,printarg不是递归终止函数,只是一个处理参数包中每一个参数的函数。这种就地展开参数包的方式实现的关键是逗号表达式。逗号表达式会按顺序执行逗号前面的表达式,比如:

        d = (a = b, c);

        这个表达式会按如下顺序执行:b会先赋值给a,接着括号中的逗号表达式返回c的值,因此d将等于c。

        expand函数中的逗号表达式:(printarg(arg), 0),也是按照这个执行顺序,先执行printarg(args),再得到逗号表达式的结果0。同时还用到了C++11的另外一个特性---初始化列表,通过初始化列表来初始化一个变长数组,{(printarg(args), 0)}...将会展开成((printarg(args1), 0), (printarg(args2), 0), (printarg(args3), 0), etc...),最终会创建一个元素值都为0的数组int arr[sizeof...(Args)]。由于是逗号表达式,在创建数组的过程中会执行逗号表达式前面的部分printarg(args)打印参数,也就是说在构造int数组的过程中就将参数包展开了,这个数组的目的纯粹是为了在数组构造的过程中展开参数包。

        我们还可以对上面的expand方法做一点改进,通过std::initializer_list来代替原来的int arr数组。改进之后的代码如下:

#include <iostream>
#include <stdio.h>
using namespace std;

template<class T>
void printarg(T t)
{
    cout << t << endl;
}

template<class... Args>
void expand(Args... args)
{
    initializer_list<int>{(printarg(args), 0)...};
    ///int arr[] = {(printarg(args), 0)...};
    ///printf("%d\n", arr[0]);
}

int main()
{
    expand(1, 2, 3, 4);

    return 0;
}

        

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值