오버로드된 함수의 주소
오버로드된 함수의 주소 (Address of an overloaded function)
오버로드된 함수 이름을 그냥 호출하지 않고 함수 포인터·참조에 담거나 인자로 넘기려면, 그 오버로드 집합 중 어느 함수를 가리키는 것인지 컴파일러가 정해야 해요. 목표가 되는 타입이 있으면 그 타입이 오버로드 집합을 하나로 좁혀 줘요.
함수 호출 표현식(여기서 오버로드 해석이 일어나요) 외에도, 오버로드된 함수의 이름은 다음 7가지 문맥에서 나타날 수 있어요.
| 문맥 | 대상 |
|---|---|
| 객체나 참조의 선언에서 초기화자 | 초기화되는 객체나 참조 |
| 대입 표현식의 오른쪽 | 대입의 왼쪽 |
| 함수 호출 인자 | 함수 매개변수 |
| 사용자 정의 연산자 인자 | 연산자 매개변수 |
return 문 |
함수나 변환의 반환 값 |
명시적 캐스트나 static_cast 인자 |
대응하는 캐스트 |
| 비타입 템플릿 인자 | 대응하는 템플릿 매개변수 |
각 문맥에서 오버로드된 함수의 이름 앞에는 주소 연산자 &가 올 수 있고, 불필요한 괄호로 둘러쌀 수도 있어요.
| 대상 타입에 자리표시자 타입이 있으면 자리표시자 타입 추론이 수행되고, 아래 설명은 추론된 타입을 대상 타입으로 사용해요. | (C++26부터) |
출처: cppreference
본문
함수 선택(Selecting functions)
오버로드된 함수의 주소를 취하면, 오버로드 함수의 이름이 가리키는 오버로드 집합에서 함수 집합 S가 선택돼요.
- 대상이 없으면, 이름 붙은 모든 비템플릿 함수가 선택돼요.
- 그 외에는, 타입
F의 비템플릿 함수가 대상 타입의 함수 타입FT에 대해, (가능하면 함수 포인터 변환을 적용한 후, C++17부터)F가FT와 동일하면 선택돼요.[^1] - 이름 붙은 각 함수 템플릿에 대해 템플릿 인자 추론이 생성한 특수화(있으면)도
S에 추가돼요.
대상이 함수 포인터 타입이나 함수에 대한 참조 타입이면, S는 비멤버 함수, (C++23부터) 명시적 객체 멤버 함수, 정적 멤버 함수만 포함할 수 있어요. 대상이 멤버 함수 포인터 타입이면, S는 암시적 객체 멤버 함수만 포함할 수 있어요.
[^1]: 다시 말해, 대상 타입이 멤버 함수 포인터 타입이면 그 함수가 멤버인 클래스는 무시돼요.
함수 제거(Eliminating functions)
집합 S를 만든 후 함수들을 다음 순서로 제거해요.
- 충족되지 않는 제약(constraints)이 연관된 모든 함수가 S에서 제거돼요. |
(C++20부터) |
S에 함수가 둘 이상 남으면,S에 비템플릿 함수도 포함돼 있을 때S의 모든 함수 템플릿 특수화가 제거돼요.
- 주어진 비템플릿 함수 func는, S에 func보다 부분 순서에서 더 제약된 두 번째 비템플릿 함수가 있으면 제거돼요. |
(C++20부터) |
- 주어진 함수 템플릿 특수화
spec는,S에 그 함수 템플릿이spec의 함수 템플릿보다 더 특수화된 두 번째 함수 템플릿 특수화가 있으면 제거돼요.
이런 제거(있으면) 후 S에는 정확히 하나의 선택된 함수가 남아야 해요. 그렇지 않으면 프로그램은 잘못된 형태(ill-formed)예요.
예제
다양한 문맥에서 오버로드가 어떻게 하나의 함수로 좁혀지는지 보여 주는 예시예요.
int f(int) { return 1; }
int f(double) { return 2; }
void g(int(&f1)(int), int(*f2)(double)) { f1(0); f2(0.0); }
template<int(*F)(int)>
struct Templ {};
struct Foo
{
int mf(int) { return 3; }
int mf(double) { return 4; }
};
struct Emp
{
void operator<<(int (*)(double)) {}
};
int main()
{
// 1. initialization
int (*pf)(double) = f; // selects int f(double)
int (&rf)(int) = f; // selects int f(int)
int (Foo::*mpf)(int) = &Foo::mf; // selects int mf(int)
// 2. assignment
pf = nullptr;
pf = &f; // selects int f(double)
// 3. function argument
g(f, f); // selects int f(int) for the 1st argument
// and int f(double) for the second
// 4. user-defined operator
Emp{} << f; //selects int f(double)
// 5. return value
auto foo = []() -> int (*)(int)
{
return f; // selects int f(int)
};
// 6. cast
auto p = static_cast<int(*)(int)>(f); // selects int f(int)
// 7. template argument
Templ<f> t; // selects int f(int)
// prevent “unused variable” warnings as if by [[maybe_unused]]
[](...){}(pf, rf, mpf, foo, p, t);
}
결함 보고(Defect reports)
이전에 발표된 C++ 표준들에 소급 적용된 동작 변경 결함 보고들이 있어요.
| DR | 적용 대상 | 발표 당시 동작 | 올바른 동작 |
|---|---|---|---|
| CWG 202 | C++98 | 비타입 템플릿 인자는 오버로드된 함수의 주소를 취하는 문맥이 아니었음 | 그렇다 |
| CWG 250 | C++98 | 추론되지 않은 템플릿 인자로 생성된 함수 템플릿 특수화가 오버로드 집합에서 선택되지 않았음 | 또한 선택됨 |
| CWG 1153 | C++98 | 주어진 함수 타입이 대상 타입과 일치하는지 불명확했음 | 명확해짐 |
| CWG 1563 | C++11 | 목록 초기화가 오버로드된 함수의 주소를 취하는 문맥인지 불명확했음 | 명확해짐 |
더 알아보기
- 오버로드 해석(Overload resolution): 함수 호출에서 어떤 오버로드를 쓸지 고르는 과정.
- 함수 템플릿 오버로딩(Function template overloading): 템플릿 특수화 간 부분 순서.
- 오버로드된 연산자 호출(Operator overloading)