Как компилятор C++ выбирает функцию из списка доступных перегрузок?

Ответ

Разрешение перегрузки (overload resolution) — это процесс выбора наиболее подходящей функции для данного вызова. Он происходит в несколько чётких этапов.

1. Поиск имён (Name Lookup): Компилятор находит все функции с указанным именем в текущей области видимости (с учётом пространств имён и ADL — Argument-Dependent Lookup).

2. Формирование набора кандидатов: Из найденных функций отбираются те, которые можно вызвать с данным числом аргументов (после учёта аргументов по умолчанию).

3. Ранжирование кандидатов: Для каждого кандидата определяется, насколько хорошо типы аргументов соответствуют типам параметров. Устанавливается «ранг» преобразования:

Ранг преобразования Пример (вызов f(arg)) Приоритет
Точное совпадение Типы идентичны, включая const/volatile. f(int)f(42) Наивысший
Продвижение (Promotion) Целочисленные: charint, boolint. f(int)f('a') Высокий
Стандартное преобразование intdouble, указатель→bool. f(double)f(42) Средний
Пользовательское преобразование Через конструктор или оператор. f(MyClass)f(42) Низкий
Многоточие (...) f(int, ...)f(42, 1.0) Наинизший

4. Выбор наилучшей функции:

  • Выбирается функция, для которой все преобразования аргументов имеют не хуже ранг, чем у любого другого кандидата.
  • Если для какого-то кандидата есть хотя бы один аргумент с лучшим рангом преобразования, он побеждает.
  • Если два кандидата одинаково хороши — возникает неоднозначность (ambiguity), и компилятор выдаёт ошибку.

Пример с ранжированием:

void process(int);        // (1)
void process(double);     // (2)
void process(long, int=0);// (3)

process(10);    // Выбирает (1): точное совпадение (int ← int).
process(10.0f); // Выбирает (2): float → double (стандартное) лучше, чем float → int (стандартное) или float → long (стандартное). Ранги равны, но стандартные преобразования имеют разную «стоимость». 
process(10L);   // Ошибка неоднозначности: (2) требует long → double, (3) — точное совпадение по первому аргументу. Ни одна функция не лучше другой по всем аргументам.

Важные нюансы:

  • Шаблонные функции участвуют в перегрузке. Нешаблонная функция обычно приоритетнее шаблонной при прочих равных.
  • Учитываются ссылки, rvalue-ссылки и const.
  • Преобразования, требующие const_cast, не рассматриваются.

Ответ 18+ 🔞

А, ну ты глянь, какой интересный вопрос подкинули. Разрешение перегрузки, говоришь? Ну это, бля, целая операция, как в хорошем детективе — компилятор тут сыщик, который из кучи подозреваемых (функций) одного самого подходящего вычислить должен. Сейчас разжуём, как это всё внутри происходит, без соплей.

1. Поиск имён (Name Lookup): Всё начинается с того, что компилятор, этот хитрожопый ищейка, начинает шарить по всем закоулкам твоего кода. Ему надо найти всех, кто носит это имя — process, print, calculate, неважно. Смотрит в текущей области, лезет в неймспейсы, если они есть, и включает свой режим «ADL» — это когда он ещё и в пространствах имён, откуда пришли аргументы, ищет. Короче, собирает всех «в оцепление».

2. Формирование набора кандидатов: Дальше начинается отсев. Из всей этой толпы отбираются только те, кого вообще можно позвать с тем количеством аргументов, которое ты указал. Тут же учитываются аргументы по умолчанию — если функция говорит «я могу с двумя, но второй у меня по дефолту ноль», то её тоже в список кандидатов запишут. Остальные — свободны, нахуй.

3. Ранжирование кандидатов: Вот тут самое мясо. Каждого из оставшихся «подозреваемых» начинают проверять на совместимость с уликами (аргументами). Смотрят, насколько твои переданные типы похожи на то, что функция ждёт. И выставляют «ранг» — типа, насколько сильно пришлось извращаться, чтобы подогнать аргумент под параметр.

Смотри, вот тебе таблица, чтобы не охуеть от этого всего:

Ранг преобразования Пример (вызов f(arg)) Приоритет
Точное совпадение Типы один в один, даже const/volatile на месте. f(int)f(42) Наивысший, идеал, мечта
Продвижение (Promotion) Это как мелкие целочисленные типы (char, bool) превращаются в int. f(int)f('a') Высокий, почти почётно
Стандартное преобразование Уже посерьёзнее: int в double, указатель в bool. f(double)f(42) Средний, бывает
Пользовательское преобразование Вот тут уже через конструктор или оператор каст происходит. f(MyClass)f(42) Низкий, компилятор морщится, но терпит
Многоточие (...) А это полный пиздец, «что угодно на вход». f(int, ...)f(42, 1.0) Наинизший, крайний случай, когда уже нихуя не подходит

4. Выбор наилучшей функции: А теперь — момент истины, ёпта. Компилятор смотрит на ранги всех кандидатов.

  • Победит та функция, у которой все преобразования для всех аргументов оказались не хуже, чем у любого другого конкурента.
  • Если же нашёлся хоть один чувак, у которого для какого-то аргумента преобразование лучше (ранг выше), а для остальных не хуже — то он чемпион, остальные в утиль.
  • А вот если вышла ничья — два кандидата одинаково хороши (или одинаково плохи) — компилятор просто разводит руками и орет: неоднозначность (ambiguity), ошибка, разбирайтесь сами, пидарасы.

Пример с ранжированием, чтобы вникнуть:

void process(int);        // (1)
void process(double);     // (2)
void process(long, int=0);// (3)

process(10);    // Выберет (1): тут вообще сказка — точное совпадение (int ← int). Остальные даже рядом не стояли.
process(10.0f); // Выберет (2): тут float нужно к double привести (стандартное преобразование). Вариант (1) тоже требует float → int (стандартное), но преобразование к double считается «лучше» для float. Вариант (3) вообще мимо, ему long нужен.
process(10L);   // А вот тут, ядрёна вошь, облом! (2) требует long → double (стандартное), а (3) — точное совпадение по первому аргументу (long ← long). Ни одна не лучше другой по всем параметрам — неоднозначность, пиздец.

Важные нюансы, без которых нихуя не понятно:

  • Шаблонные функции тоже в этой движухе участвуют. Но если на арене есть и шаблонная, и обычная функция, и они одинаково подходят, то компилятор, как честный судья, обычно выбирает нешаблонную — она приоритетнее.
  • Все эти ссылки (&), rvalue-ссылки (&&) и const — компилятор их видит и учитывает. Это тоже часть «точного совпадения».
  • А вот преобразования, которые требуют const_cast — на них компилятор сразу забивает болт. Не, ну а чё, это уже твои проблемы, чувак.