Аргументы по умолчанию
Позволяет вызывать функцию без предоставления одного или нескольких завершающих аргументов.
Обозначается использованием следующего синтаксиса для списка-параметров объявления функции.
атрибуты (необязательно) последовательность-спецификаторов-объявления декларатор = инициализатор
|
(1) | ||||||||
атрибуты (необязательно) последовательность-спецификаторов-объявления абстрактный-декларатор (необязательно) = инициализатор
|
(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); // OK: аргумент по умолчанию `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; создаёт объявление 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
Аргументы по умолчанию разрешены только в списках параметров объявлений функций и лямбда-выражений, (начиная с C++11) и не разрешены в объявлениях указателей на функции, ссылках на функции или в объявлениях typedef. Списки параметров шаблона используют аналогичный синтаксис для своих нет названия раздела.
Для функций, не являющихся шаблонами, аргументы по умолчанию могут быть добавлены к уже объявленной функции, если функция переобъявлена в той же области видимости. В момент вызова функции аргументы по умолчанию представляют собой объединение аргументов по умолчанию, представленных во всех видимых объявлениях функции. Повторное объявление не может ввести аргумент по умолчанию для параметра, для которого аргумент по умолчанию уже виден (даже если значение то же самое). Повторное объявление во внутренней области видимости не получает аргументы по умолчанию из внешних областей видимости.
void f(int, int); // #1 void f(int, int = 7); // #2 OK: добавляет аргумент по умолчанию 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); // OK: вызов f(4,6); void f(int, int = 6); // Ошибка: второй параметр уже имеет аргумент по умолчанию // (даже если значения одинаковы) } void f(int = 1, int); // #3 OK, добавляет аргумент по умолчанию к #2 void n() { // начинается новая область видимости f(); // #1, #2, и #3 находятся в области видимости: вызывается f(1, 7); }
Если встраиваемая функция объявлена в разных единицах трансляции, накопленные наборы аргументов по умолчанию должны быть одинаковыми в конце каждой единицы трансляции.
Если невстроенная функция объявлена в одной и той же области видимости пространства имён в разных единицах трансляции, соответствующие аргументы по умолчанию должны быть одинаковыми, если они присутствуют (но некоторые аргументы по умолчанию могут отсутствовать в некоторых единицах трансляции). |
(начиная с C++20) |
Если в объявлении friend указан аргумент по умолчанию, это должно быть определение дружественной функции, и никакие другие объявления этой функции не допускаются в единице трансляции.
нет названия раздела переносят набор известных аргументов по умолчанию, и если позже в пространство имён функции будет добавлено больше аргументов по умолчанию, эти аргументы по умолчанию также будут видны везде, где видно 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) {} // OK: в этой единице трансляции // 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(); // OK: вызов Derived::f(7) d.f(); // Ошибка: нет аргумента по умолчанию }
Локальные переменные не допускаются в аргументах по умолчанию, если только они не являются не оцениваемыми:
void f() { int n = 1; extern void g(int x = n); // ошибка: локальная переменная не может быть // аргументом по умолчанию extern void h(int x = sizeof n); // OK с CWG 2082 }
Указатель this не разрешён в аргументах по умолчанию:
class A { void f(A* p = this) {} // ошибка: это не разрешено };
Нестатические элементы класса не допускаются в аргументах по умолчанию (даже если они не оцениваются), за исключением случаев, когда они используются для формирования указателя на элемент или в выражении доступа к элементу:
int b; class X { int a; int mem1(int i = a); // ошибка: нельзя использовать нестатический элемент int mem2(int i = b); // OK: поиск находит X::b, статический элемент int mem3(int X::* i = &X::a); // OK: можно использовать нестатический элемент int mem4(int i = x.a); // OK: в выражении доступа к элементу 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)
Операторные функции не могут иметь аргументов по умолчанию, за исключением оператора вызова функции:
class C { int operator[](int i = 0); // некорректно int operator()(int x = 0); // OK };
no section name не могут иметь аргументы по умолчанию: struct S { void f(this const S& = S{}); }; // некорректно |
(начиная с C++23) |
[править] Примечание
Пробелы могут быть необходимы, чтобы избежать составного маркера присваивания, если имя параметра отсутствует.
void f1(int*=0); // Ошибка, '*=' здесь не ожижается void g1(const int&=0); // Ошибка, '&=' здесь не ожижается void f2(int* = 0); // OK void g2(const int& = 0); // OK 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 | определения внеклассовых функций-элементов вложенных шаблонных классов могут иметь аргументы по умолчанию |
запрещено |