본문 바로가기

함수 오버로딩

같은 이름의 함수를 매개변수 목록으로 구분하는 오버로딩 규칙과 컴파일러의 호출 후보 선택 과정을 이해합니다.

두 수를 더하는 함수를 정수용과 실수용으로 따로 만들면 addInt, addDouble처럼 이름이 늘어나고, 쓰는 사람은 타입마다 다른 이름을 기억해야 합니다. C++에서는 같은 이름의 함수를 매개변수만 다르게 여러 개 정의할 수 있으며, 이것을 함수 오버로딩(function overloading)이라고 합니다.

이 글은 어떤 함수들이 서로 다른 오버로드가 될 수 있는지, 컴파일러가 호출마다 어느 함수를 고르는지, 고를 수 없을 때 생기는 모호한 호출을 봅니다. 끝으로 오버로드한 함수가 링커에서 구별되는 이름 맹글링을 간단히 소개합니다.

오버로드를 구별하는 기준

같은 이름의 함수들은 매개변수 타입의 목록이 달라야 서로 다른 오버로드가 됩니다. 매개변수의 개수가 다르거나, 같은 자리의 타입이 다르거나, 서로 다른 타입의 순서가 바뀌면 목록이 달라집니다.

C++ 함수 오버로드를 구분하는 매개변수 타입 목록 규칙과 예측 가능한 오버로드 설계 기준

Identity before resolution

일반 함수의 새 오버로드는 조정된 매개변수 타입 목록으로 구분한다

새 오버로드가 되려면 조정된 매개변수 타입 목록이 달라야 합니다. 호출문에서 보이지 않는 매개변수 이름이나 반환 타입만으로는 일반 함수를 구분할 수 없습니다.

  1. 개수 차이 · 가능

    add(int, int)와 add(int, int, int)는 매개변수 타입 목록의 길이가 다릅니다.

  2. 타입 차이 · 가능

    add(int, int)와 add(double, double)은 대응하는 매개변수 타입이 다릅니다.

  3. 서로 다른 타입의 순서 · 가능

    show(std::string, int)와 show(int, std::string)은 순서를 바꾼 조정된 매개변수 타입 목록이 다릅니다.

  4. 매개변수 이름만 · 불가

    show(int left)와 show(int value)처럼 이름만 바꿔도 함수 타입은 달라지지 않습니다.

  5. 값 매개변수의 최상위 cv · 불가

    read(int)와 read(const int)은 함수 타입을 만들 때 최상위 const가 제거되므로 같은 함수입니다.

  6. 배열·함수 매개변수 조정 · 불가

    scan(char[])과 scan(char*)은 같은 매개변수 타입입니다. using Callback = int(int);일 때 apply(Callback)과 apply(Callback*)도 같습니다. 배열·함수 타입 매개변수는 각각 대응하는 char*와 Callback*로 조정됩니다.

  7. 반환 타입만 · 불가

    int add(int, int)와 double add(int, int)처럼 같은 매개변수 타입 목록을 반환 타입만으로 나눌 수 없습니다.

같은 개념

이름은 하나의 의미 계열을 약속한다

정수·실수 덧셈처럼 같은 작업을 다른 입력 타입에 제공할 때 이름을 공유합니다. 문자열 결합도 API가 같은 “결합” 개념으로 정의할 때만 add 계열에 둡니다.

호출 겹침 점검

구분 가능해도 모호한 호출은 만들 수 있다

오버로드를 늘릴수록 후보 집합도 커집니다. 0, nullptr, 문자 리터럴의 변환 대상과 기본 인자를 함께 점검하고, 의미가 다르거나 호출 형태가 겹치면 별도 이름을 사용합니다.

설계 순서: 선언 identity를 먼저 확인한 뒤, 실제 호출마다 어떤 후보와 변환이 생기는지 별도로 검토합니다.

