오버로드된 함수의 주소

오버로드된 함수의 주소 (Address of an overloaded function)

오버로드된 함수 이름을 그냥 호출하지 않고 함수 포인터·참조에 담거나 인자로 넘기려면, 그 오버로드 집합 중 어느 함수를 가리키는 것인지 컴파일러가 정해야 해요. 목표가 되는 타입이 있으면 그 타입이 오버로드 집합을 하나로 좁혀 줘요.

함수 호출 표현식(여기서 오버로드 해석이 일어나요) 외에도, 오버로드된 함수의 이름은 다음 7가지 문맥에서 나타날 수 있어요.

문맥 대상
객체나 참조선언에서 초기화자 초기화되는 객체나 참조
대입 표현식의 오른쪽 대입의 왼쪽
함수 호출 인자 함수 매개변수
사용자 정의 연산자 인자 연산자 매개변수
return 함수나 변환의 반환 값
명시적 캐스트static_cast 인자 대응하는 캐스트
비타입 템플릿 인자 대응하는 템플릿 매개변수

각 문맥에서 오버로드된 함수의 이름 앞에는 주소 연산자 &가 올 수 있고, 불필요한 괄호로 둘러쌀 수도 있어요.

대상 타입에 자리표시자 타입이 있으면 자리표시자 타입 추론이 수행되고, 아래 설명은 추론된 타입을 대상 타입으로 사용해요. (C++26부터)

출처: cppreference

본문

함수 선택(Selecting functions)

오버로드된 함수의 주소를 취하면, 오버로드 함수의 이름이 가리키는 오버로드 집합에서 함수 집합 S가 선택돼요.

  • 대상이 없으면, 이름 붙은 모든 비템플릿 함수가 선택돼요.
  • 그 외에는, 타입 F의 비템플릿 함수가 대상 타입의 함수 타입 FT에 대해, (가능하면 함수 포인터 변환을 적용한 후, C++17부터) FFT와 동일하면 선택돼요.[^1]
  • 이름 붙은 각 함수 템플릿에 대해 템플릿 인자 추론이 생성한 특수화(있으면)도 S에 추가돼요.

대상이 함수 포인터 타입이나 함수에 대한 참조 타입이면, S는 비멤버 함수, (C++23부터) 명시적 객체 멤버 함수, 정적 멤버 함수만 포함할 수 있어요. 대상이 멤버 함수 포인터 타입이면, S는 암시적 객체 멤버 함수만 포함할 수 있어요.

[^1]: 다시 말해, 대상 타입이 멤버 함수 포인터 타입이면 그 함수가 멤버인 클래스는 무시돼요.

함수 제거(Eliminating functions)

집합 S를 만든 후 함수들을 다음 순서로 제거해요.

- 충족되지 않는 제약(constraints)이 연관된 모든 함수가 S에서 제거돼요. (C++20부터)
  • S에 함수가 둘 이상 남으면, S에 비템플릿 함수도 포함돼 있을 때 S의 모든 함수 템플릿 특수화가 제거돼요.
- 주어진 비템플릿 함수 func는, Sfunc보다 부분 순서에서 더 제약된 두 번째 비템플릿 함수가 있으면 제거돼요. (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 목록 초기화가 오버로드된 함수의 주소를 취하는 문맥인지 불명확했음 명확해짐

더 알아보기