允许函数接受任意数量的实参。
由跟在函数声明的 形参列表 之后的(并非引入包展开的) (C++11 起)尾随 … 指定。
形参列表 非空时,指示变参函数的 … 之前可以出现可选的逗号。这提供与 C 的兼容性(从 C++ 采纳函数原型时,C 向其添加了逗号的要求)。
- // 如下声明的函数
- int printx(const char* fmt, ...);
- // 能以一个或多个实参调用:
- printx("hello world");
- printx("a=%d b=%d", a, b);
- int printx(const char* fmt...); // 同上(为与 C 兼容允许额外的逗号)
- int printy(..., const char* fmt); // 错误:... 不能作为形参出现
- int printz(...); // 合法,但无法可移植地访问参数
注意:这与函数形参包展开是不同的,形参包展开是由作为形参声明符一部分的省略号指定的,而非由出现在所有形参声明之后的省略号。形参包展开和“变参”省略号都能出现在函数模板声明中,如 std::is_function 的情况。 | (C++11 起) |
默认转换
调用变参函数时,进行左值到右值、数组到指针及函数到指针转换后,可变实参列表中的每个实参都要经过称为默认实参提升的额外转换:
- 转换 std::nullptr_t 到 void*
- 转换 float 到 double,如同浮点提升
- 转换 bool、char、short 及无作用域枚举到 int 或更宽的整数类型,如同整数提升
仅允许算术、枚举、指针、成员指针及类类型的实参(但不包括拥有非平凡复制构造函数、非平凡移动构造函数或非平凡析构函数的类类型,这些类型是条件性支持的,并具有由实现定义的语义)
因为变长形参对于重载决议而言具有最低的优先级,所以它们常被用作 SFINAE 中的万应后备(catch-all fallback)。
在使用变长实参的函数体内,可利用一些 <cstdarg> 库设施访问这些实参的值:
若省略号前的最后一个形参具有引用类型,或其类型与默认实参提升所产生的各种类型不兼容,则 va_start 宏的行为未定义。
替代方案
- 变参模板亦可用于创建接受可变数量实参的函数。它们通常是更好的选择,因为它们对实参类型不施加任何制约,不进行整型和浮点提升,且是类型安全的。 - 当所有变长实参均为相同类型时,std::initializer_list 提供了一种访问变长实参的便利机制。不过这种情况下不能修改各个实参,因为 std::initializer_list 仅提供指向其各元素的 const 指针。 | (C++11 起) |
注解
在 C 编程语言中,省略号前必须至少出现一个具名形参,故 printz(…);
非法。C++ 中允许这种形式,尽管无法访问传递给这种函数的实参,此形式常用作 SFINAE 的后备重载,它开发利用了重载决议中省略号转换的最低优先级。
变长实参的语法于 1983 年引入 C++,无省略号前的逗号。C89 从 C++ 接受函数原型时,它以要求逗号的语法替换了该语法。为了兼容,C++98 一并接受 C++ 风格 f(int n…) 和 C 风格 f(int n, …)。
当前内容版权归 cppreference 或其关联方所有,如需对内容或内容相关联开源项目进行关注与资助,请访问 cppreference .