위 그림의 아래쪽처럼 겉보기와 달리 같은 함수로 취급되는 경우도 있습니다. 매개변수 이름만 바꾼 것은 같은 함수이고, 반환 타입만 다른 것은 오버로드가 될 수 없어 컴파일 오류입니다. 호출문 add(1, 2)만 보고는 어느 반환 타입을 원하는지 알 수 없기 때문입니다.

not_an_overload.cpp
int add(int a, int b) { return a + b; }
// double add(int a, int b) { return a + b; } // 컴파일 오류: 반환 타입만 다름

void read(int value);
void read(const int value); // 같은 함수의 선언

void scan(char text[]);
void scan(char* text);      // 같은 함수의 선언

값 매개변수의 맨 바깥 const는 함수 타입을 정할 때 빠지므로 read(int)와 read(const int)는 같은 함수입니다. 매개변수의 배열 타입은 1차원 배열에서 본 대로 포인터로 바뀌므로 scan(char[])과 scan(char*)도 같습니다. 반면 참조자에서 볼 int&와 const int&처럼 참조하는 대상의 타입이 다르면 서로 다른 오버로드가 됩니다.

오버로딩 예

정수 덧셈, 실수 덧셈, 세 정수의 덧셈, 문자열 이어 붙이기를 모두 add라는 이름으로 정의해 봅니다. 네 정의는 이름이 같고 매개변수의 개수나 타입만 다릅니다. 반환 타입도 서로 다르지만 반환 타입만 다른 두 함수는 오버로딩으로 인정되지 않으므로, 함수를 구별하는 것은 매개변수 쪽입니다.

overload_add.cpp
#include <iostream>
#include <string>

int add(int a, int b) { return a + b; }
double add(double a, double b) { return a + b; }
int add(int a, int b, int c) { return a + b + c; }
std::string add(const std::string& a, const std::string& b) { return a + b; }

int main() {
    std::cout << add(5, 7) << '\n';
    std::cout << add(3.5, 2.25) << '\n';
    std::cout << add(1, 2, 3) << '\n';
    std::cout << add("Hello, ", "World") << '\n';
}
12
5.75
6
Hello, World

컴파일러는 호출마다 인자의 개수와 타입을 보고 알맞은 함수를 고릅니다. add(5, 7)은 두 int가 정확히 맞는 첫째 함수를, add(3.5, 2.25)는 둘째 함수를, 인자가 셋인 add(1, 2, 3)은 셋째 함수를 부릅니다. 이렇게 호출에 맞는 함수를 고르는 과정을 오버로드 해석(overload resolution)이라고 하며, 실행 중이 아니라 컴파일할 때 이루어집니다.

마지막 호출의 "Hello, "는 std::string이 아니라 char 배열입니다. 배열은 포인터로 바뀐 뒤 std::string의 생성자로 변환되어야 넷째 함수에 넘길 수 있는데, 이처럼 클래스가 정의한 변환을 사용자 정의 변환(user-defined conversion)이라고 합니다. 다른 세 함수에는 포인터를 넘길 방법이 없으므로 넷째 함수가 선택됩니다.

변환의 순위

여러 함수가 모두 호출 가능할 때, 컴파일러는 인자마다 필요한 변환의 순위를 비교합니다. 순위는 타입이 그대로 맞는 정확한 일치(exact match)가 가장 좋고, 그다음이 승격(promotion), 그다음 그 밖의 표준 변환(conversion), 마지막이 사용자 정의 변환입니다. 승격에는 형변환에서 본 작은 정수를 int로 넓히는 것과 함께 float를 double로 넓히는 것도 들어갑니다.

conversion_rank.cpp
#include <iostream>

void print(int value) { std::cout << "int " << value << '\n'; }
void print(double value) { std::cout << "double " << value << '\n'; }

int main() {
    print(10);
    print(10.0);
    print(true);
    print('A');
    print(2.5f);
    // print(10L); // 컴파일 오류: 모호한 호출
}
int 10
double 10
int 1
int 65
double 2.5

