当前位置:嗨网首页>书籍在线阅读

34-在可变参数模板函数中使用递归

  
选择背景色: 黄橙 洋红 淡粉 水蓝 草绿 白色 选择字体: 宋体 黑体 微软雅黑 楷体 选择字体大小: 恢复默认

18.6.3 在可变参数模板函数中使用递归

虽然前面的递归让show_list1()成为有用函数的希望破灭,但正确使用递归为访问参数包的内容提供了解决方案。这里的核心理念是,将函数参数包展开,对列表中的第一项进行处理,再将余下的内容传递给递归调用,以此类推,直到列表为空。与常规递归一样,确保递归将终止很重要。这里的技巧是将模板头改为如下所示:

template<typename T, typename... Args>
void show_list3( T value, Args... args)

对于上述定义,show_list3()的第一个实参决定了T和value的值,而其他实参决定了Args和args的值。这让函数能够对value进行处理,如显示它。然后,可递归调用show_list3(),并以args…的方式将其他实参传递给它。每次递归调用都将显示一个值,并传递缩短了的列表,直到列表为空为止。程序清单18.9提供了一种实现,它虽然不完美,但演示了这种技巧。

程序清单18.9 variadic1.cpp

//variadic1.cpp -- using recursion to unpack a parameter pack
#include <iostream>
#include <string>
// definition for 0 parameters -- terminating call
void show_list3() {}
// definition for 1 or more parameters
template<typename T, typename... Args>
void show_list3( T value, Args... args)
{
    std::cout << value << ", ";
    show_list3(args...);
}
int main()
{
    int n = 14;
    double x = 2.71828;
    std::string mr = "Mr. String objects!";
    show_list3(n, x);
    show_list3(x*x, '!', 7, mr);
    return 0;
}

1.程序说明

请看下面的函数调用:

show_list3(x*x, '!', 7, mr);

第一个实参导致T为double,value为x*x。其他三种类型(char、int和std::string)将放入Args包中,而其他三个值(‘!’、7和mr)将放入args包中。

接下来,函数show_list3()使用cout显示value(大约为7.38905)和字符串“, ”。这完成了显示列表中第一项的工作。

接下来是下面的调用:

show_list3(args...);

考虑到args…的展开作用,这与如下代码等价:

show_list3('!', 7, mr);

前面说过,列表将每次减少一项。这次T和value分别为char和‘!’,而余下的两种类型和两个值分别被包装到Args和args中,下次递归调用将处理这些缩小了的包。最后,当args为空时,将调用不接受任何参数的show_list3(),导致处理结束。

程序清单18.9中两个函数调用的输出如下:

14, 2.71828, 7.38905, !, 7, Mr. String objects!,

2.改进

可对show_list3()做两方面的改进。当前,该函数在列表的每项后面显示一个逗号,但如果能省去最后一项后面的逗号就好了。为此,可添加一个处理一项的模板,并让其行为与通用模板稍有不同:

// definition for 1 parameter
template<typename T>
void show_list3(T value)
{
    std::cout << value << '\n';
}

这样,当args包缩短到只有一项时,将调用这个版本,而它打印换行符而不是逗号。另外,由于没有递归调用show_list3(),它也将终止递归。

另一个可改进的地方是,当前的版本按值传递一切。对于这里使用的简单类型来说,这没问题,但对于cout可打印的大型类来说,这样做的效率很低。在可变参数模板中,可指定展开模式(pattern)。为此,可将下述代码:

show_list3(Args... args);

替换为如下代码:

show_list3(const Args&... args);

这将对每个函数参数应用模式const &。这样,最后分析的参数将不是std::string mr,而是const std::string& mr。

程序清单18.10包含这两项修改。

程序清单18.10 variadic2.cpp

// variadic2.cpp
#include <iostream>
#include <string>
// definition for 0 parameters
void show_list() {}
// definition for 1 parameter
template<typename T>
void show_list(const T& value)
{
    std::cout << value << '\n';
}
// definition for 2 or more parameters
template<typename T, typename... Args>
void show_list(const T& value, const Args&... args)
{
    std::cout << value << ", ";
    show_list(args...);
}
int main()
{
    int n = 14;
    double x = 2.71828;
    std::string mr = "Mr. String objects!";
    show_list(n, x);
    show_list(x*x, '!', 7, mr);
    return 0;
}

该程序的输出如下:

14, 2.71828
7.38905, !, 7, Mr. String objects!