Namespaces
Variants

Variadic arguments

From cppreference.net
C++ language
General topics
Flow control
Conditional execution statements
Iteration statements (loops)
Jump statements
Functions
Function declaration
Lambda function expression
inline specifier
Dynamic exception specifications ( until C++17* )
noexcept specifier (C++11)
Exceptions
Namespaces
Types
Specifiers
constexpr (C++11)
consteval (C++20)
constinit (C++20)
Storage duration specifiers
Initialization
Expressions
Alternative representations
Literals
Boolean - Integer - Floating-point
Character - String - nullptr (C++11)
User-defined (C++11)
Utilities
Attributes (C++11)
Types
typedef declaration
Type alias declaration (C++11)
Casts
Memory allocation
Classes
Class-specific function properties
Special member functions
Templates
Miscellaneous

允许函数接受任意数量的额外参数。

当函数的 参数列表 中最后一个参数是省略号( ... )时,该函数为可变参数函数。

省略号前的逗号可以省略。 (deprecated in C++26)
// 函数声明如下
int printx(const char* fmt, ...);
int printx(const char* fmt...); // 与上述相同,但自 C++26 起已弃用
// 可使用一个或多个参数调用:
printx("hello world");
printx("a=%d b=%d", a, b);
int printy(..., const char* fmt); // 错误:... 只能是最后一个参数
int printz(...); // 有效,但无法以可移植方式访问参数

这与函数 形参包 展开不同——后者由作为形参声明符组成部分的省略号表示,而非将省略号作为独立形参。形参包展开和“可变参数”省略号均可出现在函数模板声明中,例如 std::is_function 的情况。

(since C++11)

目录

默认参数提升

当调用可变参数函数时,在经历左值到右值、数组到指针和函数到指针 转换 后,属于可变参数列表的每个参数还会进行额外的转换,称为 默认参数提升

(since C++11)
  • float 参数会按照 浮点提升 规则转换为 double
  • bool char short 及无作用域枚举类型会按照 整型提升 规则转换为 int 或更宽的整数类型。

非POD类类型 (C++11前) 具有合格非平凡复制构造函数、合格非平凡移动构造函数或非平凡析构函数的作用域枚举和类类型 (C++11起) 在具有实现定义语义的潜在求值调用中为有条件支持(这些类型在 不求值调用 中始终受支持)。

由于可变参数在 重载解析 中具有最低优先级,它们通常被用作 SFINAE 中的全能后备方案。

在使用可变参数的函数体内,可以通过 <cstdarg> 库工具 访问这些参数的值:

定义于头文件 <cstdarg>
启用可变参数函数参数的访问
(函数宏)
访问下一个可变参数函数参数
(函数宏)
(C++11)
创建可变参数函数参数的副本
(函数宏)
结束可变参数函数参数的遍历
(函数宏)
持有 va_start va_arg va_end va_copy 所需的信息
(类型定义)

若省略号前的最后一个参数具有引用类型,或其类型与 默认参数提升 后的类型不兼容,则 va_start 宏的行为是未定义的。

若将 包展开 或由 lambda 捕获 产生的实体用作 va_start 的最后一个参数,则程序非良构,不要求诊断。

(since C++11)

替代方案

  • 可变参数模板 也可用于创建接受可变数量参数的函数。它们通常是更优的选择,因为不对参数类型施加限制,不执行整型和浮点型提升,且具备类型安全性。
  • 若所有可变参数共享同一类型, std::initializer_list 提供了一种便捷机制(尽管语法不同)来访问可变参数。但此种情况下参数不可修改,因为 std::initializer_list 仅能提供指向其元素的常量指针。
(自 C++11 起)

注释

在C语言中,直到C23标准之前,省略号参数前必须至少出现一个命名参数,因此 R printz ( ... ) ; 在C23之前是无效的。在C++中,即使传递给此类函数的参数不可访问,这种形式也是被允许的,并且通常用作 SFINAE 中的备选重载,利用省略号转换在 重载解析 中的最低优先级特性。

这种可变参数语法于1983年在C++中引入时,省略号前没有逗号。当C89从C++引入函数原型时,将其替换为要求逗号的语法。为保持兼容性,C++98同时接受C++风格的 f ( int n... ) 和C风格的 f ( int n, ... ) 。原始C++风格语法自C++26起被弃用。

逗号可用于简写函数模板中,使省略号表示可变参数函数而非可变参数模板:

void f1 ( auto ... ) ; // same as template<class... Ts> void f3(Ts...)
void f2 ( auto , ... ) ; // same as template<class T> void f3(T, ...)

(since C++20)

缺陷报告

以下行为变更缺陷报告被追溯应用于先前发布的C++标准。

缺陷报告 适用标准 发布时行为 正确行为
CWG 506 C++98 向省略号传递非POD类参数
会导致未定义行为
传递此类参数为有条件支持,
具有实现定义的语义
CWG 634 C++98 有条件支持的类类型
导致某些SFINAE用法失效
在未求值语境中始终支持
CWG 2247 C++11 未限制向 va_start 传递
参数包或lambda捕获
视为病式构造,
不要求诊断
CWG 2347 C++11 未明确作用域枚举类型传递给
省略号时是否适用默认参数提升
传递作用域枚举为有条件支持,
具有实现定义的语义

参阅

C 文档 关于 可变参数
C 文档 关于 隐式转换