true와 'A'는 int로의 승격이 double로의 변환보다 순위가 높아 print(int)를 고르고, 2.5f는 double로의 승격이 있어 print(double)을 고릅니다. 'A'가 65로 출력되는 것은 GCC가 쓰는 UTF-8에서 A의 값이 65이기 때문입니다. 반면 10L은 long에서 int로 가는 것과 double로 가는 것이 모두 같은 순위의 변환이어서, 어느 쪽도 더 낫지 않아 컴파일 오류가 납니다.

이 순위 때문에 생기는 함정도 있습니다. show(bool)과 show(const std::string&)가 함께 있으면 show("hello")는 문자열 버전이 아니라 bool 버전을 부릅니다. 포인터를 bool로 바꾸는 것은 표준 변환이고, std::string을 만드는 것은 그보다 순위가 낮은 사용자 정의 변환이기 때문입니다. 문자열을 받는 오버로드를 만들 때는 show(const char*)도 함께 두거나, 인자를 std::string으로 넘깁니다.

모호한 호출

인자가 여러 개이면 비교가 조금 더 엄격해집니다. 한 후보가 선택되려면 모든 인자에서 다른 후보보다 나쁘지 않고, 적어도 한 인자에서는 더 좋아야 합니다. 변환의 개수나 비용을 더해서 고르는 것이 아닙니다.

choose.cpp
#include <iostream>

void choose(int, double) { std::cout << "A\n"; }
void choose(double, int) { std::cout << "B\n"; }

int main() {
    choose(1, 2.0);
    choose(1.0, 2);
    // choose(1, 2); // 컴파일 오류: 모호한 호출
}
A
B

choose(1, 2.0)에서 A는 두 인자가 모두 정확히 맞고 B는 둘 다 변환이 필요하므로 A가 선택되며, choose(1.0, 2)는 반대로 B입니다. 아래 그림은 주석으로 막아 둔 choose(1, 2)를 따라갑니다. 두 후보 모두 호출할 수는 있지만, A는 첫째 인자에서, B는 둘째 인자에서 더 좋아 우열이 엇갈립니다.

C++ 오버로드 해석 흐름과 호출 판독

이름 조회 후보→호출 가능 후보→제공된 각 인자의 변환 비교→유일 best/오류는 의미상 후보를 좁히는 순서다. 후보별 viability와 변환 순위 장부를 실제 호출 하나에 적용하며 컴파일러 내부 pass·변환 비용 합·실패 후 런타임 호출을 만들지 않는다.

C++ 오버로드 해석이 이름 조회 후보를 호출 가능한 함수로 좁히고 인자별 변환 순위를 비교해 유일한 최적 후보 또는 오류를 결정하는 과정

Lookup → viable → rank → best

인자별 변환을 비교해 유일한 최적 오버로드를 고른다

오버로드 해석은 변환 비용을 모두 더하는 계산이 아닙니다. 호출 지점에서 찾은 후보를 걸러낸 뒤 각 인자의 변환 순서를 비교하여 유일한 최적 후보를 찾아야 합니다.

