变长实参
允许函数接受任意数量的额外实参。
由跟在函数声明的 形参列表 之后的(并非引入包展开的) (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 起) |
默认转换
调用变参函数时,进行左值到右值、数组到指针及函数到指针的转换后,可变实参列表中的每个实参都要经过称为默认实参提升的额外转换:
|
(C++11 起) |
只能有算术、枚举、指针、成员指针及类类型的(转换后的)额外实参。然而,非 POD 类类型 (C++11 前)拥有合格的非平凡复制构造函数、合格的非平凡移动构造函数或非平凡析构函数的类类型,以及有作用域枚举类型 (C++11 起)在潜在求值的调用中是以实现定义的语义条件性支持的(这些类型在不求值调用中始终支持)。
因为变长形参对于重载决议而言具有最低的优先级,所以它们常被用作 SFINAE 中的万应后备(catch-all fallback)。
在使用变长实参的函数体内,可以用一些 <cstdarg>
库设施访问这些实参的值:
在标头
<cstdarg> 定义 | |
启用对可变函数实参的访问 (宏函数) | |
访问下一个可变函数实参 (宏函数) | |
(C++11) |
制造可变函数实参的副本 (宏函数) |
结束对可变函数实参的遍历 (宏函数) | |
保有 va_start、va_arg、va_end 和 va_copy 所需的信息 (typedef) |
如果省略号前的最后一个形参具有引用类型,或其类型与默认实参提升所产生的各种类型不兼容,那么 va_start 宏的行为未定义。
(C++11 起) |
替代方案
|
(C++11 起) |
注解
因为在 C23 前的 C 编程语言中,省略号前必须至少出现一个具名形参,所以 R printz(...); 在 C23 前非法。C++ 中允许这种形式,尽管无法访问传递给这种函数的实参,此形式常用作 SFINAE 的后备重载,它利用了重载决议中省略号转换的最低优先级。
变长实参的语法在 1983 年引入 C++,省略号前没有逗号。C89 从 C++ 接受函数原型时,它以要求逗号的语法替换了该语法。为了兼容,C++98 一并接受 C++ 风格 f(int n...) 和 C 风格 f(int n, ...)。
缺陷报告
下列更改行为的缺陷报告追溯地应用于以前出版的 C++ 标准。
缺陷报告 | 应用于 | 出版时的行为 | 正确行为 |
---|---|---|---|
CWG 506 | C++98 | 传递非 POD 类实参给省略号会导致未定义行为 | 传递此类参数受条件性支持,并且语义由实现定义 |
CWG 634 | C++98 | 条件性支持的类类型使某些 SFINAE 手法无法起效 | 如果不求值则始终支持 |
CWG 2247 | C++11 | 不限制传递形参包或 lambda 捕获给 va_start
|
改为非良构,不要求诊断 |
CWG 2347 | C++11 | 不明确传递有作用域枚举给省略号是否适用默认实参提升 | 传递有作用域枚举受条件性支持,并且语义由实现定义 |