Namespaces
Variants

Default 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

允许在不提供一个或多个尾部参数的情况下调用函数。

通过在 函数声明 参数列表 中对参数使用以下语法来表示。

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); // 调用 point(1, 2)
point(1);    // 调用 point(1, 4)
point();     // 调用 point(3, 4)

在函数声明中,若某个参数带有默认实参,则其后的所有参数必须:

  • 在此或同一作用域的前一个声明中提供了默认参数:
int x(int = 1, int); // 错误:只有末尾参数才能拥有默认实参
                     //        (假设不存在“x”的前置声明)
void f(int n, int k = 1);
void f(int n = 0, int k); // 正确:参数“k”的默认实参由
                          // 同一作用域中的前置声明提供
void g(int, int = 7);
void h()
{
    void g(int = 1, int); // 错误:不在同一作用域
}
  • ...除非参数是从参数包展开的:
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
(since C++11)

省略号不是参数,因此可以跟在带有默认参数的参数后面:

int g(int n = 0, ...); // 正确

默认实参仅允许出现在 函数声明 lambda表达式 的参数列表中, (C++11 起) 不允许出现在函数指针声明、函数引用声明或 typedef 声明中。模板参数列表使用类似语法表示其 默认模板实参

对于非模板函数,如果函数在同一作用域内被重新声明,则可以为已声明的函数添加默认参数。在函数调用点,默认参数是所有可见声明中提供的默认参数的并集。重新声明不能为已存在可见默认参数的形参引入默认参数(即使值相同)。内部作用域中的重新声明不会从外部作用域获取默认参数。

void f(int, int);     // #1
void f(int, int = 7); // #2 正确:添加了默认参数
void h()
{
    f(3); // #1 和 #2 在作用域内;调用 f(3,7)
    void f(int = 1, int); // 错误:第二个参数的默认参数
                          // 不会从外部作用域获取
}
void m()
{ // 新作用域开始
    void f(int, int); // 内部作用域声明;无默认参数
    f(4); // 错误:调用 f(int, int) 参数不足
    void f(int, int = 6);
    f(4); // 正确:调用 f(4, 6)
    void f(int, int = 6); // 错误:第二个参数已存在
                          // 默认参数(即使参数值相同)
}
void f(int = 1, int); // #3 正确,为 #2 添加默认参数
void n()
{ // 新作用域开始
    f(); // #1、#2 和 #3 在作用域内:调用 f(1, 7)
}

如果 inline 函数在不同翻译单元中声明,每个翻译单元末尾的默认参数集合必须保持一致。

如果同一命名空间作用域中的非内联函数在不同翻译单元中被声明,若存在对应的默认参数,则这些默认参数必须保持一致(但某些翻译单元中可以缺少部分默认参数)。

(since C++20)

如果 friend 声明指定了默认参数,则它必须是友元函数定义,且该翻译单元中不允许存在此函数的其他声明。

using 声明 会继承已知的默认参数集合,如果后续在函数所属命名空间中添加了更多默认参数,这些默认参数在使用该 using 声明的任何位置也都可见:

namespace N
{
    void f(int, int = 1);
}
using N::f;
void g()
{
    f(7); // 调用 f(7, 1);
    f();  // 错误
}
namespace N
{
    void f(int = 2, int);
}
void h()
{
    f();  // 调用 f(2, 1);
}

默认参数中使用的名称会在声明点进行查找、检查 可访问性 并完成绑定,但实际执行发生在函数调用点:

int a = 1;
int f(int);
int g(int x = f(a)); // 对 f 的查找找到 ::f,对 a 的查找找到 ::a
                     // 此时 ::a 的值为 1,但未被使用
void h()
{
    a = 2; // 修改 ::a 的值
    {
        int a = 3;
        g(); // 调用 f(2),随后以结果调用 g()
    }
}

对于 成员函数 属于非 模板化 类的情况,允许在类外定义中提供默认参数,这些参数将与类体内声明提供的默认参数合并。如果这些类外默认参数会将成员函数转变为默认构造函数或复制 /移动 (C++11 起) 构造函数/赋值运算符(这将导致调用歧义),则程序非良构。对于模板化类的成员函数,所有默认参数必须在成员函数的初始声明中提供。