호출 가능한 후보에서 유일한 최적 후보 또는 오류로 분기한다일곱노드와일곱간선이다. 이름조회→호출가능성→후보존재조건. 후보없음분기는오류로 우회한다. 후보있음은 인자별순위→유일best조건으로 진행한다. 유일best 예는선택함수로,아니요는오류로향한다. 오류노드의후보없음과모호함은서로다른실패이유다. 선택뒤삭제/접근성등후속검사를통과해야한다. 모바일도같은조건과우회관계다.이름 조회보이는 선언의 후보호출 가능성개수·변환·기본값후보가 있나요?없으면 호출 오류인자별 순위모두 나쁘지 않은가?유일한 best?동률 규칙까지 비교선택한 함수후속 적합성도 검사호출 오류후보 없음 / 모호함예예아니요아니요인자별 우열을 비교하며변환 비용 합으로 고르지 않음
호출 가능한 후보에서 유일한 최적 후보 또는 오류로 분기한다일곱노드와일곱간선이다. 이름조회→호출가능성→후보존재조건. 후보없음분기는오류로 우회한다. 후보있음은 인자별순위→유일best조건으로 진행한다. 유일best 예는선택함수로,아니요는오류로향한다. 오류노드의후보없음과모호함은서로다른실패이유다. 선택뒤삭제/접근성등후속검사를통과해야한다. 모바일도같은조건과우회관계다.이름 조회보이는 선언의 후보호출 가능성개수·변환·기본값후보가 있나요?없으면 호출 오류인자별 순위모두 나쁘지 않은가?유일한 best?동률 규칙까지 비교선택한 함수후속 적합성도 검사호출 오류후보 없음 / 모호함예예아니요아니요인자별 우열을 비교하며변환 비용 합으로 고르지 않음
해석 절차
  1. 이름 조회로 후보 집합을 만든다

    호출 지점에서 조회되는 같은 이름의 선언들이 후보가 됩니다. 프로그램 전체의 같은 이름을 무조건 모두 모으는 것은 아닙니다.

  2. 호출 가능한 함수만 남긴다

    인자 개수, 기본 인자, 각 인자에서 매개변수 타입으로 갈 수 있는 암시적 변환을 검사합니다.

  3. 각 인자의 변환 순위를 비교한다

    기본 표준 변환 순위는 exact match가 promotion보다, promotion이 conversion보다 좋고, 표준 변환 순서는 사용자 정의 변환 순서보다 우선합니다.

  4. 유일한 최적 후보를 결정한다

    먼저 모든 인자에서 나쁘지 않고 적어도 하나에서 더 좋은 후보를 찾습니다. 변환 순서가 동률이면 나머지 tie-break 규칙을 적용합니다. 호출 가능한 후보가 없거나 유일한 최적 후보가 없으면 오버로드 해석에 실패합니다.

  1. 정수 두 개

    add(5, 7)은 두 인자가 정확히 일치하는 add(int, int)를 선택합니다.

  2. 실수 두 개

    add(3.5, 2.1)은 두 인자가 정확히 일치하는 add(double, double)을 선택합니다.

  3. 정수 세 개

    add(1, 2, 3)은 인자 개수가 맞는 세 매개변수 후보를 선택합니다.

  4. 문자열 리터럴

    add("Hello", "World")은 const char[N]에서 std::string으로 가는 사용자 정의 변환을 거쳐 문자열 후보를 선택합니다. 정확한 일치는 아닙니다.

  5. long · 모호함

    print(10L)은 long에서 int와 double로 가는 변환이 모두 conversion 순위이므로 유일한 최적 후보가 없어 컴파일 오류입니다.

  6. bool · 정수 선택

    print(true)는 bool에서 int로 가는 promotion이 double로 가는 conversion보다 좋아 정수 후보를 선택합니다.

유일한 best

오버로드 해석 성공

모든 비교와 tie-break 뒤 하나의 최적 후보가 남습니다. 다만 선택된 함수가 삭제됐거나 접근할 수 없는 등 후속 검사를 통과하지 못하면 최종 호출은 여전히 ill-formed입니다.

없음 또는 동률

컴파일 오류

호출 가능한 후보가 없거나 최적 후보가 둘 이상이면 컴파일러가 임의로 하나를 선택하지 않습니다.

핵심: 선언 identity와 호출 해석은 별개입니다. 함께 선언할 수 있어도 특정 인자에서는 모호해질 수 있습니다.

이 예의 조건

  • 본문과 원 도표에 제시된 사례의 입력·조건을 사용합니다. 설명용 수치나 독립 비교 경로는 각 단계에 표시합니다.
  • 현재 상태·값과 전환 근거의 위치는 모든 단계에서 같습니다. 조건이 맞지 않거나 실행되지 않은 경로를 성공한 동작으로 해석하지 않습니다.
