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

«Как компилятор C++ выбирает функцию из списка доступных перегрузок?» — вопрос из категории C++ Core, который задают на 25% собеседований C/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, не рассматриваются.