在C++11中引入了可变参数模板,顾名思义参数的数量是可以改变的,我们先来看一段代码

#include <iostream>
using namespace std;

void myPrint()
{
    cout << "the last one";
}

template <typename T, typename... Args>
void myPrint(T first, Args... rest)
{
    cout << "process:" << first << endl;
    myPrint(rest...);
}

int main()
{
    myPrint(123, "hello", 3.14);
    return 0;
}

输出

process:123
process:hello
process:3.14
the last one

我们慢慢分析这段代码,这段代码先是定义了一个myPrint函数,这个函数没有参数,当参数包为空时会调用该函数。
再是定义了一个函数模板,函数模板,模板参数中有一个typename... Args,这个参数是可变参数模板的关键。
在C++17之前可变参数模板是通过递归展开的。我们可以看到myPrint函数中调用了myPrint函数,这表明该可变参数函数模板通过递归的方式展开参数,而上述提到的无参数的函数模板就是该递归的终止条件。
对于递归展开的可变参数函数模板,必须确保每次递归参数包严格减少,必须定义终止函数即无参数同名函数。

上述代码的调用过程分析。

在main函数中我们给myPrint函数传入参数包{123,"hello",3.14}

  1. 第一次调用
    T=int,Args = {const char*,double},rest... = {"hello",3.14}
    cout<<123<<endl;
    myPrint("hello",3.14);
  2. 第二次调用
    T = const char*,Args = {double},rest... ={3.14}
    cout<<"hello"<<endl;
    myPrint(3.14);
  3. 第三次调用
    T = double,Args = {},rest... ={}
    cout<<3.14<<endl;
    myPrint();
  4. 第四次调用
    cout << "the last one";

    底层机制

    递归展开在编译期间生成多个函数实现:

  5. myPrint(int,const char*,double)
  6. myPrint(const char*,double)
  7. myPrint(double)
  8. myPrint()
    每个实例处理不同数量的类型。

    缺点

    参数包过大会导致编译时间加长,生成代码体积变大。

    其他方案

    在C++17以后可以使用折叠表达式简化代码,避免了现实递归。

    #include <iostream>
    using namespace std;
    
    template <typename... Args>
    void myPrint(Args... rest)
    {
     (cout <<...<<rest);
    }
    
    int main()
    {
     myPrint(123, "hello", 3.14);//输出123hello3.14
     return 0;
    }

    在下一篇文章中介绍折叠表达式

最后修改:2025 年 02 月 28 日
如果觉得我的文章对你有用,请随意赞赏