관계도의 보충 설명
원 도표의 제목·노드·관계·조건·주석
범위원문
관계·조건 설명일곱 노드와 일곱 간선이다. 이름 조회→호출 가능성→후보 존재 조건으로 이어진다. 후보 없음 분기는 오류로 우회한다. 후보 있음은 인자별 순위→유일 best 조건으로 진행한다. 유일 best의 예 분기는 선택 함수로, 아니요 분기는 오류로 향한다. 오류 노드의 후보 없음과 모호함은 서로 다른 실패 이유다. 선택 뒤 삭제/접근성 등 후속 검사를 통과해야 한다. 모바일도 같은 조건과 우회 관계다.
예의 값과 변화
관찰·상황값·보충 정보변화·이유
후보 집합

본문의 choose(1,2)를 두 선언에 대응시킨다.

  • A: choose(int,double)
  • B: choose(double,int)
  • 이름 조회로 보이는 두 선언을 검토한다; 내부 pass의 시간표가 아니다.
인자별 변환

두 후보는 모두 호출 가능하나 우열이 갈린다.

  • A: 정확한 일치 / 변환
  • B: 변환 / 정확한 일치
  • 각 인자의 순위를 따로 비교하며 총비용을 더해 고르지 않는다.
최종 판정

모든 인자에서 나쁘지 않은 유일 후보가 없다.

  • 판정: 모호한 컴파일 오류
  • 호출: 실행하지 않음
  • A와 B가 서로 다른 인자에서 우세하므로 둘 중 하나를 임의로 선택할 수 없다.

C++ 오버로드 해석 흐름과 호출 판독

이름 조회 후보→호출 가능 후보→제공된 각 인자의 변환 비교→유일 best/오류는 의미상 후보를 좁히는 순서다. 후보별 viability와 변환 순위 장부를 실제 호출 하나에 적용하며 컴파일러 내부 pass·변환 비용 합·실패 후 런타임 호출을 만들지 않는다.

C++ 오버로드 해석이 이름 조회 후보를 호출 가능한 함수로 좁히고 인자별 변환 순위를 비교해 유일한 최적 후보 또는 오류를 결정하는 과정

Lookup → viable → rank → best

인자별 변환을 비교해 유일한 최적 오버로드를 고른다

오버로드 해석은 변환 비용을 모두 더하는 계산이 아닙니다. 호출 지점에서 찾은 후보를 걸러낸 뒤 각 인자의 변환 순서를 비교하여 유일한 최적 후보를 찾아야 합니다.

