默认实参
允许在调用函数时无需提供一个或多个尾随实参。
通过在形参列表中对函数声明的形参使用以下语法来指示。
attr(可选) decl-specifier-seq declarator = initializer |
(1) | ||||||||
attr(可选) decl-specifier-seq abstract-declarator(可选) = initializer |
(2) | ||||||||
默认实参用于代替函数调用中缺失的尾随实参
void point(int x = 3, int y = 4); point(1, 2); // calls point(1, 2) point(1); // calls point(1, 4) point(); // calls point(3, 4)
在函数声明中,在具有默认实参的形参之后,所有后续形参都必须
- 在此作用域或先前作用域的同一声明中提供默认实参
int x(int = 1, int); // Error: only the trailing parameters can have default arguments // (assuming there's no previous declaration of “x”) void f(int n, int k = 1); void f(int n = 0, int k); // OK: the default argument of “k” is provided by // the previous declaration in the same scope void g(int, int = 7); void h() { void g(int = 1, int); // Error: not the same scope }
template<class... T> struct C { void f(int n = 0, T...); }; C<int> c; // OK; instantiates declaration void C::f(int n = 0, int)
template<class... T> void h(int i = 0, T... args); // OK |
(自 C++11 起) |
省略号不是形参,因此可以跟随具有默认实参的形参
int g(int n = 0, ...); // OK
默认实参仅允许用于函数声明 和 lambda 表达式的形参列表,(自 C++11 起) 而不允许用于函数指针、函数引用或 typedef 声明的声明中。模板形参列表为其默认模板实参使用类似的语法。
对于非模板函数,如果函数在同一作用域中重新声明,则可以将默认实参添加到已声明的函数中。在函数调用点,默认实参是函数的所有可见声明中提供的默认实参的并集。重新声明不能为已可见默认实参的形参引入默认实参(即使值相同)。内部作用域中的重新声明不会从外部作用域获取默认实参。
void f(int, int); // #1 void f(int, int = 7); // #2 OK: adds a default argument void h() { f(3); // #1 and #2 are in scope; makes a call to f(3,7) void f(int = 1, int); // Error: the default argument of the second // parameter is not acquired from outer scopes } void m() { // new scope begins void f(int, int); // inner scope declaration; has no default argument. f(4); // Error: not enough arguments to call f(int, int) void f(int, int = 6); f(4); // OK: calls f(4, 6); void f(int, int = 6); // Error: the second parameter already has a // default argument (even if the values are the same) } void f(int = 1, int); // #3 OK, adds a default argument to #2 void n() { // new scope begins f(); // #1, #2, and #3 are in scope: calls f(1, 7); }
如果 inline 函数在不同的翻译单元中声明,则在每个翻译单元结束时,累积的默认实参集合必须相同。
如果非内联函数在不同翻译单元的同一命名空间作用域中声明,则相应的默认实参如果存在则必须相同(但某些默认实参可能在某些 TU 中不存在)。 |
(自 C++20 起) |
如果 friend 声明指定了默认实参,则它必须是友元函数定义,并且在此翻译单元中不允许对此函数的其他声明。
using 声明 携带已知的默认实参集合,如果稍后在函数的命名空间中添加了更多默认实参,则这些默认实参在 using 声明可见的任何地方也可见
namespace N { void f(int, int = 1); } using N::f; void g() { f(7); // calls f(7, 1); f(); // error } namespace N { void f(int = 2, int); } void h() { f(); // calls f(2, 1); }
默认实参中使用的名称在声明点查找、检查可访问性并绑定,但在函数调用点执行
int a = 1; int f(int); int g(int x = f(a)); // lookup for f finds ::f, lookup for a finds ::a // the value of ::a, which is 1 at this point, is not used void h() { a = 2; // changes the value of ::a { int a = 3; g(); // calls f(2), then calls g() with the result } }
对于非模板类的成员函数,默认实参允许在类外部定义中出现,并与类主体内部声明提供的默认实参组合。如果这些类外部默认实参会将成员函数变成默认构造函数或复制/移动(自 C++11 起)构造函数/赋值运算符(这会使调用不明确),则程序是非良构的。对于模板类的成员函数,所有默认实参都必须在成员函数的初始声明中提供。
class C { void f(int i = 3); void g(int i, int j = 99); C(int arg); // non-default constructor }; void C::f(int i = 3) {} // error: default argument already // specified in class scope void C::g(int i = 88, int j) {} // OK: in this translation unit, // C::g can be called with no argument C::C(int arg = 1) {} // Error: turns this into a default constructor
虚函数的重写器不会从基类声明中获取默认实参,并且在进行虚函数调用时,默认实参会根据对象的静态类型来决定(注意:这可以使用 非虚接口 模式来避免)。
struct Base { virtual void f(int a = 7); }; struct Derived : Base { void f(int a) override; }; void m() { Derived d; Base& b = d; b.f(); // OK: calls Derived::f(7) d.f(); // Error: no default argument }
局部变量不允许在默认实参中使用,除非它们是未求值的
void f() { int n = 1; extern void g(int x = n); // error: local variable cannot be a default argument extern void h(int x = sizeof n); // OK as of CWG 2082 }
this 指针不允许在默认实参中使用
class A { void f(A* p = this) {} // error: this is not allowed };
非静态类成员不允许在默认实参中使用(即使它们未被求值),除非用于形成指向成员的指针或在成员访问表达式中使用
int b; class X { int a; int mem1(int i = a); // error: non-static member cannot be used int mem2(int i = b); // OK: lookup finds X::b, the static member int mem3(int X::* i = &X::a); // OK: non-static member can be used int mem4(int i = x.a); // OK: in a member access expression static X x; static int b; };
每次调用函数时,如果对应的形参没有实参,则会求值默认实参。函数形参不允许在默认实参中使用,除非它们是未求值的。请注意,在形参列表中较早出现的形参在作用域中
int a; int f(int a, int b = a); // Error: the parameter a used in a default argument int g(int a, int b = sizeof a); // Error until resolving CWG 2082 // OK after resolution: use in unevaluated context is OK
默认实参不是函数类型的一部分
int f(int = 0); void h() { int j = f(1); int k = f(); // calls f(0); } int (*p1)(int) = &f; int (*p2)() = &f; // Error: the type of f is int(int)
除函数调用运算符 和 下标运算符(自 C++23 起) 之外的运算符函数不能具有默认实参
class C { int operator++(int i = 0); // ill-formed int operator[](int j = 0); // OK since C++23 int operator()(int k = 0); // OK };
显式对象形参不能具有默认实参 struct S { void f(this const S& = S{}); }; // ill-formed |
(自 C++23 起) |
[编辑] 注意
如果形参名称缺失,则可能需要空格以避免复合赋值标记(参见 最大匹配原则)。
void f1(int*=0); // Error, “*=” is unexpected here void g1(const int&=0); // Error, “&=” is unexpected here void f2(int* = 0); // OK void g2(const int& = 0); // OK void h(int&&=0); // OK even without spaces, “&&” is a token here
[编辑] 缺陷报告
以下行为变更缺陷报告被追溯应用于先前发布的 C++ 标准。
DR | 应用于 | 已发布行为 | 正确行为 |
---|---|---|---|
CWG 217 | C++98 | 默认实参可以添加到类的非 模板成员函数 |
禁止 |
CWG 1344 | C++98 | 在成员函数的类外部定义中添加的默认实参可能会将其更改为特殊成员函数 成员函数可能变为特殊成员函数 |
禁止 |
CWG 1716 | C++98 | 即使调用者提供了实参,默认实参也会在每次调用函数时求值 被调用,即使调用者提供了参数 |
仅在没有为 对应的形参提供 实参时才求值 |
CWG 2082 | C++98 | 默认实参被禁止在未求值上下文中使用局部变量 和前面的形参 |
未求值上下文 允许使用 |
CWG 2233 | C++11 | 从形参包展开的形参可能不会出现在具有默认实参的形参之后 不会出现在带有默认参数的参数之后 |
允许 |
CWG 2683 | C++98 | 类模板的嵌套类的成员函数的类外部定义可能具有默认实参 类模板嵌套类的成员函数的类外定义可能具有默认参数 |
禁止 |