← 문제로

12. 오버로드 해석과 암시적 변환 순위, ADL

난이도 최상
내 리뷰 · C++
해설 · C++

해설 — 오버로드 해석과 암시적 변환 순위, ADL

난이도: 최상

요약

컴파일러는 각 호출에서 ① 후보 함수를 모으고(이름 탐색 + ADL) ② 각 인자의 암시적 변환 순서를 매겨 ③ "모든 인자에서 더 나쁘지 않고 적어도 하나에서 더 나은" 유일한 최적 후보를 고른다. 동률이면 비템플릿이 템플릿을 이긴다. 실행 출력은 다음과 같다:

f(int)
f(double)
g(int)
g(void*)
h(bool)
k(int) non-template
k(T) template
game::emit(Vec) via ADL

핵심 개념 — 변환 순위

좋은 것부터: 정확한 일치(Exact) > 승격(Promotion) > 표준 변환(Conversion) > 사용자 정의 변환(User-defined) > 말줄임표(...). 오버로드 해석은 이 순위로 각 인자의 "암시적 변환 순서"를 비교한다.

단계별 내부 동작

  • (1) f(42)f(int): 42int. f(int)는 정확한 일치, f(double)은 int→double 표준 변환. 정확한 일치가 이긴다.
  • (2) f(3.14f)f(double): 인자는 float. float→double부동소수점 승격(promotion), float→int는 표준 변환(게다가 손실). 승격이 변환보다 위라 f(double).
  • (3) g(0)g(int): 0int. g(int) 정확한 일치. g(void*)0을 널 포인터 상수로 본 포인터 변환(표준 변환). 정확한 일치 승.
  • (4) g(nullptr)g(void*): nullptr의 타입은 std::nullptr_t. nullptr_t→void*는 허용되는 포인터 변환이지만, nullptr_t→int는 불가(정수로 못 바꾼다). 그래서 g(int)는 후보에서 탈락하고 g(void*)만 남는다. — 0nullptr이 다르게 동작하는 핵심 이유.
  • (5) h("hello")h(bool) ⚠️ 함정: "hello"const char[6]이고 배열-포인터 변환으로 const char*가 된다. 후보는
    • h(bool): const char*→bool포인터→bool 표준 변환(boolean conversion).
    • h(const std::string&): const char*→std::stringstd::string 생성자를 통한 사용자 정의 변환. 표준 변환이 사용자 정의 변환보다 이므로 h(bool)이 선택된다. 의도와 정반대(문자열이 true로!). std::string 오버로드를 부르려면 h(std::string("hello")) 또는 h("hello"s).
  • (6) k(42)k(int) 비템플릿: k(int)(정확)와 k<int>(int)(정확) 둘 다 정확한 일치. 변환 순위가 같으면 비템플릿이 템플릿보다 우선한다.
  • (7) k(42.0)k(T) 템플릿: 인자는 double. k(int)double→int 변환(손실), k<double>(double)정확한 일치. 정확한 일치가 변환을 이기므로 이번엔 템플릿 승. (비템플릿 우선 규칙은 "변환 순위가 같을 때"만 적용된다.)
  • (8) emit(v) → ADL: emit는 호출 지점 스코프에 선언이 없지만, 인자 v의 타입 game::Vec이 속한 네임스페이스 game인자 의존 탐색(ADL/Koenig lookup) 으로 함께 뒤져 game::emit을 찾는다. std::cout << x, swap(a,b), begin(c)가 한정 없이 동작하는 것과 같은 메커니즘.

흔한 오해·함정

  • "std::string 오버로드가 있으니 문자열 리터럴은 거기로 가겠지" → 아니다. const char*→bool(표준)이 →std::string(사용자 정의)을 이긴다. C++11에서 std::string 직접 받는 함수에 리터럴을 넘길 때 bool 오버로드가 있으면 사고. (이래서 단일 인자 생성자에 explicit, 그리고 위험한 bool 오버로드 회피가 권장된다.)
  • "NULLnullptr은 같다" → NULL은 보통 0(정수)이라 정수 오버로드로 샐 수 있다. nullptrnullptr_t라 포인터 오버로드로만 간다.
  • "템플릿이 더 구체적이니 우선" → 변환 순위가 우선이고, 동률일 때만 비템플릿 우선. 부분 순서(더 특수한 템플릿)는 그 다음 단계.
  • ADL은 편의이자 함정: 의도치 않은 네임스페이스의 함수가 후보에 끼어들 수 있다(특히 std).

면접 포인트

  • 변환 순위(Exact > Promotion > Conversion > User-defined)를 예로 들어 설명하고, h("hello")가 왜 bool로 가는지 말할 수 있는가.
  • 0 vs nullptr의 오버로드 차이와 그 이유(nullptr_t는 정수로 변환 불가).
  • 비템플릿 우선 규칙이 "변환 순위가 같을 때만" 적용된다는 점(k(42) vs k(42.0)).
  • ADL이 무엇이며 swap/operator<<가 왜 한정 없이 동작하는지.