호출 가능한 후보에서 유일한 최적 후보 또는 오류로 분기한다일곱노드와일곱간선이다. 이름조회→호출가능성→후보존재조건. 후보없음분기는오류로 우회한다. 후보있음은 인자별순위→유일best조건으로 진행한다. 유일best 예는선택함수로,아니요는오류로향한다. 오류노드의후보없음과모호함은서로다른실패이유다. 선택뒤삭제/접근성등후속검사를통과해야한다. 모바일도같은조건과우회관계다.이름 조회보이는 선언의 후보호출 가능성개수·변환·기본값후보가 있나요?없으면 호출 오류인자별 순위모두 나쁘지 않은가?유일한 best?동률 규칙까지 비교선택한 함수후속 적합성도 검사호출 오류후보 없음 / 모호함예예아니요아니요인자별 우열을 비교하며변환 비용 합으로 고르지 않음
호출 가능한 후보에서 유일한 최적 후보 또는 오류로 분기한다일곱노드와일곱간선이다. 이름조회→호출가능성→후보존재조건. 후보없음분기는오류로 우회한다. 후보있음은 인자별순위→유일best조건으로 진행한다. 유일best 예는선택함수로,아니요는오류로향한다. 오류노드의후보없음과모호함은서로다른실패이유다. 선택뒤삭제/접근성등후속검사를통과해야한다. 모바일도같은조건과우회관계다.이름 조회보이는 선언의 후보호출 가능성개수·변환·기본값후보가 있나요?없으면 호출 오류인자별 순위모두 나쁘지 않은가?유일한 best?동률 규칙까지 비교선택한 함수후속 적합성도 검사호출 오류후보 없음 / 모호함예예아니요아니요인자별 우열을 비교하며변환 비용 합으로 고르지 않음
해석 절차
  1. 이름 조회로 후보 집합을 만든다

    호출 지점에서 조회되는 같은 이름의 선언들이 후보가 됩니다. 프로그램 전체의 같은 이름을 무조건 모두 모으는 것은 아닙니다.

  2. 호출 가능한 함수만 남긴다

    인자 개수, 기본 인자, 각 인자에서 매개변수 타입으로 갈 수 있는 암시적 변환을 검사합니다.

  3. 각 인자의 변환 순위를 비교한다

    기본 표준 변환 순위는 exact match가 promotion보다, promotion이 conversion보다 좋고, 표준 변환 순서는 사용자 정의 변환 순서보다 우선합니다.

  4. 유일한 최적 후보를 결정한다

    먼저 모든 인자에서 나쁘지 않고 적어도 하나에서 더 좋은 후보를 찾습니다. 변환 순서가 동률이면 나머지 tie-break 규칙을 적용합니다. 호출 가능한 후보가 없거나 유일한 최적 후보가 없으면 오버로드 해석에 실패합니다.

  1. 정수 두 개

    add(5, 7)은 두 인자가 정확히 일치하는 add(int, int)를 선택합니다.

  2. 실수 두 개

    add(3.5, 2.1)은 두 인자가 정확히 일치하는 add(double, double)을 선택합니다.

  3. 정수 세 개

    add(1, 2, 3)은 인자 개수가 맞는 세 매개변수 후보를 선택합니다.

  4. 문자열 리터럴

    add("Hello", "World")은 const char[N]에서 std::string으로 가는 사용자 정의 변환을 거쳐 문자열 후보를 선택합니다. 정확한 일치는 아닙니다.

  5. long · 모호함

    print(10L)은 long에서 int와 double로 가는 변환이 모두 conversion 순위이므로 유일한 최적 후보가 없어 컴파일 오류입니다.

  6. bool · 정수 선택

    print(true)는 bool에서 int로 가는 promotion이 double로 가는 conversion보다 좋아 정수 후보를 선택합니다.

유일한 best

오버로드 해석 성공

모든 비교와 tie-break 뒤 하나의 최적 후보가 남습니다. 다만 선택된 함수가 삭제됐거나 접근할 수 없는 등 후속 검사를 통과하지 못하면 최종 호출은 여전히 ill-formed입니다.

없음 또는 동률

컴파일 오류

호출 가능한 후보가 없거나 최적 후보가 둘 이상이면 컴파일러가 임의로 하나를 선택하지 않습니다.

핵심: 선언 identity와 호출 해석은 별개입니다. 함께 선언할 수 있어도 특정 인자에서는 모호해질 수 있습니다.

이 예의 조건

  • 본문과 원 도표에 제시된 사례의 입력·조건을 사용합니다. 설명용 수치나 독립 비교 경로는 각 단계에 표시합니다.
  • 현재 상태·값과 전환 근거의 위치는 모든 단계에서 같습니다. 조건이 맞지 않거나 실행되지 않은 경로를 성공한 동작으로 해석하지 않습니다.

관계도의 보충 설명