class C
{
    void f(int i = 3);
    void g(int i, int j = 99);
    C(int arg); // 非默认构造函数
};
void C::f(int i = 3) {}         // 错误:类作用域中已指定默认实参
void C::g(int i = 88, int j) {} // 正确:在此翻译单元中,
                                // C::g 可不带实参调用
C::C(int arg = 1) {}            // 错误:这将使其成为默认构造函数

虚函数的覆写者不会从基类声明中获取默认参数,当进行虚函数调用时,默认参数将根据对象的静态类型确定(注意:可通过 非虚接口模式 避免此行为)。

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(); // 正确:调用 Derived::f(7)
    d.f(); // 错误:无默认参数
}

局部变量不允许在默认参数中使用,除非它们是 不被求值 的:

void f()
{
    int n = 1;
    extern void g(int x = n); // 错误:局部变量不能作为默认参数
    extern void h(int x = sizeof n); // 根据CWG 2082提案允许使用
}

this 指针不允许在默认参数中使用:

class A
{
    void f(A* p = this) {} // 错误:不允许使用 this
};

非静态类成员不允许在默认参数中使用(即使它们未被求值),除非用于形成指向成员的指针或在成员访问表达式中使用:

int b;
class X
{
    int a;
    int mem1(int i = a); // 错误:非静态成员不能使用
    int mem2(int i = b); // 正确:查找找到 X::b,即静态成员
    int mem3(int X::* i = &X::a); // 正确:非静态成员可以使用
    int mem4(int i = x.a); // 正确:在成员访问表达式中
    static X x;
    static int b;
};

默认实参在每次调用函数且未提供对应参数时都会被求值。函数参数不允许出现在默认实参中,除非它们 不被求值 。注意:在参数列表中较早出现的参数处于 作用域 内:

int a;
int f(int a, int b = a); // 错误:在默认参数中使用了参数a
int g(int a, int b = sizeof a); // 在CWG 2082决议前是错误用法
                                // 决议后正确:在未求值上下文中的使用是允许的

默认参数不属于函数类型:

int f(int = 0);
void h()
{
    int j = f(1);
    int k = f(); // 调用 f(0);
}
int (*p1)(int) = &f;
int (*p2)()    = &f; // 错误:f 的类型是 int(int)

函数调用运算符 下标运算符 (自 C++23 起) 外的运算符函数不能具有默认参数:

class C
{
    int operator++(int i = 0); // 非良构
    int operator[](int j = 0); // 自 C++23 起合法
    int operator()(int k = 0); // 合法
};

显式对象形参 不能拥有默认实参:

struct S { void f(this const S& = S{}); }; // ill-formed
(since C++23)

说明

当参数名称缺失时,可能需要使用空格来避免形成复合赋值标记(参见 最大吞噬原则 )。

void f1(int*=0);         // 错误,此处“*=”不符合预期
void g1(const int&=0);   // 错误,此处“&=”不符合预期
void f2(int* = 0);       // 正确
void g2(const int& = 0); // 正确
void h(int&&=0);         // 即使没有空格也正确,此处“&&”是一个独立标记

缺陷报告

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

缺陷报告 适用标准 发布时行为 正确行为
CWG 217 C++98 可以向类模板的非模板成员函数添加默认参数 禁止此操作
CWG 1344 C++98 在成员函数类外定义中添加的默认参数可能将其变为特殊成员函数 禁止此操作
CWG 1716 C++98 每次调用函数时都会对默认参数求值,即使调用者已提供实参 仅当未提供对应形参的实参时才进行求值
CWG 2082 C++98 禁止在未求值上下文中使用局部变量和前序形参作为默认参数 允许未求值上下文的使用
CWG 2233 C++11 从参数包展开的形参不能出现在带有默认参数的形参之后 允许此用法
CWG 2683 C++98 类模板嵌套类的成员函数的类外定义可以具有默认参数 禁止此操作