省略号和可变参数模板

本文介绍了如何将省略号 (...) 与 C++ 可变参数模板一起使用。 省略号在 C 和 C++ 中有多种用法。 其中包括函数的变量参数列表。 C 运行时库中的 printf() 函数是最著名的示例之一。

“可变参数模板”是支持任意数量的参数的类或函数模板。 此机制对 C++ 库开发人员特别有用:可以将其应用于类模板和函数模板,从而提供广泛的类型安全且非凡的功能和灵活性。

语法

可变参数模板以两种方式使用省略号。 在参数名称的左侧,表示“参数包”,在参数名称的右侧,将参数包扩展为单独的名称。

下面是可变参数类模板定义语法的一个基本示例:

template<typename... Arguments> class classname;

对于参数包和扩展,您可根据您的偏好在省略号周围添加空白,如以下示例所示:

template<typename ...Arguments> class classname;

或者此示例:

template<typename ... Arguments> class classname;

本文使用第一个示例中显示的约定(省略号已附加到 typename)。

在前面的示例中,Arguments 是参数包。 类 classname 可以接受可变数量的参数,如以下示例所示:

template<typename... Arguments> class vtclass;

vtclass< > vtinstance1;
vtclass<int> vtinstance2;
vtclass<float, bool> vtinstance3;
vtclass<long, std::vector<int>, std::string> vtinstance4;

通过使用可变参数类模板定义,还可以至少需要一个参数:

template <typename First, typename... Rest> class classname;

下面是可变参数函数模板语法的基本示例:

template <typename... Arguments> returntype functionname(Arguments... args);

然后,将展开 Arguments 参数包以供使用,如下一部分所示。

可以采用其他形式的可变参数函数模板语法,包括但不限于以下示例:

template <typename... Arguments> returntype functionname(Arguments&... args);
template <typename... Arguments> returntype functionname(Arguments&&... args);
template <typename... Arguments> returntype functionname(Arguments*... args);

也允许使用说明符(如 const):

template <typename... Arguments> returntype functionname(const Arguments&... args);

与可变参数模板类定义一样,可以生成要求至少一个参数的函数:

template <typename First, typename... Rest> returntype functionname(const First& first, const Rest&... args);

可变参数模板使用 sizeof...() 运算符(与旧的 sizeof() 运算符无关):

template<typename... Arguments>
void tfunc(const Arguments&... args)
{
    constexpr auto numargs{ sizeof...(Arguments) };

    X xobj[numargs]; // array of some previously defined type X

    helper_func(xobj, args...);
}

有关省略号位置的更多信息

此前,本文介绍了以以下形式定义参数包和扩展的省略号位置:“参数名称的左侧表示参数包,参数名称的右侧将参数包扩展为单独的名称”。 虽然从技术上讲的确如此,但在转换为代码的过程中可能会造成混淆。 请注意以下几点:

  • 在模板参数列表 (template <parameter-list>) 中,typename... 引入了模板参数包。

  • 在参数声明子句 (func(parameter-list)) 中,“顶级”省略号引入了函数参数包,并且省略号定位很重要:

    // v1 is NOT a function parameter pack:
    template <typename... Types> void func1(std::vector<Types...> v1);
    
    // v2 IS a function parameter pack:
    template <typename... Types> void func2(std::vector<Types>... v2);
    
  • 其中省略号显示在参数名称的后面,并且您具有一个参数包扩展。

示例

说明可变参数函数模板机制的一个好方法是在重写 printf 的某些功能时使用它:

#include <iostream>

using namespace std;

void print() {
    cout << endl;
}

template <typename T> void print(const T& t) {
    cout << t << endl;
}

template <typename First, typename... Rest> void print(const First& first, const Rest&... rest) {
    cout << first << ", ";
    print(rest...); // recursive call using pack expansion syntax
}

int main()
{
    print(); // calls first overload, outputting only a newline
    print(1); // calls second overload

    // these call the third overload, the variadic template,
    // which uses recursion as needed.
    print(10, 20);
    print(100, 200, 300);
    print("first", 2, "third", 3.14159);
}

输出

1
10, 20
100, 200, 300
first, 2, third, 3.14159

注意

合并可变参数函数模板的大多数实现使用某种形式的递归,但与传统递归略有不同。 传统的递归涉及使用同一签名调用自身的函数。 (它可能会重载或模板化,但每次选择相同的签名。)可变参数递归涉及通过使用变化(基本都是减少)数量的参数来调用可变参数函数模板,从而每次都清除不同的签名。 仍然需要“基案例”,但递归的性质不同。