원 도표의 제목·노드·관계·조건·주석
범위원문
관계·조건 설명일곱 노드와 일곱 간선이다. 이름 조회→호출 가능성→후보 존재 조건으로 이어진다. 후보 없음 분기는 오류로 우회한다. 후보 있음은 인자별 순위→유일 best 조건으로 진행한다. 유일 best의 예 분기는 선택 함수로, 아니요 분기는 오류로 향한다. 오류 노드의 후보 없음과 모호함은 서로 다른 실패 이유다. 선택 뒤 삭제/접근성 등 후속 검사를 통과해야 한다. 모바일도 같은 조건과 우회 관계다.
예의 값과 변화
관찰·상황값·보충 정보변화·이유
후보 집합

본문의 choose(1,2)를 두 선언에 대응시킨다.

  • A: choose(int,double)
  • B: choose(double,int)
  • 이름 조회로 보이는 두 선언을 검토한다; 내부 pass의 시간표가 아니다.
인자별 변환

두 후보는 모두 호출 가능하나 우열이 갈린다.

  • A: 정확한 일치 / 변환
  • B: 변환 / 정확한 일치
  • 각 인자의 순위를 따로 비교하며 총비용을 더해 고르지 않는다.
최종 판정

모든 인자에서 나쁘지 않은 유일 후보가 없다.

  • 판정: 모호한 컴파일 오류
  • 호출: 실행하지 않음
  • A와 B가 서로 다른 인자에서 우세하므로 둘 중 하나를 임의로 선택할 수 없다.

모든 인자에서 나쁘지 않은 유일한 후보가 없으므로 컴파일러는 둘 중 하나를 마음대로 고르지 않고 모호한 호출(ambiguous call)로 컴파일 오류를 냅니다. 호출 가능한 후보가 하나도 없을 때의 오류와는 다른 실패입니다. 이런 호출은 choose(1, static_cast<double>(2))처럼 인자의 타입을 밝히거나, 두 함수가 하는 일이 다르다면 이름을 나누어 해결합니다.

뒤의 기본 매개변수에서 볼 기본값도 모호함을 만들 수 있습니다. void f(int)와 void f(int, int = 0)이 함께 있으면 f(1)은 두 함수가 모두 같은 조건으로 호출 가능해 모호합니다. 오버로드를 늘릴 때는 같은 호출 모양을 받아들이는 함수가 둘 생기지 않는지 확인합니다.

main은 오버로딩할 수 없습니다

프로그램에는 전역 main 함수가 정확히 하나 있어야 하므로 main은 오버로딩할 수 없습니다. 컴파일러는 적어도 int main()과 int main(int argc, char* argv[]) 두 형태를 받아들여야 하며, 반환 타입은 언제나 int입니다. 두 형태 가운데 하나를 골라 정의합니다.

이름 맹글링

오버로드된 함수들은 소스에서 이름이 같지만, 링커는 이들을 서로 다른 함수로 구별해야 합니다. 그래서 컴파일러는 함수 이름에 매개변수 타입 정보를 덧붙인 심볼 이름을 만들며, 이것을 이름 맹글링(name mangling)이라고 합니다.

MSYS2의 GCC를 포함해 대부분의 GCC와 Clang이 따르는 Itanium C++ ABI에서는 add(int, int)가 _Z3addii, add(double, double)이 _Z3adddd처럼 표현됩니다. 반환 타입은 이 이름에 들어가지 않으며, 이것도 반환 타입만으로 오버로드를 구별할 수 없는 것과 맞아떨어집니다. MSVC는 다른 규칙을 쓰고 C++ 표준은 특정 형식을 요구하지 않으므로, 맹글링은 컴파일러가 오버로딩을 구현하는 방식으로 이해하면 됩니다.

더 깊이: 맹글링을 끄는 extern "C"와 맹글링 풀기

