기본 인자
기본 인자 (Default arguments)
함수를 호출할 때, 뒤쪽에 있는 인자 하나 이상을 생략해도 되게 만들어 주는 게 기본 인자(default argument)예요. 함수 선언의 매개변수 목록에서 특정 매개변수에 = 초기화식을 붙이면, 호출 쪽에서 그 자리에 인자를 안 넘겨줘도 그 값이 대신 들어가요.
출처: cppreference
본문
문법 (Syntax)
함수 선언의 매개변수 목록에서 다음 형태로 표기해요.
| 표기 | 의미 |
|---|---|
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); // OK: "k"의 기본 인자는 같은 스코프의
// 이전 선언이 제공
void g(int, int = 7);
void h()
{
void g(int = 1, int); // 오류: 같은 스코프가 아님
}
- 예외 (C++11부터): 매개변수가 매개변수 팩(parameter pack)에서 확장됐거나 함수 매개변수 팩이라면 이 규칙이 적용되지 않아요.
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
- 줄임표(
...)는 매개변수가 아니므로, 기본 인자가 있는 매개변수 뒤에 와도 돼요.
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 선언과 기본 인자
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) 호출
}
기본 인자의 이름 해석 시점
기본 인자에 쓰인 이름들은 선언하는 지점에서 이름을 찾고(lookup), 접근성을 검사하고, 바인딩돼요. 다만 그 값은 함수 호출 지점에서 평가돼요.
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()를 호출
}
}
멤버 함수와 기본 인자
템플릿이 아닌 클래스의 멤버 함수는 클래스 밖 정의(out-of-class definition)에서도 기본 인자를 가질 수 있고, 클래스 본문 안의 선언이 제공한 기본 인자와 합쳐져요. 만약 이런 밖에서의 기본 인자 때문에 멤버 함수가 기본 생성자나 복사/이동(C++11부터) 생성자·대입 연산자로 바뀌어서(호출이 모호해져서) 버리면, 그 프로그램은 ill-formed예요. 템플릿 클래스의 멤버 함수는 모든 기본 인자를 멤버 함수의 처음 선언에서 제공해야 해요.
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) {} // 오류: 이걸 기본 생성자로 바꿔 버림
가상 함수와 기본 인자
가상 함수를 재정의(override)하는 함수는 기반 클래스 선언의 기본 인자를 물려받지 않아요. 그리고 가상 함수 호출을 할 때 기본 인자는 객체의 정적 타입(static type) 을 기준으로 결정돼요. (참고로 NVI 패턴, 즉 비가상 인터페이스 패턴으로 이 문제를 피할 수 있어요.)
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); // CWG 2082 이후로 OK
}
this포인터는 기본 인자에 쓸 수 없어요.
class A
{
void f(A* p = this) {} // 오류: this는 허용되지 않음
};
- 비정적 클래스 멤버는 (평가되지 않아도) 기본 인자에 쓸 수 없어요. 단 멤버에 대한 포인터(pointer-to-member)를 만드는 데 쓰거나, 멤버 접근 표현식 안에 쓰거나, (C++26부터) 반영 연산자
^^의 피연산자로 쓸 때는 예외예요.
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: 멤버 접근 표현식 안
consteval int mem5(std::meta::info r = ^^a); // C++26부터 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 해결 전까지는 오류
// 해결 후: 평가되지 않는 문맥에서의 사용은 OK
기본 인자는 함수 타입의 일부가 아니다
기본 인자는 함수의 타입에 포함되지 않아요. 그래서 함수 포인터 타입은 기본 인자를 반영하지 않아요.
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); // ill-formed
int operator[](int j = 0); // C++23부터 OK
int operator()(int k = 0); // OK
};
(C++23부터) 명시적 객체 매개변수(explicit object parameter) 는 기본 인자를 가질 수 없어요.
struct S { void f(this const S& = S{}); }; // ill-formed
참고 (Note)
매개변수 이름이 없을 때, 합성 대입 토큰(compound assignment token)과 겹치지 않도록 공백이 필요할 수 있어요(maximal munch 규칙 때문).
void f1(int*=0); // 오류: 여기서 "*="는 예상하지 못한 토큰
void g1(const int&=0); // 오류: 여기서 "&="는 예상하지 못한 토큰
void f2(int* = 0); // OK
void g2(const int& = 0); // OK
void h(int&&=0); // 공백 없이도 OK, 여기서 "&&"는 하나의 토큰
결함 보고 (Defect reports)
다음의 동작을 바꾸는 결함 보고(DR)가 이전에 공표된 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 | 클래스 템플릿의 중첩 클래스 멤버 함수의 클래스 밖 정의가 기본 인자를 가질 수 있었음 | 금지 |