C로 작성한 라이브러리의 함수는 맹글링 없이 이름 그대로 목적 파일에 들어 있으므로, C++에서 그 함수를 부르려면 컴파일러가 맹글링한 이름을 찾지 않게 해야 합니다. extern "C" int open(const char* path, int flags, ...);처럼 선언하면 그 함수는 C 언어 링크(language linkage)를 가지며, 호출 규약과 이름을 만드는 방식이 C에 맞춰집니다. 반대로 C++로 정의한 함수에 extern "C"를 붙이면 C 코드에서 그 함수를 부를 수 있습니다. 대신 C 언어 링크를 가진 같은 이름의 함수 선언은 이름 공간이 달라도 같은 함수를 가리키므로, extern "C" 함수는 매개변수만 바꾼 오버로드를 둘 수 없습니다.

링커 오류나 디버거에 맹글링된 이름이 보이면 도구로 되돌려 읽을 수 있습니다. GNU binutils의 c++filt에 _Z3addii를 넘기면 add(int, int)로 바꾸어 보여 주며, 인자 없이 실행하면 표준 입력으로 받은 이름을 차례로 풀어 줍니다. MSVC의 이름은 형식이 달라 undname.exe로 풀며, Microsoft의 문서에 따르면 64비트 환경의 extern "C" 함수는 __vectorcall 호출 규약을 쓸 때만 이름이 꾸며집니다.

정리

  • 같은 이름의 함수는 매개변수 타입의 목록이 달라야 오버로드가 되며, 반환 타입이나 매개변수 이름만 다른 것은 오버로드가 아닙니다.
  • 값 매개변수의 맨 바깥 const와 배열 매개변수의 포인터 변환 때문에 겉보기와 달리 같은 함수가 되는 경우가 있습니다.
  • 컴파일러는 인자마다 정확한 일치, 승격, 표준 변환, 사용자 정의 변환의 순서로 순위를 비교해 함수를 고릅니다.
  • 문자열 리터럴은 std::string보다 bool로 먼저 변환되므로, bool과 문자열을 받는 오버로드를 함께 둘 때 주의합니다.
  • 모든 인자에서 나쁘지 않은 유일한 후보가 없으면 모호한 호출로 컴파일 오류이며, 인자의 타입을 밝히거나 이름을 나누어 해결합니다.

연습 문제

1. 순위 비교

conversion_rank.cpp에서 print(short{3})과 print(3u)는 각각 어떻게 되나요?

해설 보기

short는 int로의 승격이 있으므로 print(int)를 골라 int 3을 출력합니다. 3u는 unsigned int이며, int로 가는 것은 승격이 아닌 정수 변환이고 double로 가는 것도 변환이어서 순위가 같습니다. 어느 쪽도 더 낫지 않으므로 print(10L)처럼 모호한 호출로 컴파일 오류가 납니다.

2. 두 인자의 우열

choose.cpp에서 choose(1.0, 2.0)은 어떻게 되나요?

해설 보기

A는 첫째 인자에 double에서 int로의 변환이 필요하고 둘째는 정확히 맞습니다. B는 첫째가 정확히 맞고 둘째에 변환이 필요합니다. 우열이 엇갈려 모든 인자에서 나쁘지 않은 후보가 없으므로, choose(1, 2)처럼 모호한 호출로 컴파일 오류입니다.

3. 서로 다른 오버로드인가

다음 네 쌍 가운데 서로 다른 두 오버로드가 되는 것은 무엇인가요? read(int)와 read(const int), scan(char[])과 scan(char*), f(int&)와 f(const int&), g(int)와 g(long)

해설 보기

f(int&)와 f(const int&), g(int)와 g(long)이 서로 다른 오버로드입니다. 앞의 두 쌍은 맨 바깥 const가 빠지고 배열 매개변수가 포인터로 바뀌어 같은 함수가 되므로, 둘 다 본문을 정의하면 같은 함수를 두 번 정의한 컴파일 오류가 됩니다.

다음 글 인라인 함수에서는 inline 키워드가 실제로 정하는 것과, 컴파일러의 인라인 확장이 서로 다른 이야기라는 점을 다룹니다.